1 /**
2 	Local and REST API access.
3 
4 	Copyright: © 2015-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.api;
9 
10 import userman.db.controller : UserManController, UserManCommonSettings;
11 static import userman.db.controller;
12 
13 import vibe.data.json : Json;
14 import vibe.http.router : URLRouter;
15 import vibe.http.common : enforceHTTP;
16 import vibe.http.status : HTTPStatus;
17 import vibe.inet.url : URL;
18 import vibe.web.rest;
19 
20 /**
21 	Registers a RESTful interface to the UserMan API on the given router.
22 */
23 void registerUserManRestInterface(URLRouter router, UserManController ctrl)
24 {
25 	router.registerRestInterface(new UserManAPIImpl(ctrl));
26 }
27 
28 /**
29 	Returns an API instance for accessing a process local UserMan instance.
30 */
31 UserManAPI createLocalUserManAPI(UserManController ctrl)
32 {
33 	return new UserManAPIImpl(ctrl);
34 }
35 
36 /**
37 	Returns an API instance for accessing a RESTful remove UserMan instance.
38 */
39 RestInterfaceClient!UserManAPI createUserManRestAPI(URL base_url)
40 {
41 	return new RestInterfaceClient!UserManAPI(base_url);
42 }
43 
44 
45 /// Root entry point for the UserMan API
46 interface UserManAPI {
47 @safe:
48 	/// Interface suitable for manipulating user information
49 	@property Collection!UserManUserAPI users();
50 
51 	/// Interface suitable for manipulating group information
52 	@property Collection!UserManGroupAPI groups();
53 
54 	@property UserManAPISettings settings();
55 }
56 
57 class UserManAPISettings : UserManCommonSettings {
58 
59 }
60 
61 /// Interface suitable for manipulating user information
62 interface UserManUserAPI {
63 @safe:
64 	struct CollectionIndices {
65 		User.ID _user;
66 	}
67 
68 	/// Gets the total number of registered users.
69 	@property long count();
70 
71 	/// Accesses the properties of a user.
72 	@property Collection!UserManUserPropertyAPI properties(User.ID _user);
73 
74 	/// Tests a username/e-mail and password combination for validity.
75 	User.ID testLogin(string name, string password);
76 
77 	/// Registers a new user.
78 	User.ID register(string email, string name, string full_name, string password);
79 
80 	/// Invites a user.
81 	User.ID invite(string email, string full_name, string message, bool send_mail = true);
82 
83 	/// Activates a user account using an activation code.
84 	void activate(string email, string activation_code);
85 
86 	/// Re-sends an e-mail containing the account activation code.
87 	void resendActivation(string email);
88 
89 	/// Sends an e-mail with a password reset code.
90 	void requestPasswordReset(string email);
91 
92 	/// Sets a new password using a password reset code.
93 	void resetPassword(string email, string reset_code, string new_password);
94 
95 	/// Gets information about a user.
96 	User get(User.ID _user);
97 
98 	/// Gets information about a user using the user name as the identifier.
99 	User getByName(string q);
100 
101 	/// Gets information about a user using the e-mail address as the identifier.
102 	User getByEmail(string q);
103 
104 	/// Gets information about a user using the user name or e-mail address as the identifier.
105 	User getByEmailOrName(string q);
106 
107 	/// Gets information about a range of users, suitable for pagination.
108 	User[] getRange(int first_user, int max_count);
109 
110 	/// Deletes a user account.
111 	void remove(User.ID _user);
112 	//void update(in ref User user);
113 
114 	/// Updates the e-mail address of a user account.
115 	void setEmail(User.ID _user, string email);
116 
117 	/// Updates the display name of a user.
118 	void setFullName(User.ID _user, string full_name);
119 
120 	/// Sets a new password.
121 	void setPassword(User.ID _user, string password);
122 
123 	/// Sets the activation state of a user.
124 	void setActive(User.ID _user, bool active);
125 
126 	/// Sets the banned state of a user.
127 	void setBanned(User.ID _user, bool banned);
128 
129 	/// Returns the names of all groups the user is in.
130 	string[] getGroups(User.ID _user);
131 }
132 
133 interface UserManUserPropertyAPI {
134 @safe:
135 	struct CollectionIndices {
136 		User.ID _user;
137 		string _name;
138 	}
139 
140 	Json[string] get(User.ID _user);
141 	Json get(User.ID _user, string _name);
142 	void set(User.ID _user, string _name, Json value);
143 	void remove(User.ID _user, string _name);
144 }
145 
146 struct User {
147 	alias ID = userman.db.controller.User.ID;
148 	ID id;
149 	bool active;
150 	bool banned;
151 	string name;
152 	string fullName;
153 	string email;
154 
155 	this(userman.db.controller.User usr)
156 	@safe {
157 		assert(&this !is null);
158 
159 		this.id = usr.id;
160 		this.active = usr.active;
161 		this.banned = usr.banned;
162 		this.name = usr.name;
163 		this.fullName = usr.fullName;
164 		this.email = usr.email;
165 	}
166 }
167 
168 /// Interface suitable for manipulating group information
169 interface UserManGroupAPI {
170 @safe:
171 	struct CollectionIndices {
172 		string _group;
173 	}
174 
175 	Collection!UserManGroupMemberAPI members(string _group);
176 
177 	/// The total number of groups.
178 	@property long count();
179 
180 	/// Creates a new group.
181 	void create(string name, string description);
182 
183 	/// Removes a group.
184 	void remove(string _group);
185 
186 	/// Gets information about an existing group.
187 	//Group getByID(Group.ID id);
188 
189 	/// Sets the description of a group.
190 	void setDescription(string _group, string description);
191 
192 	/// Gets information about a group using its name as the identifier.
193 	Group get(string _group);
194 
195 	/// Gets a range of groups, suitable for pagination.
196 	Group[] getRange(long first_group, long max_count);
197 }
198 
199 interface UserManGroupMemberAPI {
200 @safe:
201 	struct CollectionIndices {
202 		string _group;
203 		User.ID _user;
204 	}
205 
206 	/// Gets the number of members of a certain group.
207 	long count(string _group);
208 
209 	/// Gets a list of group members, suitable for pagination.
210 	User.ID[] getRange(string _group, long first_member, long max_count);
211 
212 	/// Adds a user to a group.
213 	void add(string _group, User.ID user_id);
214 
215 	/// Removes a user from a group.
216 	void remove(string _group, User.ID _user);
217 }
218 
219 struct Group {
220 	string id;
221 	string description;
222 	//Json[string] properties;
223 
224 	this(userman.db.controller.Group grp)
225 	@safe {
226 		this.id = grp.id;
227 		this.description = grp.description;
228 	}
229 }
230 
231 private class UserManAPIImpl : UserManAPI {
232 	private {
233 		UserManController m_ctrl;
234 		UserManUserAPIImpl m_users;
235 		UserManGroupAPIImpl m_groups;
236 		UserManAPISettings m_settings;
237 	}
238 
239 	this(UserManController ctrl)
240 	{
241 		m_ctrl = ctrl;
242 		m_users = new UserManUserAPIImpl(ctrl);
243 		m_groups = new UserManGroupAPIImpl(ctrl);
244 		m_settings = new UserManAPISettings;
245 		m_settings.userNameSettings = ctrl.settings.userNameSettings;
246 		m_settings.useUserNames = ctrl.settings.useUserNames;
247 		m_settings.requireActivation = ctrl.settings.requireActivation;
248 		m_settings.serviceName = ctrl.settings.serviceName;
249 		m_settings.serviceURL = ctrl.settings.serviceURL;
250 		m_settings.serviceEmail = ctrl.settings.serviceEmail;
251 	}
252 
253 	@property Collection!UserManUserAPI users() { return Collection!UserManUserAPI(m_users); }
254 	@property Collection!UserManGroupAPI groups() { return Collection!UserManGroupAPI(m_groups); }
255 	@property UserManAPISettings settings() { return m_settings; }
256 }
257 
258 private class UserManUserAPIImpl : UserManUserAPI {
259 	private {
260 		UserManController m_ctrl;
261 		UserManUserPropertyAPIImpl m_properties;
262 	}
263 
264 	this(UserManController ctrl)
265 	{
266 		m_ctrl = ctrl;
267 		m_properties = new UserManUserPropertyAPIImpl(m_ctrl);
268 	}
269 
270 	@property long count()
271 	{
272 		return m_ctrl.getUserCount();
273 	}
274 
275 	@property Collection!UserManUserPropertyAPI properties(User.ID _id)
276 	{
277 		return Collection!UserManUserPropertyAPI(m_properties, _id);
278 	}
279 
280 	User.ID testLogin(string name, string password)
281 	{
282 		auto ret = m_ctrl.testLogin(name, password);
283 		enforceHTTP(!ret.isNull, HTTPStatus.unauthorized, "Wrong user name or password.");
284 		return ret.get();
285 	}
286 
287 	User.ID register(string email, string name, string full_name, string password)
288 	{
289 		return m_ctrl.registerUser(email, name, full_name, password);
290 	}
291 
292 	User.ID invite(string email, string full_name, string message, bool send_mail = true)
293 	{
294 		return m_ctrl.inviteUser(email, full_name, message, send_mail);
295 	}
296 
297 	void activate(string email, string activation_code)
298 	{
299 		m_ctrl.activateUser(email, activation_code);
300 	}
301 
302 	void resendActivation(string email)
303 	{
304 		m_ctrl.resendActivation(email);
305 	}
306 
307 	void requestPasswordReset(string email)
308 	{
309 		m_ctrl.requestPasswordReset(email);
310 	}
311 
312 	void resetPassword(string email, string reset_code, string new_password)
313 	{
314 		m_ctrl.resetPassword(email, reset_code, new_password);
315 	}
316 
317 	User get(User.ID id)
318 	{
319 		// COMPILERBUG: For an unconceivable reason, the this pointer of the
320 		// constructed struct is null inside the constructor if it is returned
321 		// directly, here. Creating a variable first works around this.
322 		auto ret = User(m_ctrl.getUser(id));
323 		return ret;
324 	}
325 
326 	User getByName(string q)
327 	{
328 		return User(m_ctrl.getUserByName(q));
329 	}
330 
331 	User getByEmail(string q)
332 	{
333 		return User(m_ctrl.getUserByEmail(q));
334 	}
335 
336 	User getByEmailOrName(string q)
337 	{
338 		return User(m_ctrl.getUserByEmailOrName(q));
339 	}
340 
341 	User[] getRange(int first_user, int max_count)
342 	{
343 		import std.array : appender;
344 		auto ret = appender!(User[]);
345 		m_ctrl.enumerateUsers(first_user, max_count, (ref usr) { ret ~= User(usr); });
346 		return ret.data;
347 	}
348 
349 	void remove(User.ID id)
350 	{
351 		m_ctrl.deleteUser(id);
352 	}
353 
354 	//void update(in ref User user);
355 
356 	void setEmail(User.ID id, string email)
357 	{
358 		m_ctrl.setEmail(id, email);
359 	}
360 
361 	void setFullName(User.ID id, string full_name)
362 	{
363 		m_ctrl.setFullName(id, full_name);
364 	}
365 
366 	void setPassword(User.ID id, string password)
367 	{
368 		m_ctrl.setPassword(id, password);
369 	}
370 
371 	void setActive(User.ID id, bool active)
372 	{
373 		// FIXME: efficiency and atomicity
374 		auto usr = m_ctrl.getUser(id);
375 		if (usr.active != active) {
376 			usr.active = active;
377 			m_ctrl.updateUser(usr);
378 		}
379 	}
380 
381 	void setBanned(User.ID id, bool banned)
382 	{
383 		// FIXME: efficiency and atomicity
384 			import vibe.core.log; logInfo("DO ITMAYBE");
385 		auto usr = m_ctrl.getUser(id);
386 		if (usr.banned != banned) {
387 			usr.banned = banned;
388 			m_ctrl.updateUser(usr);
389 			import vibe.core.log; logInfo("DO IT");
390 		}
391 	}
392 
393 	string[] getGroups(User.ID id)
394 	{
395 		return m_ctrl.getUser(id).groups;
396 	}
397 }
398 
399 private class UserManUserPropertyAPIImpl : UserManUserPropertyAPI {
400 	private {
401 		UserManController m_ctrl;
402 	}
403 
404 	this(UserManController ctrl)
405 	{
406 		m_ctrl = ctrl;
407 	}
408 
409 	final override Json[string] get(User.ID _user)
410 	{
411 		return m_ctrl.getUser(_user).properties;
412 	}
413 
414 	final override Json get(User.ID _user, string _name)
415 	{
416 		auto props = m_ctrl.getUser(_user).properties;
417 		auto pv = _name in props;
418 		if (!pv) return Json(null);
419 		return *pv;
420 	}
421 
422 	final override void set(User.ID _user, string _name, Json value)
423 	{
424 		m_ctrl.setProperty(_user, _name, value);
425 	}
426 
427 	final override void remove(User.ID _user, string _name)
428 	{
429 		m_ctrl.removeProperty(_user, _name);
430 	}
431 }
432 
433 private class UserManGroupAPIImpl : UserManGroupAPI {
434 	private {
435 		UserManController m_ctrl;
436 		UserManGroupMemberAPIImpl m_members;
437 	}
438 
439 	this(UserManController ctrl)
440 	{
441 		m_ctrl = ctrl;
442 		m_members = new UserManGroupMemberAPIImpl(ctrl);
443 	}
444 
445 	Collection!UserManGroupMemberAPI members(string _group) { return Collection!UserManGroupMemberAPI(m_members, _group); }
446 
447 	@property long count()
448 	{
449 		return m_ctrl.getGroupCount();
450 	}
451 
452 	void create(string name, string description)
453 	{
454 		m_ctrl.addGroup(name, description);
455 	}
456 
457 	void remove(string name)
458 	{
459 		m_ctrl.removeGroup(name);
460 	}
461 
462 	void setDescription(string name, string description)
463 	{
464 		m_ctrl.setGroupDescription(name, description);
465 	}
466 
467 	/*Group getByID(Group.ID id)
468 	{
469 		return m_ctrl.getGroup(id);
470 	}*/
471 
472 	Group get(string id)
473 	{
474 		return Group(m_ctrl.getGroup(id));
475 	}
476 
477 	Group[] getRange(long first_group, long max_count)
478 	{
479 		import std.array : appender;
480 		auto ret = appender!(Group[]);
481 		m_ctrl.enumerateGroups(first_group, max_count, (ref grp) { ret ~= Group(grp); });
482 		return ret.data;
483 	}
484 }
485 
486 private class UserManGroupMemberAPIImpl : UserManGroupMemberAPI {
487 	private {
488 		UserManController m_ctrl;
489 	}
490 
491 	this(UserManController ctrl)
492 	{
493 		m_ctrl = ctrl;
494 	}
495 
496 	long count(string _group)
497 	{
498 		return m_ctrl.getGroupMemberCount(_group);
499 	}
500 
501 	User.ID[] getRange(string _group, long first_member, long max_count)
502 	{
503 		User.ID[] ret;
504 		m_ctrl.enumerateGroupMembers(_group, first_member, max_count, (id) { ret ~= id; });
505 		return ret;
506 	}
507 
508 	void add(string _group, User.ID user_id)
509 	{
510 		m_ctrl.addGroupMember(_group, user_id);
511 	}
512 
513 	void remove(string _group, User.ID _user)
514 	{
515 		m_ctrl.removeGroupMember(_group, _user);
516 	}
517 }
518 
519 
520 private {
521 	UserManAPI m_api;
522 }