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