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 }