def post(self, login): """ --- summary: Accept a pending registration description: | Accepts pending user registration. Requires `manage_users` capability. security: - bearerAuth: [] tags: - user parameters: - in: path name: login schema: type: string description: Login of pending account responses: 200: description: When user is successfully accepted content: application/json: schema: UserSuccessResponseSchema 400: description: When invalid login is provided. 403: description: When user doesn't have `manage_users` capability. 500: description: When SMTP server is unavailable or not properly configured on the server. """ user_login_obj = load_schema({"login": login}, UserLoginSchemaBase()) user = db.session.query(User).filter(User.login == login, User.pending == true()).first() user.pending = False user.registered_on = datetime.datetime.now() user.registered_by = g.auth_user.id db.session.add(user) try: send_email_notification( "register", "New account registered in MWDB", user.email, base_url=app_config.mwdb.base_url, login=user.login, set_password_token=user.generate_set_password_token().decode( "utf-8")) except MailError: logger.exception("Can't send e-mail notification") raise InternalServerError( "SMTP server needed to fulfill this request is not configured or unavailable." ) db.session.commit() logger.info('User accepted', extra={'user': user.login}) schema = UserSuccessResponseSchema() return schema.dump({"login": user.login})
def get(self, login): """ --- summary: Get profile information description: | Returns information about specific user security: - bearerAuth: [] tags: - user parameters: - in: path name: login schema: type: string description: User login responses: 200: description: Returns information about specific user content: application/json: schema: UserProfileResponseSchema 404: description: When user doesn't exist or is not a member of user's group 503: description: | Request canceled due to database statement timeout. """ user_login_obj = load_schema({"login": login}, UserLoginSchemaBase()) if g.auth_user.has_rights(Capabilities.manage_users): user = (db.session.query(User).filter( User.login == user_login_obj["login"]).first()) else: user = (db.session.query(User).join(User.memberships).join( Member.group).filter(Group.name != "public").filter( g.auth_user.is_member(Group.id)).filter( User.login == user_login_obj["login"])).first() if user is None: raise NotFound( "User doesn't exist or is not a member of your group") if g.auth_user.login == user_login_obj["login"]: schema = UserOwnProfileResponseSchema() else: schema = UserProfileResponseSchema() return schema.dump(user)
def post(self, provider_name): provider = (db.session.query(OpenIDProvider).filter( OpenIDProvider.name == provider_name).first()) if not provider: raise NotFound( f"Requested provider name '{provider_name}' not found") schema = OpenIDAuthorizeRequestSchema() obj = loads_schema(request.get_data(as_text=True), schema) redirect_uri = f"{app_config.mwdb.base_url}/oauth/callback" userinfo = provider.fetch_id_token(obj["code"], obj["state"], obj["nonce"], redirect_uri) # register user with information from provider if db.session.query(exists().where( and_( OpenIDUserIdentity.provider_id == provider.id, OpenIDUserIdentity.sub_id == userinfo["sub"], ))).scalar(): raise Conflict("User is already bound with selected provider.") login_claims = ["preferred_username", "nickname", "name"] for claim in login_claims: username = userinfo.get(claim) if not username: continue try: UserLoginSchemaBase().load({"login": username}) except ValidationError: continue already_exists = db.session.query( exists().where(Group.name == username)).scalar() if not already_exists: break # If no candidates in claims: try fallback login else: # If no candidates in claims: try fallback login sub_md5 = hashlib.md5( userinfo["sub"].encode("utf-8")).hexdigest()[:8] username = f"{provider_name}-{sub_md5}" if "email" in userinfo.keys(): user_email = userinfo["email"] else: user_email = f'{userinfo["sub"]}@mwdb.local' user = User.create( username, user_email, "Registered via OpenID Connect protocol", ) identity = OpenIDUserIdentity(sub_id=userinfo["sub"], provider_id=provider.id, user_id=user.id) db.session.add(identity) user.logged_on = datetime.datetime.now() db.session.commit() auth_token = user.generate_session_token() user_private_group = next( (g for g in user.groups if g.name == user.login), None) hooks.on_created_user(user) if user_private_group: hooks.on_created_group(user_private_group) logger.info( "User logged in via OpenID Provider", extra={ "login": user.login, "provider": provider_name }, ) schema = AuthSuccessResponseSchema() return schema.dump({ "login": user.login, "token": auth_token, "capabilities": user.capabilities, "groups": user.group_names, })
def delete(self, name, login): """ --- summary: Delete member from group description: | Removes member from existing group. Works only for user-defined groups (excluding private and 'public') Requires `manage_users` capability. security: - bearerAuth: [] tags: - group parameters: - in: path name: name schema: type: string description: Group name - in: path name: login schema: type: string description: Member login responses: 200: description: When member was removed successfully content: application/json: schema: GroupSuccessResponseSchema 400: description: When request body is invalid 403: description: When user doesn't have `manage_users` capability, group is immutable or user is pending 404: description: When user or group doesn't exist """ group_name_obj = load_schema({"name": name}, GroupNameSchemaBase()) user_login_obj = load_schema({"login": login}, UserLoginSchemaBase()) member = db.session.query(User).filter(User.login == login).first() if member is None: raise NotFound("No such user") if member.pending: raise Forbidden("User is pending and need to be accepted first") group = db.session.query(Group).options(joinedload( Group.users)).filter(Group.name == name).first() if group is None: raise NotFound("No such group") if group.immutable: raise Forbidden( "Removing members from private or public group is not allowed") group.users.remove(member) member.reset_sessions() db.session.commit() logger.info('Group member deleted', extra={ 'user': member.login, 'group': group.name }) schema = GroupSuccessResponseSchema() return schema.dump({"name": name})
def delete(self, name, login): """ --- summary: Delete member from group description: | Removes member from existing group. Works only for user-defined groups (excluding private and 'public') Requires `manage_users` capability or group_admin membership. security: - bearerAuth: [] tags: - group parameters: - in: path name: name schema: type: string description: Group name - in: path name: login schema: type: string description: Member login responses: 200: description: When member was removed successfully content: application/json: schema: GroupSuccessResponseSchema 400: description: When request body is invalid 403: description: | When user doesn't have `manage_users` capability, group is immutable or user is pending 404: description: When user or group doesn't exist 409: description: When member is already removed """ group_name_obj = load_schema({"name": name}, GroupNameSchemaBase()) user_login_obj = load_schema({"login": login}, UserLoginSchemaBase()) group = (db.session.query(Group).options( joinedload(Group.members), joinedload(Group.members, Member.user)).filter( Group.name == group_name_obj["name"])).first() if not (g.auth_user.has_rights(Capabilities.manage_users) or g.auth_user.is_group_admin(group.id)): raise Forbidden("You are not permitted to manage this group") if group is None: raise NotFound("No such group") if group.immutable: raise Forbidden( "Removing members from immutable group is not allowed") member = (db.session.query(User).filter( User.login == user_login_obj["login"]).first()) if g.auth_user.login == member.login: raise Forbidden("You can't remove yourself from the group, " "only system admin can remove group admins.") if member is None: raise NotFound("No such user") if member.pending: raise Forbidden("User is pending and need to be accepted first") if not group.remove_member(member): raise Conflict("Member is already removed") db.session.commit() logger.info("Group member deleted", extra={ "user": member.login, "group": group.name }) schema = GroupSuccessResponseSchema() return schema.dump({"name": name})
def put(self, name, login): """ --- summary: Update group membership description: | Updates group membership for specific member e.g. to set admin role. Works only for user-defined groups (excluding private and 'public') Requires `manage_users` capability security: - bearerAuth: [] tags: - group parameters: - in: path name: name schema: type: string description: Group name - in: path name: login schema: type: string description: Member login requestBody: description: Group membership information content: application/json: schema: GroupMemberUpdateRequestSchema responses: 200: description: When member was added successfully content: application/json: schema: GroupSuccessResponseSchema 400: description: When request body is invalid 403: description: | When user doesn't have `manage_users` capability, group is immutable or user is pending 404: description: When user or group doesn't exist 409: description: When member is already added """ group_name_obj = load_schema({"name": name}, GroupNameSchemaBase()) user_login_obj = load_schema({"login": login}, UserLoginSchemaBase()) membership = loads_schema(request.get_data(as_text=True), GroupMemberUpdateRequestSchema()) group = (db.session.query(Group).options( joinedload(Group.members), joinedload(Group.members, Member.user)).filter( Group.name == group_name_obj["name"])).first() if group is None: raise NotFound("No such group") if group.immutable: raise Forbidden( "Change membership in immutable group is not allowed") member = (db.session.query(User).filter( User.login == user_login_obj["login"]).first()) if member is None: raise NotFound("No such user") if member.pending: raise Forbidden("User is pending and need to be accepted first") member.set_group_admin(group.id, membership["group_admin"]) db.session.commit() logger.info("Group member updated", extra={ "user": member.login, "group": group.name }) schema = GroupSuccessResponseSchema() return schema.dump({"name": name})
def post(self, name, login): """ --- summary: Add a member to the specific group description: | Adds new member to existing group Works only for user-defined groups (excluding private and 'public') Requires `manage_users` capability security: - bearerAuth: [] tags: - group parameters: - in: path name: name schema: type: string description: Group name - in: path name: login schema: type: string description: Member login responses: 200: description: When member was added successfully content: application/json: schema: GroupSuccessResponseSchema 400: description: When request body is invalid 403: description: | When user doesn't have `manage_users` capability, group is immutable or user is pending 404: description: When user or group doesn't exist """ group_name_obj = load_schema({"name": name}, GroupNameSchemaBase()) user_login_obj = load_schema({"login": login}, UserLoginSchemaBase()) group = (db.session.query(Group).options( joinedload(Group.members), joinedload(Group.members, Member.user)).filter( Group.name == group_name_obj["name"])).first() if group is None: raise NotFound("No such group") if group.immutable: raise Forbidden("Adding members to immutable group is not allowed") member = (db.session.query(User).filter( User.login == user_login_obj["login"]).first()) if member is None: raise NotFound("No such user") if member.pending: raise Forbidden("User is pending and need to be accepted first") if not group.add_member(member): raise Conflict("Member is already added") db.session.commit() logger.info("Group member added", extra={ "user": member.login, "group": group.name }) schema = GroupSuccessResponseSchema() return schema.dump({"name": name})
def put(self, login): """ --- summary: Update existing user description: | Updates existing user account. Requires `manage_users` capability. security: - bearerAuth: [] tags: - user parameters: - in: path name: login schema: type: string description: User login requestBody: description: User information content: application/json: schema: UserUpdateRequestSchema responses: 200: description: When user was updated successfully content: application/json: schema: UserSuccessResponseSchema 400: description: When request body is invalid 403: description: | When user doesn't have `manage_users` capability or modified user is pending. 404: description: When user doesn't exist. """ schema = UserUpdateRequestSchema() obj = loads_schema(request.get_data(as_text=True), schema) user_login_obj = load_schema({"login": login}, UserLoginSchemaBase()) user = (db.session.query(User).filter( User.login == user_login_obj["login"]).first()) if user is None: raise NotFound("No such user") if user.pending: raise Forbidden("User is pending and need to be accepted first") email = obj["email"] if email is not None: user.email = email additional_info = obj["additional_info"] if additional_info is not None: user.additional_info = additional_info feed_quality = obj["feed_quality"] if feed_quality is not None: user.feed_quality = feed_quality disabled = obj["disabled"] if disabled is not None: user.disabled = disabled user.reset_sessions() db.session.commit() logger.info("User updated", extra=obj) schema = UserSuccessResponseSchema() return schema.dump({"login": user_login_obj["login"]})
def post(self, login): """ --- summary: Create a new user description: | Creates new user account Requires `manage_users` capability. security: - bearerAuth: [] tags: - user parameters: - in: path name: login schema: type: string description: New user login requestBody: description: User information content: application/json: schema: UserCreateRequestSchema responses: 200: description: When user was created successfully content: application/json: schema: UserSuccessResponseSchema 400: description: When request body is invalid 403: description: When user doesn't have `manage_users` capability. 409: description: When user or group with provided name already exists. 500: description: | When SMTP server is unavailable or not properly configured on the server. """ schema = UserCreateRequestSchema() obj = loads_schema(request.get_data(as_text=True), schema) user_login_obj = load_schema({"login": login}, UserLoginSchemaBase()) if db.session.query(exists().where( User.login == user_login_obj["login"])).scalar(): raise Conflict("User exists yet") if db.session.query(exists().where( Group.name == user_login_obj["login"])).scalar(): raise Conflict("Group exists yet") user = User.create( user_login_obj["login"], obj["email"], obj["additional_info"], pending=False, feed_quality=obj["feed_quality"], ) if obj["send_email"]: try: send_email_notification( "register", "New account registered in MWDB", user.email, base_url=app_config.mwdb.base_url, login=user.login, set_password_token=user.generate_set_password_token(). decode("utf-8"), ) except MailError: logger.exception("Can't send e-mail notification") raise InternalServerError( "SMTP server needed to fulfill this request is" " not configured or unavailable.") db.session.commit() logger.info("User created", extra={"user": user.login}) schema = UserSuccessResponseSchema() return schema.dump({"login": user.login})
def delete(self, login): """ --- summary: Reject a pending registration description: | Rejects pending user registration. Requires `manage_users` capability. security: - bearerAuth: [] tags: - user parameters: - in: path name: login schema: type: string description: Login of pending account responses: 200: description: When user is successfully rejected content: application/json: schema: UserSuccessResponseSchema 400: description: When invalid login is provided. 403: description: When user doesn't have `manage_users` capability. 404: description: When user doesn't exist or is already accepted/rejected. 500: description: | When SMTP server is unavailable or not properly configured on the server. """ user_login_obj = load_schema({"login": login}, UserLoginSchemaBase()) obj = load_schema(request.args, UserRejectRequestArgsSchema()) user = (db.session.query(User).filter( User.login == user_login_obj["login"], User.pending == true()).first()) if not user: raise NotFound("User doesn't exist or is already rejected") group = (db.session.query(Group).filter( Group.name == user_login_obj["login"]).first()) user.groups.remove(group) db.session.delete(group) db.session.delete(user) db.session.commit() if obj["send_email"]: try: send_email_notification( "rejection", "MWDB account request has been rejected", user.email, base_url=app_config.mwdb.base_url, login=user.login, set_password_token=user.generate_set_password_token(). decode("utf-8"), ) except MailError: logger.exception("Can't send e-mail notification") raise InternalServerError( "SMTP server needed to fulfill this request " "is not configured or unavailable.") logger.info( "User rejected with notification", extra={"user": user_login_obj["login"]}, ) else: logger.info( "User rejected without notification", extra={"user": user_login_obj["login"]}, ) schema = UserSuccessResponseSchema() return schema.dump({"login": user_login_obj["login"]})
def post(self, login): """ --- summary: Get password change link for specific user description: | Requests password change link for specific user. Link expires after setting a new password or after 14 days. Link is sent to the e-mail address set in user's profile. Requires `manage_users` capability. security: - bearerAuth: [] tags: - user parameters: - in: path name: login required: true schema: type: string description: Login of specific user responses: 200: description: | When password change link was successfully sent to the user's e-mail content: application/json: schema: UserSuccessResponseSchema 403: description: | When user doesn't have required capability 404: description: | When user doesn't exists. 500: description: | When SMTP server is unavailable or not properly configured on the server. 503: description: | Request canceled due to database statement timeout. """ user_login_obj = load_schema({"login": login}, UserLoginSchemaBase()) user = (db.session.query(User).filter( User.login == user_login_obj["login"]).first()) if not user: raise NotFound("User doesn't exist") try: send_email_notification( "recover", "Change password in MWDB", recipient_email=user.email, base_url=app_config.mwdb.base_url, login=user.login, set_password_token=user.generate_set_password_token(), ) except MailError: logger.exception("Can't send e-mail notification") raise InternalServerError( "SMTP server needed to fulfill this request is" " not configured or unavailable.") schema = UserSuccessResponseSchema() logger.info("Requested password change token", extra={"user": login}) return schema.dump({"login": login})