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 }