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 IndexOptions opts; 62 opts.unique = true; 63 m_users.createIndex(IndexModel().add("name", 1).withOptions(opts)); 64 m_users.createIndex(IndexModel().add("email", 1).withOptions(opts)); 65 } 66 67 override bool isEmailRegistered(string email) 68 { 69 auto bu = m_users.findOne(["email": email], ["auth": true]); 70 return !bu.isNull() && bu["auth"]["method"].get!string.length > 0; 71 } 72 73 override User.ID addUser(ref User usr) 74 { 75 validateUser(usr); 76 enforce(m_users.findOne(["name": usr.name]).isNull(), "The user name is already taken."); 77 enforce(m_users.findOne(["email": usr.email]).isNull(), "The email address is already in use."); 78 79 usr.id = User.ID(BsonObjectID.generate()); 80 m_users.insert(usr); 81 82 return usr.id; 83 } 84 85 override User getUser(User.ID id) 86 { 87 auto usr = m_users.findOne!User(["_id": id.bsonObjectIDValue]); 88 enforce(!usr.isNull(), "The specified user id is invalid."); 89 return usr.get; 90 } 91 92 override User getUserByName(string name) 93 { 94 name = name.toLower(); 95 auto usr = m_users.findOne!User(["name": name]); 96 enforce(!usr.isNull(), "The specified user name is not registered."); 97 return usr.get; 98 } 99 100 override User getUserByEmail(string email) 101 { 102 email = email.toLower(); 103 auto usr = m_users.findOne!User(["email": email]); 104 enforce(!usr.isNull(), "There is no user account for the specified email address."); 105 return usr.get; 106 } 107 108 override User getUserByEmailOrName(string email_or_name) 109 { 110 auto usr = m_users.findOne!User(["$or": [["email": email_or_name.toLower()], ["name": email_or_name]]]); 111 enforce(!usr.isNull(), "The specified email address or user name is not registered."); 112 return usr.get; 113 } 114 115 alias enumerateUsers = UserManController.enumerateUsers; 116 override void enumerateUsers(long first_user, long max_count, scope void delegate(ref User usr) @safe del) 117 { 118 import std.conv : to; 119 foreach (usr; m_users.find!User(["query": null, "orderby": ["name": 1]], null, QueryFlags.None, first_user.to!int, max_count.to!int)) { 120 if (max_count-- <= 0) break; 121 del(usr); 122 } 123 } 124 125 override long getUserCount() 126 { 127 return m_users.count(Bson.emptyObject); 128 } 129 130 override void deleteUser(User.ID user_id) 131 { 132 m_users.remove(["_id": user_id.bsonObjectIDValue]); 133 } 134 135 override void updateUser(in ref User user) 136 { 137 validateUser(user); 138 enforce(m_settings.useUserNames || user.name == user.email, "User name must equal email address if user names are not used."); 139 // FIXME: enforce that no user names or emails are used twice! 140 141 m_users.update(["_id": user.id.bsonObjectIDValue], user); 142 } 143 144 override void setEmail(User.ID user, string email) 145 { 146 m_users.update(["_id": user.bsonObjectIDValue], ["$set": ["email": email]]); 147 } 148 149 override void setFullName(User.ID user, string full_name) 150 { 151 m_users.update(["_id": user.bsonObjectIDValue], ["$set": ["fullName": full_name]]); 152 } 153 154 override void setPassword(User.ID user, string password) 155 { 156 m_users.update(["_id": user.bsonObjectIDValue], ["$set": 157 ["auth.method": "password", "auth.passwordHash": generatePasswordHash(password)]]); 158 } 159 160 override void setProperty(User.ID user, string name, Json value) 161 { 162 m_users.update(["_id": user.bsonObjectIDValue], ["$set": ["properties."~name: value]]); 163 } 164 165 override void removeProperty(User.ID user, string name) 166 { 167 m_users.update(["_id": user.bsonObjectIDValue], ["$unset": ["properties."~name: ""]]); 168 } 169 170 override void addGroup(string id, string description) 171 { 172 enforce(isValidGroupID(id), "Invalid group ID."); 173 enforce(m_groups.findOne(["id": id]).isNull(), "A group with this name already exists."); 174 auto grp = new Group; 175 grp.id = id; 176 grp.description = description; 177 m_groups.insert(grp); 178 } 179 180 override void removeGroup(string id) 181 { 182 m_groups.remove(["id": id]); 183 } 184 185 override void setGroupDescription(string name, string description) 186 { 187 m_groups.update(["id": name], ["$set": ["description": description]]); 188 } 189 190 override long getGroupCount() 191 { 192 import vibe.data.bson : Bson; 193 return m_groups.count(Bson.emptyObject); 194 } 195 196 override Group getGroup(string name) 197 { 198 auto grp = m_groups.findOne!Group(["name": name]); 199 enforce(!grp.isNull(), "The specified group name is unknown."); 200 return grp.get; 201 } 202 203 alias enumerateGroups = UserManController.enumerateGroups; 204 override void enumerateGroups(long first_group, long max_count, scope void delegate(ref Group grp) @safe del) 205 { 206 import std.conv : to; 207 foreach (grp; m_groups.find!Group(["query": null, "orderby": ["id": 1]], null, QueryFlags.None, first_group.to!int, max_count.to!int)) { 208 if (max_count-- <= 0) break; 209 del(grp); 210 } 211 } 212 213 override void addGroupMember(string group, User.ID user) 214 { 215 assert(false); 216 } 217 218 override void removeGroupMember(string group, User.ID user) 219 { 220 assert(false); 221 } 222 223 override long getGroupMemberCount(string group) 224 { 225 assert(false); 226 } 227 228 alias enumerateGroupMembers = UserManController.enumerateGroupMembers; 229 override void enumerateGroupMembers(string group, long first_member, long max_count, scope void delegate(User.ID usr) @safe del) 230 { 231 assert(false); 232 } 233 }