Esempio n. 1
0
    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})
Esempio n. 2
0
    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)
Esempio n. 3
0
    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,
        })
Esempio n. 4
0
    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})
Esempio n. 5
0
    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})
Esempio n. 6
0
    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})
Esempio n. 7
0
    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})
Esempio n. 8
0
    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"]})
Esempio n. 9
0
    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})
Esempio n. 10
0
    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"]})
Esempio n. 11
0
    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})