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 }