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 }