1 /** 2 Database abstraction layer 3 4 Copyright: © 2012-2018 RejectedSoftware e.K. 5 License: Subject to the terms of the General Public License version 3, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module userman.db.mongo; 9 10 import userman.db.controller; 11 12 import vibe.core.log : logDiagnostic; 13 import vibe.db.mongo.mongo; 14 15 import std.exception : enforce; 16 import std..string; 17 import std.typecons : tuple; 18 19 20 class MongoUserManController : UserManController { 21 private { 22 MongoCollection m_users; 23 MongoCollection m_groups; 24 } 25 26 this(UserManSettings settings) 27 { 28 super(settings); 29 30 string database = "admin"; 31 MongoClientSettings dbSettings; 32 if (parseMongoDBUrl(dbSettings, settings.databaseURL)) 33 database = dbSettings.database; 34 35 auto db = connectMongoDB(settings.databaseURL).getDatabase(database); 36 m_users = db["userman.users"]; 37 m_groups = db["userman.groups"]; 38 39 // migrate old _id+name format to id (0.3.x -> 0.4.x) 40 foreach (usr; m_users.find(["groups": ["$type": cast(int)Bson.Type.objectID]])) { 41 logDiagnostic("Migrating user %s from 0.3.x to 0.4.x.", usr["_id"]); 42 string[] grps; 43 foreach (gid; usr["groups"]) { 44 auto g = m_groups.findOne(["_id": gid], ["name": true, "id": true]); 45 if (!g.isNull) { 46 auto gname = g.tryIndex("name"); 47 if (gname.isNull) gname = g["id"]; 48 grps ~= gname.get.get!string; 49 } 50 } 51 m_users.update(["_id": usr["_id"]], ["$set": ["groups": grps]]); 52 } 53 foreach (grp; m_groups.find(["name": ["$exists": true]])) { 54 logDiagnostic("Migrating group %s from 0.3.x to 0.4.x.", grp["name"].get!string); 55 auto n = grp["name"]; 56 grp.remove("name"); 57 grp["id"] = n; 58 m_groups.update(["_id": grp["_id"]], grp); 59 } 60 61 m_users.ensureIndex([tuple("name", 1)], IndexFlags.Unique); 62 m_users.ensureIndex([tuple("email", 1)], IndexFlags.Unique); 63 } 64 65 override bool isEmailRegistered(string email) 66 { 67 auto bu = m_users.findOne(["email": email], ["auth": true]); 68 return !bu.isNull() && bu["auth"]["method"].get!string.length > 0; 69 } 70 71 override User.ID addUser(ref User usr) 72 { 73 validateUser(usr); 74 enforce(m_users.findOne(["name": usr.name]).isNull(), "The user name is already taken."); 75 enforce(m_users.findOne(["email": usr.email]).isNull(), "The email address is already in use."); 76 77 usr.id = User.ID(BsonObjectID.generate()); 78 m_users.insert(usr); 79 80 return usr.id; 81 } 82 83 override User getUser(User.ID id) 84 { 85 auto usr = m_users.findOne!User(["_id": id.bsonObjectIDValue]); 86 enforce(!usr.isNull(), "The specified user id is invalid."); 87 return usr.get; 88 } 89 90 override User getUserByName(string name) 91 { 92 name = name.toLower(); 93 auto usr = m_users.findOne!User(["name": name]); 94 enforce(!usr.isNull(), "The specified user name is not registered."); 95 return usr.get; 96 } 97 98 override User getUserByEmail(string email) 99 { 100 email = email.toLower(); 101 auto usr = m_users.findOne!User(["email": email]); 102 enforce(!usr.isNull(), "There is no user account for the specified email address."); 103 return usr.get; 104 } 105 106 override User getUserByEmailOrName(string email_or_name) 107 { 108 auto usr = m_users.findOne!User(["$or": [["email": email_or_name.toLower()], ["name": email_or_name]]]); 109 enforce(!usr.isNull(), "The specified email address or user name is not registered."); 110 return usr.get; 111 } 112 113 alias enumerateUsers = UserManController.enumerateUsers; 114 override void enumerateUsers(long first_user, long max_count, scope void delegate(ref User usr) @safe del) 115 { 116 import std.conv : to; 117 foreach (usr; m_users.find!User(["query": null, "orderby": ["name": 1]], null, QueryFlags.None, first_user.to!int, max_count.to!int)) { 118 if (max_count-- <= 0) break; 119 del(usr); 120 } 121 } 122 123 override long getUserCount() 124 { 125 return m_users.count(Bson.emptyObject); 126 } 127 128 override void deleteUser(User.ID user_id) 129 { 130 m_users.remove(["_id": user_id.bsonObjectIDValue]); 131 } 132 133 override void updateUser(in ref User user) 134 { 135 validateUser(user); 136 enforce(m_settings.useUserNames || user.name == user.email, "User name must equal email address if user names are not used."); 137 // FIXME: enforce that no user names or emails are used twice! 138 139 m_users.update(["_id": user.id.bsonObjectIDValue], user); 140 } 141 142 override void setEmail(User.ID user, string email) 143 { 144 m_users.update(["_id": user.bsonObjectIDValue], ["$set": ["email": email]]); 145 } 146 147 override void setFullName(User.ID user, string full_name) 148 { 149 m_users.update(["_id": user.bsonObjectIDValue], ["$set": ["fullName": full_name]]); 150 } 151 152 override void setPassword(User.ID user, string password) 153 { 154 m_users.update(["_id": user.bsonObjectIDValue], ["$set": 155 ["auth.method": "password", "auth.passwordHash": generatePasswordHash(password)]]); 156 } 157 158 override void setProperty(User.ID user, string name, Json value) 159 { 160 m_users.update(["_id": user.bsonObjectIDValue], ["$set": ["properties."~name: value]]); 161 } 162 163 override void removeProperty(User.ID user, string name) 164 { 165 m_users.update(["_id": user.bsonObjectIDValue], ["$unset": ["properties."~name: ""]]); 166 } 167 168 override void addGroup(string id, string description) 169 { 170 enforce(isValidGroupID(id), "Invalid group ID."); 171 enforce(m_groups.findOne(["id": id]).isNull(), "A group with this name already exists."); 172 auto grp = new Group; 173 grp.id = id; 174 grp.description = description; 175 m_groups.insert(grp); 176 } 177 178 override void removeGroup(string id) 179 { 180 m_groups.remove(["id": id]); 181 } 182 183 override void setGroupDescription(string name, string description) 184 { 185 m_groups.update(["id": name], ["$set": ["description": description]]); 186 } 187 188 override long getGroupCount() 189 { 190 import vibe.data.bson : Bson; 191 return m_groups.count(Bson.emptyObject); 192 } 193 194 override Group getGroup(string name) 195 { 196 auto grp = m_groups.findOne!Group(["name": name]); 197 enforce(!grp.isNull(), "The specified group name is unknown."); 198 return grp.get; 199 } 200 201 alias enumerateGroups = UserManController.enumerateGroups; 202 override void enumerateGroups(long first_group, long max_count, scope void delegate(ref Group grp) @safe del) 203 { 204 import std.conv : to; 205 foreach (grp; m_groups.find!Group(["query": null, "orderby": ["id": 1]], null, QueryFlags.None, first_group.to!int, max_count.to!int)) { 206 if (max_count-- <= 0) break; 207 del(grp); 208 } 209 } 210 211 override void addGroupMember(string group, User.ID user) 212 { 213 assert(false); 214 } 215 216 override void removeGroupMember(string group, User.ID user) 217 { 218 assert(false); 219 } 220 221 override long getGroupMemberCount(string group) 222 { 223 assert(false); 224 } 225 226 alias enumerateGroupMembers = UserManController.enumerateGroupMembers; 227 override void enumerateGroupMembers(string group, long first_member, long max_count, scope void delegate(User.ID usr) @safe del) 228 { 229 assert(false); 230 } 231 }