Example #1
0
class UploadPermissionController(Controller):
    """
    Endpoints for Upload Permission
    """
    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__WORKSPACE_UPLOAD_PERMISSION_ENDPOINTS])
    @hapic.handle_exception(WorkspacePublicUploadDisabledException,
                            HTTPStatus.BAD_REQUEST)
    @check_right(is_content_manager)
    @check_right(has_public_upload_enabled)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.input_body(UploadPermissionCreationBodySchema())
    @hapic.output_body(UploadPermissionSchema(many=True))
    def add_upload_permission(
            self,
            context,
            request: TracimRequest,
            hapic_data=None) -> typing.List[UploadPermissionInContext]:
        """
        Allow to add permission for upload to external person
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = UploadPermissionLib(current_user=request.current_user,
                                  session=request.dbsession,
                                  config=app_config)
        upload_permission = api.add_permission_to_workspace(
            request.current_workspace,
            hapic_data.body.emails,
            hapic_data.body.password,
            do_notify=app_config.EMAIL__NOTIFICATION__ACTIVATED,
        )
        return api.get_upload_permissions_in_context(upload_permission)

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__WORKSPACE_UPLOAD_PERMISSION_ENDPOINTS])
    @hapic.handle_exception(WorkspacePublicUploadDisabledException,
                            HTTPStatus.BAD_REQUEST)
    @check_right(is_content_manager)
    @check_right(has_public_upload_enabled)
    @hapic.input_query(UploadPermissionListQuerySchema())
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(UploadPermissionSchema(many=True))
    def get_upload_permission(
            self,
            context,
            request: TracimRequest,
            hapic_data=None) -> typing.List[UploadPermissionInContext]:
        """
        Get all upload permission of this workspace
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = UploadPermissionLib(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
            show_disabled=hapic_data.query.show_disabled,
        )
        upload_permissions = api.get_upload_permissions(
            request.current_workspace)
        return api.get_upload_permissions_in_context(upload_permissions)

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__WORKSPACE_UPLOAD_PERMISSION_ENDPOINTS])
    @hapic.handle_exception(WorkspacePublicUploadDisabledException,
                            HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UploadPermissionNotFound, HTTPStatus.BAD_REQUEST)
    @check_right(is_content_manager)
    @check_right(has_public_upload_enabled)
    @hapic.input_path(UploadPermissionIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def disable_upload_permission(self,
                                  context,
                                  request: TracimRequest,
                                  hapic_data=None) -> None:
        """
        remove an upload permission
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = UploadPermissionLib(current_user=request.current_user,
                                  session=request.dbsession,
                                  config=app_config)
        api.disable_upload_permission(request.current_workspace,
                                      hapic_data.path.upload_permission_id)

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__WORKSPACE_UPLOAD_PERMISSION_ENDPOINTS])
    @hapic.handle_exception(WorkspacePublicUploadDisabledException,
                            HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UploadPermissionNotFound, HTTPStatus.BAD_REQUEST)
    @hapic.input_path(UploadPermissionTokenPath())
    @hapic.output_body(UploadPermissionPublicInfoSchema())
    def guest_upload_info(self,
                          context,
                          request: TracimRequest,
                          hapic_data=None) -> UploadPermissionInContext:
        """
        get somes informations about upload permission
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = UploadPermissionLib(current_user=None,
                                  session=request.dbsession,
                                  config=app_config)
        upload_permission = api.get_upload_permission_by_token(
            upload_permission_token=hapic_data.path.upload_permission_token
        )  # type: UploadPermission
        workspace_api = WorkspaceApi(current_user=None,
                                     session=request.dbsession,
                                     config=app_config)
        workspace = workspace_api.get_one(upload_permission.workspace_id)
        workspace_api.check_public_upload_enabled(workspace)
        return api.get_upload_permission_in_context(upload_permission)

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__WORKSPACE_UPLOAD_PERMISSION_ENDPOINTS])
    @hapic.handle_exception(WrongSharePassword, HTTPStatus.FORBIDDEN)
    @hapic.handle_exception(UploadPermissionNotFound, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(WorkspacePublicUploadDisabledException,
                            HTTPStatus.BAD_REQUEST)
    @hapic.input_path(UploadPermissionTokenPath())
    @hapic.input_body(UploadPermissionPasswordBodySchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def guest_upload_check(self,
                           context,
                           request: TracimRequest,
                           hapic_data=None) -> None:
        """
        check upload password and token validity
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = UploadPermissionLib(current_user=None,
                                  session=request.dbsession,
                                  config=app_config)
        upload_permission = api.get_upload_permission_by_token(
            upload_permission_token=hapic_data.path.upload_permission_token
        )  # type: UploadPermission
        # TODO - G.M - 2019-08-01 - verify in access to upload_permission can be granted
        # we should considered do these check at decorator level
        workspace_api = WorkspaceApi(current_user=None,
                                     session=request.dbsession,
                                     config=app_config)
        workspace = workspace_api.get_one(upload_permission.workspace_id)
        workspace_api.check_public_upload_enabled(workspace)
        api.check_password(upload_permission,
                           password=hapic_data.body.password)
        return

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__WORKSPACE_UPLOAD_PERMISSION_ENDPOINTS])
    @hapic.handle_exception(WrongSharePassword, HTTPStatus.FORBIDDEN)
    @hapic.handle_exception(UploadPermissionNotFound, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(NoFileValidationError, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(WorkspacePublicUploadDisabledException,
                            HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(FileSizeOverWorkspaceEmptySpace,
                            HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(FileSizeOverOwnerEmptySpace,
                            HTTPStatus.BAD_REQUEST)
    @hapic.input_path(UploadPermissionTokenPath())
    @hapic.input_forms(UploadDataFormSchema())
    @hapic.input_files(UploadFileSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def guest_upload_file(self,
                          context,
                          request: TracimRequest,
                          hapic_data=None) -> None:
        """
        upload files as guest
        """
        # TODO - G.M - 2019-08-14 - replace UploadFiles Object hack to proper hapic support
        # see
        upload_files = UploadFiles(request, prefix_pattern="file_")
        # INFO - G.M - 2019-09-03 - check validation of file here, because hapic can't
        # handle them. verify if almost one file as been given.
        if len(upload_files.files) < 1:
            raise NoFileValidationError(
                "No files given at input, validation failed.")
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = UploadPermissionLib(current_user=None,
                                  session=request.dbsession,
                                  config=app_config)
        upload_permission = api.get_upload_permission_by_token(
            upload_permission_token=hapic_data.path.upload_permission_token
        )  # type: UploadPermission
        # TODO - G.M - 2019-08-01 - verify in access to upload_permission can be granted
        # we should considered do these check at decorator level
        workspace_api = WorkspaceApi(current_user=None,
                                     session=request.dbsession,
                                     config=app_config)
        workspace = workspace_api.get_one(upload_permission.workspace_id)
        workspace_api.check_public_upload_enabled(workspace)
        api.check_password(upload_permission,
                           password=hapic_data.forms.password)
        content_api = ContentApi(current_user=None,
                                 session=request.dbsession,
                                 config=app_config)
        content_api.check_workspace_size_limitation(
            content_length=request.content_length, workspace=workspace)
        content_api.check_owner_size_limitation(
            content_length=request.content_length, workspace=workspace)
        api.upload_files(
            upload_permission=upload_permission,
            uploader_username=hapic_data.forms.username,
            message=hapic_data.forms.message,
            files=upload_files.files,
            do_notify=app_config.EMAIL__NOTIFICATION__ACTIVATED,
        )
        return

    def bind(self, configurator: Configurator) -> None:
        # allow external person to upload file
        configurator.add_route(
            "add_upload_permission",
            "/workspaces/{workspace_id}/upload_permissions",
            request_method="POST",
        )
        configurator.add_view(self.add_upload_permission,
                              route_name="add_upload_permission")
        configurator.add_route(
            "get_upload_permissions",
            "/workspaces/{workspace_id}/upload_permissions",
            request_method="GET",
        )
        configurator.add_view(self.get_upload_permission,
                              route_name="get_upload_permissions")
        configurator.add_route(
            "delete_upload_permission",
            "/workspaces/{workspace_id}/upload_permissions/{upload_permission_id}",
            request_method="DELETE",
        )
        configurator.add_view(self.disable_upload_permission,
                              route_name="delete_upload_permission")

        # public upload api
        configurator.add_route(
            "guest_upload_check",
            "/public/guest-upload/{upload_permission_token}/check",
            request_method="POST",
        )
        configurator.add_view(self.guest_upload_check,
                              route_name="guest_upload_check")
        configurator.add_route(
            "guest_upload_file",
            "/public/guest-upload/{upload_permission_token}",
            request_method="POST",
        )
        configurator.add_view(self.guest_upload_file,
                              route_name="guest_upload_file")
        configurator.add_route(
            "guest_upload_info",
            "/public/guest-upload/{upload_permission_token}",
            request_method="GET",
        )
        configurator.add_view(self.guest_upload_info,
                              route_name="guest_upload_info")
Example #2
0
class WorkspaceController(Controller):

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS])
    @check_right(can_see_workspace_information)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(WorkspaceSchema())
    def workspace(self, context, request: TracimRequest, hapic_data=None):
        """
        Get workspace informations
        """
        app_config = request.registry.settings['CFG']
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        return wapi.get_workspace_with_context(request.current_workspace)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS])
    @check_right(is_administrator)
    @hapic.output_body(WorkspaceSchema(many=True), )
    def workspaces(self, context, request: TracimRequest, hapic_data=None):
        """
        Returns the list of all workspaces. This route is for admin only.
        Standard users must use their own route: /api/v2/users/me/workspaces
        """
        app_config = request.registry.settings['CFG']
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )

        workspaces = wapi.get_all()
        return [
            wapi.get_workspace_with_context(workspace)
            for workspace in workspaces
        ]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @check_right(can_modify_workspace)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.input_body(WorkspaceModifySchema())
    @hapic.output_body(WorkspaceSchema())
    def update_workspace(self, context, request: TracimRequest, hapic_data=None):  # nopep8
        """
        Update a workspace. This route is for trusted users and administrators.
        Note : a trusted user can only update spaces on which he/she is space manager
        """
        app_config = request.registry.settings['CFG']
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        wapi.update_workspace(
            request.current_workspace,
            label=hapic_data.body.label,
            description=hapic_data.body.description,
            save_now=True,
        )
        return wapi.get_workspace_with_context(request.current_workspace)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(WorkspaceLabelAlreadyUsed, HTTPStatus.BAD_REQUEST)
    @check_right(is_trusted_user)
    @hapic.input_body(WorkspaceCreationSchema())
    @hapic.output_body(WorkspaceSchema())
    def create_workspace(self, context, request: TracimRequest, hapic_data=None):  # nopep8
        """
        Create a workspace. This route is for trusted users and administrators.
        """
        app_config = request.registry.settings['CFG']
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = wapi.create_workspace(
            label=hapic_data.body.label,
            description=hapic_data.body.description,
            save_now=True,
        )
        return wapi.get_workspace_with_context(workspace)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_TRASH_AND_RESTORE_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @check_right(can_delete_workspace)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def delete_workspace(self, context, request: TracimRequest, hapic_data=None):  # nopep8
        """
        Delete a workspace. This route is for trusted users and administrators.
        Note : a trusted user can only delete spaces on which he/she is space manager
        """
        app_config = request.registry.settings['CFG']
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        wapi.delete(request.current_workspace, flush=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_TRASH_AND_RESTORE_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @check_right(can_delete_workspace)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def undelete_workspace(self, context, request: TracimRequest, hapic_data=None):  # nopep8
        """
        Restore a deleted space.
        Note : a trusted user can only restore spaces on which he/she is space manager
        """
        app_config = request.registry.settings['CFG']
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
            show_deleted=True,
        )
        wapi.undelete(request.current_workspace, flush=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS])
    @check_right(can_see_workspace_information)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(WorkspaceMemberSchema(many=True))
    def workspaces_members(
            self,
            context,
            request: TracimRequest,
            hapic_data=None
    ) -> typing.List[UserRoleWorkspaceInContext]:
        """
        Returns the list of space members with their role, avatar, etc.
        """
        app_config = request.registry.settings['CFG']
        rapi = RoleApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        
        roles = rapi.get_all_for_workspace(request.current_workspace)
        return [
            rapi.get_user_role_workspace_with_context(user_role)
            for user_role in roles
        ]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS])
    @check_right(can_see_workspace_information)
    @hapic.input_path(WorkspaceAndUserIdPathSchema())
    @hapic.output_body(WorkspaceMemberSchema())
    def workspaces_member_role(
            self,
            context,
            request: TracimRequest,
            hapic_data=None
    ) -> UserRoleWorkspaceInContext:
        """
        Returns given space member with its role, avatar, etc.
        """
        app_config = request.registry.settings['CFG']
        rapi = RoleApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )

        role = rapi.get_one(
            user_id=hapic_data.path.user_id,
            workspace_id=hapic_data.path.workspace_id,
        )
        return rapi.get_user_role_workspace_with_context(role)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS])
    @hapic.handle_exception(UserRoleNotFound, HTTPStatus.BAD_REQUEST)
    @check_right(can_modify_workspace)
    @hapic.input_path(WorkspaceAndUserIdPathSchema())
    @hapic.input_body(RoleUpdateSchema())
    @hapic.output_body(WorkspaceMemberSchema())
    def update_workspaces_members_role(
            self,
            context,
            request: TracimRequest,
            hapic_data=None
    ) -> UserRoleWorkspaceInContext:
        """
        Update role of the given space member.
        This feature is for workspace managers, trusted users and administrators.
        """
        app_config = request.registry.settings['CFG']
        rapi = RoleApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )

        role = rapi.get_one(
            user_id=hapic_data.path.user_id,
            workspace_id=hapic_data.path.workspace_id,
        )
        workspace_role = WorkspaceRoles.get_role_from_slug(hapic_data.body.role)
        role = rapi.update_role(
            role,
            role_level=workspace_role.level
        )
        return rapi.get_user_role_workspace_with_context(role)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS])
    @check_right(can_modify_workspace)
    @hapic.handle_exception(UserRoleNotFound, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UserCantRemoveHisOwnRoleInWorkspace, HTTPStatus.BAD_REQUEST)  # nopep8
    @hapic.input_path(WorkspaceAndUserIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def delete_workspaces_members_role(
            self,
            context,
            request: TracimRequest,
            hapic_data=None
    ) -> None:
        """
        Remove the user from the space.
        This feature is for workspace managers and administrators.
        """

        app_config = request.registry.settings['CFG']
        rapi = RoleApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        rapi.delete_one(
            user_id=hapic_data.path.user_id,
            workspace_id=hapic_data.path.workspace_id,
        )
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS])
    @hapic.handle_exception(EmailValidationFailed, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UserDoesNotExist, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UserIsNotActive, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UserIsDeleted, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(RoleAlreadyExistError, HTTPStatus.BAD_REQUEST)
    @check_right(can_modify_workspace)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.input_body(WorkspaceMemberInviteSchema())
    @hapic.output_body(WorkspaceMemberCreationSchema())
    def create_workspaces_members_role(
            self,
            context,
            request: TracimRequest,
            hapic_data=None
    ) -> UserRoleWorkspaceInContext:
        """
        Add a member to this workspace.
        This feature is for workspace managers and administrators.
        """
        newly_created = False
        email_sent = False
        app_config = request.registry.settings['CFG'] # type: CFG
        rapi = RoleApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        uapi = UserApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
            show_deactivated=True,
            show_deleted=True,
        )
        try:
            _, user = uapi.find(
                user_id=hapic_data.body.user_id,
                email=hapic_data.body.user_email,
                public_name=hapic_data.body.user_public_name
            )
            if user.is_deleted:
                raise UserIsDeleted('This user has been deleted. Unable to invite him.')  # nopep8
            if not user.is_active:
                raise UserIsNotActive('This user is not activated. Unable to invite him')  # nopep8
        except UserDoesNotExist as exc:
            if not uapi.allowed_to_invite_new_user(hapic_data.body.user_email):
                raise exc

            if app_config.NEW_USER_INVITATION_DO_NOTIFY:
                user = uapi.create_user(
                    auth_type=AuthType.UNKNOWN,
                    email=hapic_data.body.user_email,
                    password=password_generator(),
                    do_notify=True
                )
                if app_config.EMAIL_NOTIFICATION_ACTIVATED and \
                    app_config.NEW_USER_INVITATION_DO_NOTIFY and \
                    app_config.EMAIL_NOTIFICATION_PROCESSING_MODE.lower() == 'sync':
                    email_sent = True
            else:
                user = uapi.create_user(
                    auth_type=AuthType.UNKNOWN,
                    email=hapic_data.body.user_email,
                    password=None,
                    do_notify=False
                )

            newly_created = True

        role = rapi.create_one(
            user=user,
            workspace=request.current_workspace,
            role_level=WorkspaceRoles.get_role_from_slug(hapic_data.body.role).level,  # nopep8
            with_notif=app_config.EMAIl_NOTIFICATION_ENABLED_ON_INVITATION,
            flush=True,
        )
        return rapi.get_user_role_workspace_with_context(
            role,
            newly_created=newly_created,
            email_sent=email_sent,
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS])
    @check_right(is_reader)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.input_query(FilterContentQuerySchema())
    @hapic.output_body(ContentDigestSchema(many=True))
    def workspace_content(
            self,
            context,
            request: TracimRequest,
            hapic_data=None,
    ) -> typing.List[ContentInContext]:
        """
        return a list of contents of the space.
        This is NOT the full content list: by default, returned contents are the ones at root level.
        In order to get contents in a given folder, then use parent_id query filter.
        You can also show.hide archived/deleted contents.
        """
        app_config = request.registry.settings['CFG']
        content_filter = hapic_data.query
        api = ContentApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
            show_archived=content_filter.show_archived,
            show_deleted=content_filter.show_deleted,
            show_active=content_filter.show_active,
        )
        contents = api.get_all(
            parent_ids=content_filter.parent_ids,
            complete_path_to_id=content_filter.complete_path_to_id,
            workspace=request.current_workspace,
            content_type=content_filter.content_type or content_type_list.Any_SLUG,
            label=content_filter.label,
            order_by_properties=[Content.label]
        )
        contents = [
            api.get_content_in_context(content) for content in contents
        ]
        return contents

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UnallowedSubContent, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ParentNotFound, HTTPStatus.BAD_REQUEST)
    @check_right(can_create_content)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.input_body(ContentCreationSchema())
    @hapic.output_body(ContentDigestSchema())
    def create_generic_empty_content(
            self,
            context,
            request: TracimRequest,
            hapic_data=None,
    ) -> ContentInContext:
        """
        Creates a generic empty content. The minimum viable content has a label and a content type.
        Creating a content generally starts with a request to this endpoint.
        For specific contents like files, it is recommended to use the dedicated endpoint.
        This feature is accessible to contributors and higher role only.
        """
        app_config = request.registry.settings['CFG']
        creation_data = hapic_data.body
        api = ContentApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config
        )
        parent = None
        if creation_data.parent_id:
            try:
                parent = api.get_one(content_id=creation_data.parent_id, content_type=content_type_list.Any_SLUG)  # nopep8
            except ContentNotFound as exc:
                raise ParentNotFound(
                    'Parent with content_id {} not found'.format(creation_data.parent_id)
                ) from exc
        content = api.create(
            label=creation_data.label,
            content_type_slug=creation_data.content_type,
            workspace=request.current_workspace,
            parent=parent,
        )
        api.save(content, ActionDescription.CREATION)
        content = api.get_content_in_context(content)
        return content

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS])
    @check_right(is_reader)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.FOUND)  # nopep8
    def get_content_from_workspace(
            self,
            context,
            request: TracimRequest,
            hapic_data=None,
    ) -> None:
        """
        Convenient route allowing to get detail about a content without to known routes associated to its content type.
        This route generate a HTTP 302 with the right url
        """
        app_config = request.registry.settings['CFG']
        content = request.current_content
        content_type = content_type_list.get_one_by_slug(content.type).slug
        # TODO - G.M - 2018-08-03 - Jsonify redirect response ?
        raise HTTPFound(
            "{base_url}workspaces/{workspace_id}/{content_type}s/{content_id}".format(
                base_url=BASE_API_V2,
                workspace_id=content.workspace_id,
                content_type=content_type,
                content_id=content.content_id,
            )
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS])
    @hapic.input_path(ContentIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.FOUND)  # nopep8
    def get_content(
            self,
            context,
            request: TracimRequest,
            hapic_data=None,
    ) -> None:
        """
        Convenient route allowing to get detail about a content without to known routes associated to its content type.
        This route generate a HTTP 302 with the right url
        """
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(
            content_id=hapic_data.path['content_id'],
            content_type=content_type_list.Any_SLUG
        )
        content_type = content_type_list.get_one_by_slug(content.type).slug
        # TODO - G.M - 2018-08-03 - Jsonify redirect response ?
        raise HTTPFound(
            "{base_url}workspaces/{workspace_id}/{content_type}s/{content_id}".format(
                base_url=BASE_API_V2,
                workspace_id=content.workspace_id,
                content_type=content_type,
                content_id=content.content_id,
            )
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS])
    @hapic.handle_exception(WorkspacesDoNotMatch, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UnallowedSubContent, HTTPStatus.BAD_REQUEST)
    @check_right(can_move_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.input_body(ContentMoveSchema())
    @hapic.output_body(ContentDigestSchema())
    def move_content(
            self,
            context,
            request: TracimRequest,
            hapic_data=None,
    ) -> ContentInContext:
        """
        Move a content to specified new place.
        This requires to be content manager in both input and output spaces (which may be the same)
        """
        app_config = request.registry.settings['CFG']
        path_data = hapic_data.path
        move_data = hapic_data.body

        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(
            path_data.content_id,
            content_type=content_type_list.Any_SLUG
        )
        new_parent = api.get_one(
            move_data.new_parent_id, content_type=content_type_list.Any_SLUG
        )

        new_workspace = request.candidate_workspace

        with new_revision(
                session=request.dbsession,
                tm=transaction.manager,
                content=content
        ):
            api.move(
                content,
                new_parent=new_parent,
                new_workspace=new_workspace,
                must_stay_in_same_workspace=False,
            )
        updated_content = api.get_one(
            path_data.content_id,
            content_type=content_type_list.Any_SLUG
        )
        return api.get_content_in_context(updated_content)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ALL_TRASH_AND_RESTORE_ENDPOINTS])  # nopep8
    @check_right(is_content_manager)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def delete_content(
            self,
            context,
            request: TracimRequest,
            hapic_data=None,
    ) -> None:
        """
        Move a content to the trash. After that, the content will be invisible by default.
        This action requires the user to be a content manager.
        Note: the content is still accessible but becomes read-only.
        """
        app_config = request.registry.settings['CFG']
        path_data = hapic_data.path
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(
            path_data.content_id,
            content_type=content_type_list.Any_SLUG
        )
        with new_revision(
                session=request.dbsession,
                tm=transaction.manager,
                content=content
        ):
            api.delete(content)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ALL_TRASH_AND_RESTORE_ENDPOINTS])  # nopep8
    @check_right(is_content_manager)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def undelete_content(
            self,
            context,
            request: TracimRequest,
            hapic_data=None,
    ) -> None:
        """
        Restore a content from the trash. The content will be visible and editable again.
        """
        app_config = request.registry.settings['CFG']
        path_data = hapic_data.path
        api = ContentApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
            show_deleted=True,
            show_archived=True,
        )
        content = api.get_one(
            path_data.content_id,
            content_type=content_type_list.Any_SLUG
        )
        with new_revision(
                session=request.dbsession,
                tm=transaction.manager,
                content=content
        ):
            api.undelete(content)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ALL_ARCHIVE_AND_RESTORE_ENDPOINTS])  # nopep8
    @check_right(is_content_manager)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def archive_content(
            self,
            context,
            request: TracimRequest,
            hapic_data=None,
    ) -> None:
        """
        Archives a content. The content will be invisible but still available.
        Difference with delete is that optimizing workspace will not delete archived contents
        This action requires the user to be a content manager.
        Note: the content is still accessible but becomes read-only.
        the difference with delete is that optimizing workspace will not delete archived contents
        """
        app_config = request.registry.settings['CFG']
        path_data = hapic_data.path
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(path_data.content_id, content_type=content_type_list.Any_SLUG)  # nopep8
        with new_revision(
                session=request.dbsession,
                tm=transaction.manager,
                content=content
        ):
            api.archive(content)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ALL_ARCHIVE_AND_RESTORE_ENDPOINTS])  # nopep8
    @check_right(is_content_manager)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def unarchive_content(
            self,
            context,
            request: TracimRequest,
            hapic_data=None,
    ) -> None:
        """
        Restore a content from archive. The content will be visible and editable again.
        """
        app_config = request.registry.settings['CFG']
        path_data = hapic_data.path
        api = ContentApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
            show_archived=True,
            show_deleted=True,
        )
        content = api.get_one(
            path_data.content_id,
            content_type=content_type_list.Any_SLUG
        )
        with new_revision(
                session=request.dbsession,
                tm=transaction.manager,
                content=content
        ):
            api.unarchive(content)
        return

    def bind(self, configurator: Configurator) -> None:
        """
        Create all routes and views using
        pyramid configurator for this controller
        """

        # Workspaces
        configurator.add_route('workspaces', '/workspaces', request_method='GET')  # nopep8
        configurator.add_view(self.workspaces, route_name='workspaces')
        # Workspace
        configurator.add_route('workspace', '/workspaces/{workspace_id}', request_method='GET')  # nopep8
        configurator.add_view(self.workspace, route_name='workspace')
        # Create workspace
        configurator.add_route('create_workspace', '/workspaces', request_method='POST')  # nopep8
        configurator.add_view(self.create_workspace, route_name='create_workspace')  # nopep8
        # Delete/Undelete workpace
        configurator.add_route('delete_workspace', '/workspaces/{workspace_id}/trashed', request_method='PUT')  # nopep8
        configurator.add_view(self.delete_workspace, route_name='delete_workspace')  # nopep8
        configurator.add_route('undelete_workspace', '/workspaces/{workspace_id}/trashed/restore', request_method='PUT')  # nopep8
        configurator.add_view(self.undelete_workspace, route_name='undelete_workspace')  # nopep8
        # Update Workspace
        configurator.add_route('update_workspace', '/workspaces/{workspace_id}', request_method='PUT')  # nopep8
        configurator.add_view(self.update_workspace, route_name='update_workspace')  # nopep8
        # Workspace Members (Roles)
        configurator.add_route('workspace_members', '/workspaces/{workspace_id}/members', request_method='GET')  # nopep8
        configurator.add_view(self.workspaces_members, route_name='workspace_members')  # nopep8
        # Workspace Members (Role) Individual
        configurator.add_route('workspace_member_role', '/workspaces/{workspace_id}/members/{user_id}', request_method='GET')  # nopep8
        configurator.add_view(self.workspaces_member_role, route_name='workspace_member_role')  # nopep8
        # Update Workspace Members roles
        configurator.add_route('update_workspace_member', '/workspaces/{workspace_id}/members/{user_id}', request_method='PUT')  # nopep8
        configurator.add_view(self.update_workspaces_members_role, route_name='update_workspace_member')  # nopep8
        # Create Workspace Members roles
        configurator.add_route('create_workspace_member', '/workspaces/{workspace_id}/members', request_method='POST')  # nopep8
        configurator.add_view(self.create_workspaces_members_role, route_name='create_workspace_member')  # nopep8
        # Delete Workspace Members roles
        configurator.add_route('delete_workspace_member', '/workspaces/{workspace_id}/members/{user_id}', request_method='DELETE')  # nopep8
        configurator.add_view(self.delete_workspaces_members_role, route_name='delete_workspace_member')  # nopep8
        # Workspace Content
        configurator.add_route('workspace_content', '/workspaces/{workspace_id}/contents', request_method='GET')  # nopep8
        configurator.add_view(self.workspace_content, route_name='workspace_content')  # nopep8
        # Create Generic Content
        configurator.add_route('create_generic_content', '/workspaces/{workspace_id}/contents', request_method='POST')  # nopep8
        configurator.add_view(self.create_generic_empty_content, route_name='create_generic_content')  # nopep8
        # Get Content
        configurator.add_route('get_content', '/contents/{content_id}', request_method='GET')  # nopep8
        configurator.add_view(self.get_content, route_name='get_content')
        # Get Content From workspace
        configurator.add_route('get_content_from_workspace', '/workspaces/{workspace_id}/contents/{content_id}', request_method='GET')  # nopep8
        configurator.add_view(self.get_content_from_workspace, route_name='get_content_from_workspace')  # nopep8
        # Move Content
        configurator.add_route('move_content', '/workspaces/{workspace_id}/contents/{content_id}/move', request_method='PUT')  # nopep8
        configurator.add_view(self.move_content, route_name='move_content')  # nopep8
        # Delete/Undelete Content
        configurator.add_route('delete_content', '/workspaces/{workspace_id}/contents/{content_id}/trashed', request_method='PUT')  # nopep8
        configurator.add_view(self.delete_content, route_name='delete_content')  # nopep8
        configurator.add_route('undelete_content', '/workspaces/{workspace_id}/contents/{content_id}/trashed/restore', request_method='PUT')  # nopep8
        configurator.add_view(self.undelete_content, route_name='undelete_content')  # nopep8
        # # Archive/Unarchive Content
        configurator.add_route('archive_content', '/workspaces/{workspace_id}/contents/{content_id}/archived', request_method='PUT')  # nopep8
        configurator.add_view(self.archive_content, route_name='archive_content')  # nopep8
        configurator.add_route('unarchive_content', '/workspaces/{workspace_id}/contents/{content_id}/archived/restore', request_method='PUT')  # nopep8
        configurator.add_view(self.unarchive_content, route_name='unarchive_content')  # nopep8
Example #3
0
class FileController(Controller):
    """
    Endpoints for File Content
    """

    # File data
    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UnallowedSubContent, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ParentNotFound, HTTPStatus.BAD_REQUEST)
    @check_right(can_create_file)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(ContentDigestSchema())
    @hapic.input_forms(FileCreationFormSchema())
    @hapic.input_files(SimpleFileSchema())
    def create_file(self, context, request: TracimRequest, hapic_data=None):
        """
        Create a file .This will create 2 new revision.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        _file = hapic_data.files.files
        parent_id = hapic_data.forms.parent_id
        api = ContentApi(
            current_user=request.current_user, session=request.dbsession, config=app_config
        )

        parent = None  # type: typing.Optional['Content']
        if parent_id:
            try:
                parent = api.get_one(content_id=parent_id, content_type=content_type_list.Any_SLUG)
            except ContentNotFound as exc:
                raise ParentNotFound(
                    "Parent with content_id {} not found".format(parent_id)
                ) from exc
        content = api.create(
            filename=_file.filename,
            content_type_slug=FILE_TYPE,
            workspace=request.current_workspace,
            parent=parent,
        )
        api.save(content, ActionDescription.CREATION)
        with new_revision(session=request.dbsession, tm=transaction.manager, content=content):
            api.update_file_data(
                content,
                new_filename=_file.filename,
                new_mimetype=_file.type,
                new_content=_file.file,
            )

        return api.get_content_in_context(content)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_contributor)
    @check_right(is_file_content)
    @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST)
    @hapic.input_path(FilePathSchema())
    @hapic.input_files(SimpleFileSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def upload_file(self, context, request: TracimRequest, hapic_data=None):
        """
        Upload a new version of raw file of content. This will create a new
        revision.
        Good pratice for filename is filename is `{label}{file_extension}` or `{filename}`.
        Default filename value is 'raw' (without file extension) or nothing.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        _file = hapic_data.files.files
        with new_revision(session=request.dbsession, tm=transaction.manager, content=content):
            api.update_file_data(
                content,
                new_filename=_file.filename,
                new_mimetype=_file.type,
                new_content=_file.file,
            )
        api.save(content)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_file_content)
    @hapic.input_query(FileQuerySchema())
    @hapic.input_path(FilePathSchema())
    @hapic.output_file([])
    def download_file(self, context, request: TracimRequest, hapic_data=None):
        """
        Download raw file of last revision of content.
        Good pratice for filename is filename is `{label}{file_extension}` or `{filename}`.
        Default filename value is 'raw' (without file extension) or nothing.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        try:
            file = DepotManager.get().get(content.depot_file)
        except IOError as exc:
            raise TracimFileNotFound(
                "file related to revision {} of content {} not found in depot.".format(
                    content.revision_id, content.content_id
                )
            ) from exc
        filename = hapic_data.path.filename
        if not filename or filename == "raw":
            filename = content.file_name
        return HapicFile(
            file_object=file,
            mimetype=file.content_type,
            filename=filename,
            as_attachment=hapic_data.query.force_download,
            content_length=file.content_length,
            last_modified=content.updated,
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_file_content)
    @hapic.input_query(FileQuerySchema())
    @hapic.input_path(FileRevisionPathSchema())
    @hapic.output_file([])
    def download_revisions_file(self, context, request: TracimRequest, hapic_data=None):
        """
        Download raw file for specific revision of content.
        Good pratice for filename is filename is `{label}_r{revision_id}{file_extension}`.
        Default filename value is 'raw' (without file extension) or nothing.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        revision = api.get_one_revision(revision_id=hapic_data.path.revision_id, content=content)
        try:
            file = DepotManager.get().get(revision.depot_file)
        except IOError as exc:
            raise TracimFileNotFound(
                "file related to revision {} of content {} not found in depot.".format(
                    revision.revision_id, revision.content_id
                )
            ) from exc

        filename = hapic_data.path.filename
        if not filename or filename == "raw":
            filename = "{label}_r{revision_id}{file_extension}".format(
                label=revision.file_name,
                revision_id=revision.revision_id,
                file_extension=revision.file_extension,
            )
        return HapicFile(
            file_object=file,
            mimetype=file.content_type,
            filename=filename,
            as_attachment=hapic_data.query.force_download,
            content_length=file.content_length,
            last_modified=revision.updated,
        )

    # preview
    # pdf
    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_file_content)
    @hapic.handle_exception(TracimUnavailablePreviewType, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST)
    @hapic.input_query(PageQuerySchema())
    @hapic.input_path(FilePathSchema())
    @hapic.output_file([])
    def preview_pdf(self, context, request: TracimRequest, hapic_data=None):
        """
        Obtain a specific page pdf preview of last revision of content.
        Good pratice for filename is filename is `{label}_page_{page_number}.pdf`.
        Default filename value is 'raw' (without file extension) or nothing.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        pdf_preview_path = api.get_pdf_preview_path(
            content.content_id,
            content.revision_id,
            page_number=hapic_data.query.page,
            file_extension=content.file_extension,
        )
        filename = hapic_data.path.filename
        if not filename or filename == "raw":
            filename = "{label}_page_{page_number}.pdf".format(
                label=content.label, page_number=hapic_data.query.page
            )
        return HapicFile(
            file_path=pdf_preview_path,
            filename=filename,
            as_attachment=hapic_data.query.force_download,
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_file_content)
    @hapic.handle_exception(TracimUnavailablePreviewType, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST)
    @hapic.input_query(FileQuerySchema())
    @hapic.input_path(FilePathSchema())
    @hapic.output_file([])
    def preview_pdf_full(self, context, request: TracimRequest, hapic_data=None):
        """
        Obtain a full pdf preview (all page) of last revision of content.
        Good pratice for filename is filename is `{label}.pdf`.
        Default filename value is 'raw' (without file extension) or nothing.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        pdf_preview_path = api.get_full_pdf_preview_path(
            content.revision_id, file_extension=content.file_extension
        )
        filename = hapic_data.path.filename
        if not filename or filename == "raw":
            filename = "{label}.pdf".format(label=content.label)
        return HapicFile(
            file_path=pdf_preview_path,
            filename=filename,
            as_attachment=hapic_data.query.force_download,
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_file_content)
    @hapic.handle_exception(TracimUnavailablePreviewType, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST)
    @hapic.input_path(FileRevisionPathSchema())
    @hapic.input_query(FileQuerySchema())
    @hapic.output_file([])
    def preview_pdf_full_revision(self, context, request: TracimRequest, hapic_data=None):
        """
        Obtain full pdf preview of a specific revision of content.
        Good pratice for filename is filename is `{label}_r{revision_id}.pdf`.
        Default filename value is 'raw' (without file extension) or nothing.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        revision = api.get_one_revision(revision_id=hapic_data.path.revision_id, content=content)
        pdf_preview_path = api.get_full_pdf_preview_path(
            revision.revision_id, file_extension=revision.file_extension
        )
        filename = hapic_data.path.filename
        if not filename or filename == "raw":
            filename = "{label}_r{revision_id}.pdf".format(
                revision_id=revision.revision_id, label=revision.label
            )
        return HapicFile(
            file_path=pdf_preview_path,
            filename=filename,
            as_attachment=hapic_data.query.force_download,
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_file_content)
    @hapic.handle_exception(TracimUnavailablePreviewType, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST)
    @hapic.input_path(FileRevisionPathSchema())
    @hapic.input_query(PageQuerySchema())
    @hapic.output_file([])
    def preview_pdf_revision(self, context, request: TracimRequest, hapic_data=None):
        """
        Obtain a specific page pdf preview of a specific revision of content.
        Good pratice for filename is filename is `{label}_page_{page_number}.pdf`.
        Default filename value is 'raw' (without file extension) or nothing.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        revision = api.get_one_revision(revision_id=hapic_data.path.revision_id, content=content)
        pdf_preview_path = api.get_pdf_preview_path(
            revision.content_id,
            revision.revision_id,
            page_number=hapic_data.query.page,
            file_extension=revision.file_extension,
        )
        filename = hapic_data.path.filename
        if not filename or filename == "raw":
            filename = "{label}_page_{page_number}.pdf".format(
                label=content.label, page_number=hapic_data.query.page
            )
        return HapicFile(
            file_path=pdf_preview_path,
            filename=filename,
            as_attachment=hapic_data.query.force_download,
        )

    # jpg
    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_file_content)
    @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST)
    @hapic.input_path(FilePathSchema())
    @hapic.input_query(PageQuerySchema())
    @hapic.output_file([])
    def preview_jpg(self, context, request: TracimRequest, hapic_data=None):
        """
        Obtain normally sized jpg preview of last revision of content.
        Good pratice for filename is `filename is {label}_page_{page_number}.jpg`.
        Default filename value is 'raw' (without file extension) or nothing.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        allowed_dim = api.get_jpg_preview_allowed_dim()
        jpg_preview_path = api.get_jpg_preview_path(
            content_id=content.content_id,
            revision_id=content.revision_id,
            page_number=hapic_data.query.page,
            file_extension=content.file_extension,
            width=allowed_dim.dimensions[0].width,
            height=allowed_dim.dimensions[0].height,
        )
        filename = hapic_data.path.filename
        if not filename or filename == "raw":
            filename = "{label}_page_{page_number}.jpg".format(
                label=content.label, page_number=hapic_data.query.page
            )
        return HapicFile(
            file_path=jpg_preview_path,
            filename=filename,
            as_attachment=hapic_data.query.force_download,
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_file_content)
    @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(PreviewDimNotAllowed, HTTPStatus.BAD_REQUEST)
    @hapic.input_query(PageQuerySchema())
    @hapic.input_path(FilePreviewSizedPathSchema())
    @hapic.output_file([])
    def sized_preview_jpg(self, context, request: TracimRequest, hapic_data=None):
        """
        Obtain resized jpg preview of last revision of content.
        Good pratice for filename is filename is `{label}_page_{page_number}_{width}x{height}.jpg`.
        Default filename value is 'raw' (without file extension) or nothing.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        jpg_preview_path = api.get_jpg_preview_path(
            content_id=content.content_id,
            revision_id=content.revision_id,
            file_extension=content.file_extension,
            page_number=hapic_data.query.page,
            height=hapic_data.path.height,
            width=hapic_data.path.width,
        )
        filename = hapic_data.path.filename
        if not filename or filename == "raw":
            filename = "{label}_page_{page_number}_{width}x{height}.jpg".format(
                label=content.label,
                page_number=hapic_data.query.page,
                width=hapic_data.path.width,
                height=hapic_data.path.height,
            )
        return HapicFile(
            file_path=jpg_preview_path,
            filename=filename,
            as_attachment=hapic_data.query.force_download,
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_file_content)
    @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(PreviewDimNotAllowed, HTTPStatus.BAD_REQUEST)
    @hapic.input_path(FileRevisionPreviewSizedPathSchema())
    @hapic.input_query(PageQuerySchema())
    @hapic.output_file([])
    def sized_preview_jpg_revision(self, context, request: TracimRequest, hapic_data=None):
        """
        Obtain resized jpg preview of a specific revision of content.
        Good pratice for filename is filename is `{label}_r{revision_id}_page_{page_number}_{width}x{height}.jpg`.
        Default filename value is 'raw' (without file extension) or nothing.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        revision = api.get_one_revision(revision_id=hapic_data.path.revision_id, content=content)
        jpg_preview_path = api.get_jpg_preview_path(
            content_id=content.content_id,
            revision_id=revision.revision_id,
            page_number=hapic_data.query.page,
            height=hapic_data.path.height,
            width=hapic_data.path.width,
            file_extension=revision.file_extension,
        )
        filename = hapic_data.path.filename
        if not filename or filename == "raw":
            filename = "{label}_r{revision_id}_page_{page_number}_{width}x{height}.jpg".format(
                revision_id=revision.revision_id,
                label=revision.label,
                page_number=hapic_data.query.page,
                width=hapic_data.path.width,
                height=hapic_data.path.height,
            )
        return HapicFile(
            file_path=jpg_preview_path,
            filename=filename,
            as_attachment=hapic_data.query.force_download,
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_file_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(AllowedJpgPreviewDimSchema())
    def allowed_dim_preview_jpg(self, context, request: TracimRequest, hapic_data=None):
        """
        Get allowed dimensions of jpg preview. If restricted is true,
        only those dimensions are strictly accepted.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        return api.get_jpg_preview_allowed_dim()

    # File infos
    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_file_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(FileContentSchema())
    def get_file_infos(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:
        """
        Get thread content
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        return api.get_content_in_context(content)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST)
    @check_right(is_contributor)
    @check_right(is_file_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.input_body(FileContentModifySchema())
    @hapic.output_body(FileContentSchema())
    def update_file_info(
        self, context, request: TracimRequest, hapic_data=None
    ) -> ContentInContext:
        """
        update thread
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        with new_revision(session=request.dbsession, tm=transaction.manager, content=content):
            api.update_content(
                item=content,
                new_label=hapic_data.body.label,
                new_content=hapic_data.body.raw_content,
            )
            api.save(content)
        return api.get_content_in_context(content)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_file_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(FileRevisionSchema(many=True))
    def get_file_revisions(
        self, context, request: TracimRequest, hapic_data=None
    ) -> typing.List[RevisionInContext]:
        """
        get file revisions
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        revisions = content.revisions
        return [api.get_revision_in_context(revision) for revision in revisions]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @check_right(is_contributor)
    @check_right(is_file_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.input_body(SetContentStatusSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    @hapic.handle_exception(ContentStatusException, HTTPStatus.BAD_REQUEST)
    def set_file_status(self, context, request: TracimRequest, hapic_data=None) -> None:
        """
        set file status
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        if content.status == request.json_body.get("status"):
            raise ContentStatusException(
                "Content id {} already have status {}".format(content.content_id, content.status)
            )
        with new_revision(session=request.dbsession, tm=transaction.manager, content=content):
            api.set_status(content, hapic_data.body.status)
            api.save(content)
        return

    def bind(self, configurator: Configurator) -> None:
        """
        Add route to configurator.
        """

        # file info #
        # Get file info
        configurator.add_route(
            "file_info", "/workspaces/{workspace_id}/files/{content_id}", request_method="GET"
        )
        configurator.add_view(self.get_file_infos, route_name="file_info")
        # update file
        configurator.add_route(
            "update_file_info",
            "/workspaces/{workspace_id}/files/{content_id}",
            request_method="PUT",
        )
        configurator.add_view(self.update_file_info, route_name="update_file_info")

        # raw file #
        # create file
        configurator.add_route(
            "create_file", "/workspaces/{workspace_id}/files", request_method="POST"
        )
        configurator.add_view(self.create_file, route_name="create_file")
        # upload raw file
        configurator.add_route(
            "upload_file",
            "/workspaces/{workspace_id}/files/{content_id}/raw/{filename:[^/]*}",
            request_method="PUT",
        )
        configurator.add_view(self.upload_file, route_name="upload_file")
        # download raw file
        configurator.add_route(
            "download_file",
            "/workspaces/{workspace_id}/files/{content_id}/raw/{filename:[^/]*}",
            request_method="GET",
        )
        configurator.add_view(self.download_file, route_name="download_file")
        # download raw file of revision
        configurator.add_route(
            "download_revision",
            "/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/raw/{filename:[^/]*}",
            request_method="GET",
        )
        configurator.add_view(self.download_revisions_file, route_name="download_revision")

        # previews #
        # get preview pdf full
        configurator.add_route(
            "preview_pdf_full",
            "/workspaces/{workspace_id}/files/{content_id}/preview/pdf/full/{filename:[^/]*}",
            request_method="GET",
        )
        configurator.add_view(self.preview_pdf_full, route_name="preview_pdf_full")
        # get preview pdf
        configurator.add_route(
            "preview_pdf",
            "/workspaces/{workspace_id}/files/{content_id}/preview/pdf/{filename:[^/]*}",
            request_method="GET",
        )
        configurator.add_view(self.preview_pdf, route_name="preview_pdf")
        # get preview jpg allowed dims
        configurator.add_route(
            "allowed_dim_preview_jpg",
            "/workspaces/{workspace_id}/files/{content_id}/preview/jpg/allowed_dims",
            request_method="GET",
        )
        configurator.add_view(self.allowed_dim_preview_jpg, route_name="allowed_dim_preview_jpg")
        # get preview jpg
        configurator.add_route(
            "preview_jpg",
            "/workspaces/{workspace_id}/files/{content_id}/preview/jpg/{filename:[^/]*}",
            request_method="GET",
        )
        configurator.add_view(self.preview_jpg, route_name="preview_jpg")
        # get preview jpg with size
        configurator.add_route(
            "sized_preview_jpg",
            "/workspaces/{workspace_id}/files/{content_id}/preview/jpg/{width}x{height}/{filename:[^/]*}",
            request_method="GET",
        )
        configurator.add_view(self.sized_preview_jpg, route_name="sized_preview_jpg")
        # get jpg preview for revision
        configurator.add_route(
            "sized_preview_jpg_revision",
            "/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/preview/jpg/{width}x{height}/{filename:[^/]*}",
            request_method="GET",
        )
        configurator.add_view(
            self.sized_preview_jpg_revision, route_name="sized_preview_jpg_revision"
        )
        # get full pdf preview for revision
        configurator.add_route(
            "preview_pdf_full_revision",
            "/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/preview/pdf/full/{filename:[^/]*}",
            request_method="GET",
        )
        configurator.add_view(
            self.preview_pdf_full_revision, route_name="preview_pdf_full_revision"
        )
        # get pdf preview for revision
        configurator.add_route(
            "preview_pdf_revision",
            "/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/preview/pdf/{filename:[^/]*}",
            request_method="GET",
        )
        configurator.add_view(self.preview_pdf_revision, route_name="preview_pdf_revision")
        # others #
        # get file revisions
        configurator.add_route(
            "file_revisions",
            "/workspaces/{workspace_id}/files/{content_id}/revisions",
            request_method="GET",
        )
        configurator.add_view(self.get_file_revisions, route_name="file_revisions")

        # get file status
        configurator.add_route(
            "set_file_status",
            "/workspaces/{workspace_id}/files/{content_id}/status",
            request_method="PUT",
        )
        configurator.add_view(self.set_file_status, route_name="set_file_status")
Example #4
0
class UserController(Controller):
    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(WorkspaceDigestSchema(many=True))
    def user_workspace(self, context, request: TracimRequest, hapic_data=None):
        """
        Get list of user workspaces
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )

        workspaces = wapi.get_all_for_user(request.candidate_user)
        return [
            wapi.get_workspace_with_context(workspace)
            for workspace in workspaces
        ]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(UserSchema())
    def user(self, context, request: TracimRequest, hapic_data=None):
        """
        Get user infos.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        return uapi.get_user_with_context(request.candidate_user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @check_right(is_administrator)
    @hapic.output_body(UserDigestSchema(many=True))
    def users(self, context, request: TracimRequest, hapic_data=None):
        """
        Get all users
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        users = uapi.get_all()
        context_users = [uapi.get_user_with_context(user) for user in users]
        return context_users

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.input_query(KnownMemberQuerySchema())
    @hapic.output_body(UserDigestSchema(many=True))
    def known_members(self, context, request: TracimRequest, hapic_data=None):
        """
        Get known users list
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
            show_deactivated=False,
        )
        users = uapi.get_known_user(
            acp=hapic_data.query.acp,
            exclude_user_ids=hapic_data.query.exclude_user_ids,
            exclude_workspace_ids=hapic_data.query.exclude_workspace_ids,
        )
        context_users = [uapi.get_user_with_context(user) for user in users]
        return context_users

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
    @hapic.handle_exception(EmailAlreadyExistInDb, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ExternalAuthUserEmailModificationDisallowed,
                            HTTPStatus.BAD_REQUEST)
    @check_right(has_personal_access)
    @hapic.input_body(SetEmailSchema())
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(UserSchema())
    def set_user_email(self, context, request: TracimRequest, hapic_data=None):
        """
        Set user Email
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        user = uapi.set_email(
            request.candidate_user,
            hapic_data.body.loggedin_user_password,
            hapic_data.body.email,
            do_save=True,
        )
        return uapi.get_user_with_context(user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
    @hapic.handle_exception(PasswordDoNotMatch, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ExternalAuthUserPasswordModificationDisallowed,
                            HTTPStatus.BAD_REQUEST)
    @check_right(has_personal_access)
    @hapic.input_body(SetPasswordSchema())
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def set_user_password(self,
                          context,
                          request: TracimRequest,
                          hapic_data=None):
        """
        Set user password
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        uapi.set_password(
            request.candidate_user,
            hapic_data.body.loggedin_user_password,
            hapic_data.body.new_password,
            hapic_data.body.new_password2,
            do_save=True,
        )
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_body(SetUserInfoSchema())
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(UserSchema())
    def set_user_infos(self, context, request: TracimRequest, hapic_data=None):
        """
        Set user info data
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        user = uapi.update(
            request.candidate_user,
            auth_type=request.candidate_user.auth_type,
            name=hapic_data.body.public_name,
            timezone=hapic_data.body.timezone,
            lang=hapic_data.body.lang,
            do_save=True,
        )
        uapi.execute_updated_user_actions(user)
        return uapi.get_user_with_context(user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @hapic.handle_exception(EmailAlreadyExistInDb, HTTPStatus.BAD_REQUEST)
    @check_right(is_administrator)
    @hapic.input_body(UserCreationSchema())
    @hapic.output_body(UserSchema())
    def create_user(self, context, request: TracimRequest, hapic_data=None):
        """
        Create new user
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        gapi = GroupApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        groups = [gapi.get_one_with_name(hapic_data.body.profile)]
        password = hapic_data.body.password
        if not password and hapic_data.body.email_notification:
            password = password_generator()

        user = uapi.create_user(
            auth_type=AuthType.UNKNOWN,
            email=hapic_data.body.email,
            password=password,
            timezone=hapic_data.body.timezone,
            lang=hapic_data.body.lang,
            name=hapic_data.body.public_name,
            do_notify=hapic_data.body.email_notification,
            groups=groups,
            do_save=True,
        )
        uapi.execute_created_user_actions(user)
        return uapi.get_user_with_context(user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENABLE_AND_DISABLE_ENDPOINTS])
    @check_right(is_administrator)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def enable_user(self, context, request: TracimRequest, hapic_data=None):
        """
        enable user
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        uapi.enable(user=request.candidate_user, do_save=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_TRASH_AND_RESTORE_ENDPOINTS])
    @hapic.handle_exception(UserCantDeleteHimself, HTTPStatus.BAD_REQUEST)
    @check_right(is_administrator)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def delete_user(self, context, request: TracimRequest, hapic_data=None):
        """
        delete user
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        uapi.delete(user=request.candidate_user, do_save=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_TRASH_AND_RESTORE_ENDPOINTS])
    @check_right(is_administrator)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def undelete_user(self, context, request: TracimRequest, hapic_data=None):
        """
        undelete user
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
            show_deleted=True,
        )
        uapi.undelete(user=request.candidate_user, do_save=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENABLE_AND_DISABLE_ENDPOINTS])
    @hapic.handle_exception(UserCantDisableHimself, HTTPStatus.BAD_REQUEST)
    @check_right(is_administrator)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def disable_user(self, context, request: TracimRequest, hapic_data=None):
        """
        disable user
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        uapi.disable(user=request.candidate_user, do_save=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @hapic.handle_exception(UserCantChangeIsOwnProfile, HTTPStatus.BAD_REQUEST)
    @check_right(is_administrator)
    @hapic.input_path(UserIdPathSchema())
    @hapic.input_body(SetUserProfileSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def set_profile(self, context, request: TracimRequest, hapic_data=None):
        """
        set user profile
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        gapi = GroupApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        groups = [gapi.get_one_with_name(hapic_data.body.profile)]
        uapi.update(
            user=request.candidate_user,
            auth_type=request.candidate_user.auth_type,
            groups=groups,
            do_save=True,
        )
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceIdPathSchema())
    @hapic.input_query(ActiveContentFilterQuerySchema())
    @hapic.output_body(ContentDigestSchema(many=True))
    def last_active_content(self,
                            context,
                            request: TracimRequest,
                            hapic_data=None):
        """
        Get last_active_content for user
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        content_filter = hapic_data.query
        api = ContentApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = None
        if hapic_data.path.workspace_id:
            workspace = wapi.get_one(hapic_data.path.workspace_id)
        before_content = None
        if content_filter.before_content_id:
            before_content = api.get_one(
                content_id=content_filter.before_content_id,
                workspace=workspace,
                content_type=content_type_list.Any_SLUG,
            )
        last_actives = api.get_last_active(workspace=workspace,
                                           limit=content_filter.limit or None,
                                           before_content=before_content)
        return [
            api.get_content_in_context(content) for content in last_actives
        ]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceIdPathSchema())
    @hapic.input_query(ContentIdsQuerySchema())
    @hapic.output_body(ReadStatusSchema(many=True))
    def contents_read_status(self,
                             context,
                             request: TracimRequest,
                             hapic_data=None):
        """
        get user_read status of contents
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = None
        if hapic_data.path.workspace_id:
            workspace = wapi.get_one(hapic_data.path.workspace_id)
        last_actives = api.get_last_active(
            workspace=workspace,
            limit=None,
            before_content=None,
            content_ids=hapic_data.query.content_ids or None,
        )
        return [
            api.get_content_in_context(content) for content in last_actives
        ]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def set_content_as_read(self,
                            context,
                            request: TracimRequest,
                            hapic_data=None):
        """
        set user_read status of content to read
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.candidate_user,
            session=request.dbsession,
            config=app_config,
        )
        api.mark_read(request.current_content, do_flush=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def set_content_as_unread(self,
                              context,
                              request: TracimRequest,
                              hapic_data=None):
        """
        set user_read status of content to unread
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.candidate_user,
            session=request.dbsession,
            config=app_config,
        )
        api.mark_unread(request.current_content, do_flush=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def set_workspace_as_read(self,
                              context,
                              request: TracimRequest,
                              hapic_data=None):
        """
        set user_read status of all content of workspace to read
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.candidate_user,
            session=request.dbsession,
            config=app_config,
        )
        api.mark_read__workspace(request.current_workspace)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_NOTIFICATION_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def enable_workspace_notification(self,
                                      context,
                                      request: TracimRequest,
                                      hapic_data=None):
        """
        enable workspace notification
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = wapi.get_one(hapic_data.path.workspace_id)
        wapi.enable_notifications(request.candidate_user, workspace)
        wapi.save(workspace)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_NOTIFICATION_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def disable_workspace_notification(self,
                                       context,
                                       request: TracimRequest,
                                       hapic_data=None):
        """
        disable workspace notification
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = wapi.get_one(hapic_data.path.workspace_id)
        wapi.disable_notifications(request.candidate_user, workspace)
        wapi.save(workspace)
        return

    def bind(self, configurator: Configurator) -> None:
        """
        Create all routes and views using pyramid configurator
        for this controller
        """

        # user workspace
        configurator.add_route(
            "user_workspace",
            "/users/{user_id:\d+}/workspaces",
            request_method="GET"  # noqa: W605
        )
        configurator.add_view(self.user_workspace, route_name="user_workspace")

        # user info
        configurator.add_route("user",
                               "/users/{user_id:\d+}",
                               request_method="GET")  # noqa: W605
        configurator.add_view(self.user, route_name="user")

        # users lists
        configurator.add_route("users", "/users", request_method="GET")
        configurator.add_view(self.users, route_name="users")

        # known members lists
        configurator.add_route(
            "known_members",
            "/users/{user_id:\d+}/known_members",
            request_method="GET",  # noqa: W605
        )
        configurator.add_view(self.known_members, route_name="known_members")

        # set user email
        configurator.add_route("set_user_email",
                               "/users/{user_id:\d+}/email",
                               request_method="PUT")  # noqa: W605
        configurator.add_view(self.set_user_email, route_name="set_user_email")

        # set user password
        configurator.add_route(
            "set_user_password",
            "/users/{user_id:\d+}/password",
            request_method="PUT"  # noqa: W605
        )
        configurator.add_view(self.set_user_password,
                              route_name="set_user_password")

        # set user_info
        configurator.add_route("set_user_info",
                               "/users/{user_id:\d+}",
                               request_method="PUT")  # noqa: W605
        configurator.add_view(self.set_user_infos, route_name="set_user_info")

        # create user
        configurator.add_route("create_user", "/users", request_method="POST")
        configurator.add_view(self.create_user, route_name="create_user")

        # enable user
        configurator.add_route("enable_user",
                               "/users/{user_id:\d+}/enabled",
                               request_method="PUT")  # noqa: W605
        configurator.add_view(self.enable_user, route_name="enable_user")

        # disable user
        configurator.add_route(
            "disable_user",
            "/users/{user_id:\d+}/disabled",
            request_method="PUT"  # noqa: W605
        )
        configurator.add_view(self.disable_user, route_name="disable_user")

        # delete user
        configurator.add_route("delete_user",
                               "/users/{user_id:\d+}/trashed",
                               request_method="PUT")  # noqa: W605
        configurator.add_view(self.delete_user, route_name="delete_user")

        # undelete user
        configurator.add_route(
            "undelete_user",
            "/users/{user_id:\d+}/trashed/restore",
            request_method="PUT",  # noqa: W605
        )
        configurator.add_view(self.undelete_user, route_name="undelete_user")

        # set user profile
        configurator.add_route(
            "set_user_profile",
            "/users/{user_id:\d+}/profile",
            request_method="PUT"  # noqa: W605
        )
        configurator.add_view(self.set_profile, route_name="set_user_profile")

        # user content
        configurator.add_route(
            "contents_read_status",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/read_status",  # noqa: W605
            request_method="GET",
        )
        configurator.add_view(self.contents_read_status,
                              route_name="contents_read_status")
        # last active content for user
        configurator.add_route(
            "last_active_content",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/recently_active",  # noqa: W605
            request_method="GET",
        )
        configurator.add_view(self.last_active_content,
                              route_name="last_active_content")

        # set content as read/unread
        configurator.add_route(
            "read_content",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/{content_id}/read",  # noqa: W605
            request_method="PUT",
        )
        configurator.add_view(self.set_content_as_read,
                              route_name="read_content")
        configurator.add_route(
            "unread_content",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/{content_id}/unread",  # noqa: W605
            request_method="PUT",
        )
        configurator.add_view(self.set_content_as_unread,
                              route_name="unread_content")

        # set workspace as read
        configurator.add_route(
            "read_workspace",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/read",  # noqa: W605
            request_method="PUT",
        )
        configurator.add_view(self.set_workspace_as_read,
                              route_name="read_workspace")

        # enable workspace notification
        configurator.add_route(
            "enable_workspace_notification",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/notifications/activate",  # noqa: W605
            request_method="PUT",
        )
        configurator.add_view(self.enable_workspace_notification,
                              route_name="enable_workspace_notification")

        # enable workspace notification
        configurator.add_route(
            "disable_workspace_notification",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/notifications/deactivate",  # noqa: W605
            request_method="PUT",
        )
        configurator.add_view(self.disable_workspace_notification,
                              route_name="disable_workspace_notification")
Example #5
0
class CommentController(Controller):
    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_COMMENT_ENDPOINTS])
    @check_right(is_reader)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(CommentSchema(many=True))
    def content_comments(self,
                         context,
                         request: TracimRequest,
                         hapic_data=None):
        """
        Get all comments related to a content in asc order (first is the oldest)
        """

        # login = hapic_data.body
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id,
                              content_type=content_type_list.Any_SLUG)
        comments = content.get_comments()
        comments.sort(key=lambda comment: comment.created)
        return [api.get_content_in_context(comment) for comment in comments]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_COMMENT_ENDPOINTS])
    @hapic.handle_exception(EmptyCommentContentNotAllowed,
                            HTTPStatus.BAD_REQUEST)  # nopep8
    @check_right(is_contributor)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.input_body(SetCommentSchema())
    @hapic.output_body(CommentSchema())
    def add_comment(self, context, request: TracimRequest, hapic_data=None):
        """
        Add new comment
        """
        # login = hapic_data.body
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id,
                              content_type=content_type_list.Any_SLUG)
        comment = api.create_comment(
            content.workspace,
            content,
            hapic_data.body.raw_content,
            do_save=True,
        )
        return api.get_content_in_context(comment)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_COMMENT_ENDPOINTS])
    @check_right(can_delete_comment)
    @hapic.input_path(CommentsPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def delete_comment(self, context, request: TracimRequest, hapic_data=None):
        """
        Delete comment
        """
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        wapi = WorkspaceApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        workspace = wapi.get_one(hapic_data.path.workspace_id)
        parent = api.get_one(hapic_data.path.content_id,
                             content_type=content_type_list.Any_SLUG,
                             workspace=workspace)
        comment = api.get_one(
            hapic_data.path.comment_id,
            content_type=content_type_list.Comment.slug,
            workspace=workspace,
            parent=parent,
        )
        with new_revision(session=request.dbsession,
                          tm=transaction.manager,
                          content=comment):
            api.delete(comment)
        return

    def bind(self, configurator: Configurator):
        # Get comments
        configurator.add_route(
            'content_comments',
            '/workspaces/{workspace_id}/contents/{content_id}/comments',
            request_method='GET')
        configurator.add_view(self.content_comments,
                              route_name='content_comments')

        # Add comments
        configurator.add_route(
            'add_comment',
            '/workspaces/{workspace_id}/contents/{content_id}/comments',
            request_method='POST')  # nopep8
        configurator.add_view(self.add_comment, route_name='add_comment')

        # delete comments
        configurator.add_route(
            'delete_comment',
            '/workspaces/{workspace_id}/contents/{content_id}/comments/{comment_id}',  # nopep8
            request_method='DELETE')
        configurator.add_view(self.delete_comment, route_name='delete_comment')
Example #6
0
class FolderController(Controller):

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FOLDER_ENDPOINTS])
    @require_workspace_role(UserRoleInWorkspace.READER)
    @require_content_types([FOLDER_TYPE])
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(TextBasedContentSchema())
    def get_folder(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
        """
        Get folder info
        """
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(
            hapic_data.path.content_id,
            content_type=content_type_list.Any_SLUG
        )
        return api.get_content_in_context(content)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FOLDER_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST)
    @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
    @require_content_types([FOLDER_TYPE])
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.input_body(FolderContentModifySchema())
    @hapic.output_body(TextBasedContentSchema())
    def update_folder(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
        """
        update folder
        """
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(
            hapic_data.path.content_id,
            content_type=content_type_list.Any_SLUG
        )
        with new_revision(
                session=request.dbsession,
                tm=transaction.manager,
                content=content
        ):
            api.update_content(
                item=content,
                new_label=hapic_data.body.label,
                new_content=hapic_data.body.raw_content,

            )
            api.set_allowed_content(
                content=content,
                allowed_content_type_slug_list=hapic_data.body.sub_content_types  # nopep8
            )
            api.save(content)
        return api.get_content_in_context(content)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FOLDER_ENDPOINTS])
    @require_workspace_role(UserRoleInWorkspace.READER)
    @require_content_types([FOLDER_TYPE])
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(TextBasedRevisionSchema(many=True))
    def get_folder_revisions(
            self,
            context,
            request: TracimRequest,
            hapic_data=None
    ) -> typing.List[RevisionInContext]:
        """
        get folder revisions
        """
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(
            hapic_data.path.content_id,
            content_type=content_type_list.Any_SLUG
        )
        revisions = content.revisions
        return [
            api.get_revision_in_context(revision)
            for revision in revisions
        ]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FOLDER_ENDPOINTS])
    @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
    @require_content_types([FOLDER_TYPE])
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.input_body(SetContentStatusSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def set_folder_status(self, context, request: TracimRequest, hapic_data=None) -> None:  # nopep8
        """
        set folder status
        """
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(
            hapic_data.path.content_id,
            content_type=content_type_list.Any_SLUG
        )
        with new_revision(
                session=request.dbsession,
                tm=transaction.manager,
                content=content
        ):
            api.set_status(
                content,
                hapic_data.body.status,
            )
            api.save(content)
        return

    def bind(self, configurator: Configurator) -> None:
        # Get folder
        configurator.add_route(
            'folder',
            '/workspaces/{workspace_id}/folders/{content_id}',
            request_method='GET'
        )
        configurator.add_view(self.get_folder, route_name='folder')  # nopep8

        # update folder
        configurator.add_route(
            'update_folder',
            '/workspaces/{workspace_id}/folders/{content_id}',
            request_method='PUT'
        )  # nopep8
        configurator.add_view(self.update_folder, route_name='update_folder')  # nopep8

        # get folder revisions
        configurator.add_route(
            'folder_revisions',
            '/workspaces/{workspace_id}/folders/{content_id}/revisions',  # nopep8
            request_method='GET'
        )
        configurator.add_view(self.get_folder_revisions, route_name='folder_revisions')  # nopep8

        # get folder revisions
        configurator.add_route(
            'set_folder_status',
            '/workspaces/{workspace_id}/folders/{content_id}/status',  # nopep8
            request_method='PUT'
        )
        configurator.add_view(self.set_folder_status, route_name='set_folder_status')  # nopep8
Example #7
0
class ThreadController(Controller):
    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_THREAD_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_thread_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(TextBasedContentSchema())
    def get_thread(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:
        """
        Get thread content
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        return api.get_content_in_context(content)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_THREAD_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UserNotMemberOfWorkspace, HTTPStatus.BAD_REQUEST)
    @check_right(is_contributor)
    @check_right(is_thread_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.input_body(TextBasedContentModifySchema())
    @hapic.output_body(TextBasedContentSchema())
    def update_thread(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:
        """
        update thread
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        with new_revision(session=request.dbsession, tm=transaction.manager, content=content):
            api.update_content(
                item=content,
                new_label=hapic_data.body.label,
                new_content=hapic_data.body.raw_content,
            )
            api.save(content)
            api.execute_update_content_actions(content)
        return api.get_content_in_context(content)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_THREAD_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_thread_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(TextBasedRevisionSchema(many=True))
    def get_thread_revisions(
        self, context, request: TracimRequest, hapic_data=None
    ) -> typing.List[RevisionInContext]:
        """
        get thread revisions
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        revisions = content.revisions
        return [api.get_revision_in_context(revision) for revision in revisions]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_THREAD_ENDPOINTS])
    @check_right(is_contributor)
    @check_right(is_thread_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.input_body(SetContentStatusSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    @hapic.handle_exception(ContentStatusException, HTTPStatus.BAD_REQUEST)
    def set_thread_status(self, context, request: TracimRequest, hapic_data=None) -> None:
        """
        set thread status
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        if content.status == request.json_body.get("status"):
            raise ContentStatusException(
                "Content id {} already have status {}".format(content.content_id, content.status)
            )
        with new_revision(session=request.dbsession, tm=transaction.manager, content=content):
            api.set_status(content, hapic_data.body.status)
            api.save(content)
            api.execute_update_content_actions(content)
        return

    def bind(self, configurator: Configurator) -> None:
        # Get thread
        configurator.add_route(
            "thread", "/workspaces/{workspace_id}/threads/{content_id}", request_method="GET"
        )
        configurator.add_view(self.get_thread, route_name="thread")

        # update thread
        configurator.add_route(
            "update_thread", "/workspaces/{workspace_id}/threads/{content_id}", request_method="PUT"
        )
        configurator.add_view(self.update_thread, route_name="update_thread")

        # get thread revisions
        configurator.add_route(
            "thread_revisions",
            "/workspaces/{workspace_id}/threads/{content_id}/revisions",
            request_method="GET",
        )
        configurator.add_view(self.get_thread_revisions, route_name="thread_revisions")

        # get thread revisions
        configurator.add_route(
            "set_thread_status",
            "/workspaces/{workspace_id}/threads/{content_id}/status",
            request_method="PUT",
        )
        configurator.add_view(self.set_thread_status, route_name="set_thread_status")
Example #8
0
class UserController(Controller):
    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.input_query(UserWorkspaceFilterQuerySchema())
    @hapic.output_body(WorkspaceSchema(many=True))
    def user_workspace(self, context, request: TracimRequest, hapic_data=None):
        """
        Get list of user workspaces
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )

        workspaces = wapi.get_all_for_user(
            request.candidate_user,
            include_owned=hapic_data.query.show_owned_workspace,
            include_with_role=hapic_data.query.show_workspace_with_role,
            parents_ids=hapic_data.query.parent_ids,
        )
        return [wapi.get_workspace_with_context(workspace) for workspace in workspaces]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(RoleAlreadyExistError, HTTPStatus.BAD_REQUEST)
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.input_body(WorkspaceIdSchema())
    @hapic.output_body(WorkspaceSchema())
    def join_workspace(self, context, request: TracimRequest, hapic_data=None):
        """
        Join a workspace.
        Only possible for OPEN workspaces.
        Subscribing to a ON_REQUEST workspace is done through /api/users/<user_id>/workspace_subscriptions.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = wapi.add_current_user_as_member(workspace_id=hapic_data.body["workspace_id"])
        return wapi.get_workspace_with_context(workspace)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(UserSchema())
    def user(self, context, request: TracimRequest, hapic_data=None):
        """
        Get user infos.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        return uapi.get_user_with_context(request.candidate_user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(UserDiskSpaceSchema())
    def user_disk_space(self, context, request: TracimRequest, hapic_data=None):
        """
        Get user space infos.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        return uapi.get_user_with_context(request.candidate_user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @check_right(is_administrator)
    @hapic.output_body(UserDigestSchema(many=True))
    def users(self, context, request: TracimRequest, hapic_data=None):
        """
        Get all users
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        users = uapi.get_all()
        context_users = [uapi.get_user_with_context(user) for user in users]
        return context_users

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.input_query(KnownMembersQuerySchema())
    @hapic.output_body(UserDigestSchema(many=True))
    @hapic.handle_exception(CannotUseBothIncludeAndExcludeWorkspaceUsers, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(TooShortAutocompleteString, HTTPStatus.BAD_REQUEST)
    def known_members(self, context, request: TracimRequest, hapic_data=None):
        """
        Get known users list
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
            show_deactivated=False,
        )
        users = uapi.get_known_users(
            acp=hapic_data.query.acp,
            exclude_user_ids=hapic_data.query.exclude_user_ids,
            exclude_workspace_ids=hapic_data.query.exclude_workspace_ids,
            include_workspace_ids=hapic_data.query.include_workspace_ids,
            limit=hapic_data.query.limit,
            filter_results=app_config.KNOWN_MEMBERS__FILTER,
        )
        context_users = [uapi.get_user_with_context(user) for user in users]
        return context_users

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
    @hapic.handle_exception(EmailAlreadyExists, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ExternalAuthUserEmailModificationDisallowed, HTTPStatus.BAD_REQUEST)
    @check_right(has_personal_access)
    @hapic.input_body(SetEmailSchema())
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(UserSchema())
    def set_user_email(self, context, request: TracimRequest, hapic_data=None):
        """
        Set user Email
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        user = uapi.set_email(
            request.candidate_user,
            hapic_data.body.loggedin_user_password,
            hapic_data.body.email,
            do_save=True,
        )
        return uapi.get_user_with_context(user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
    @hapic.handle_exception(UsernameAlreadyExists, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ReservedUsernameError, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(TracimValidationFailed, HTTPStatus.BAD_REQUEST)
    @check_right(has_personal_access)
    @hapic.input_body(SetUsernameSchema())
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(UserSchema())
    def set_user_username(self, context, request: TracimRequest, hapic_data=None):
        """
        Set user username
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        user = uapi.set_username(
            request.candidate_user,
            hapic_data.body.loggedin_user_password,
            hapic_data.body.username,
            do_save=True,
        )
        return uapi.get_user_with_context(user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
    @hapic.handle_exception(PasswordDoNotMatch, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ExternalAuthUserPasswordModificationDisallowed, HTTPStatus.BAD_REQUEST)
    @check_right(has_personal_access)
    @hapic.input_body(SetPasswordSchema())
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def set_user_password(self, context, request: TracimRequest, hapic_data=None):
        """
        Set user password
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        uapi.set_password(
            request.candidate_user,
            hapic_data.body.loggedin_user_password,
            hapic_data.body.new_password,
            hapic_data.body.new_password2,
            do_save=True,
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_body(SetUserInfoSchema())
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(UserSchema())
    def set_user_infos(self, context, request: TracimRequest, hapic_data=None):
        """
        Set user info data
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        user = uapi.update(
            request.candidate_user,
            auth_type=request.candidate_user.auth_type,
            name=hapic_data.body.public_name,
            timezone=hapic_data.body.timezone,
            lang=hapic_data.body.lang,
            do_save=True,
        )
        uapi.execute_updated_user_actions(user)
        return uapi.get_user_with_context(user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @hapic.handle_exception(EmailAlreadyExists, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UsernameAlreadyExists, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ReservedUsernameError, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(TracimValidationFailed, HTTPStatus.BAD_REQUEST)
    @check_right(is_administrator)
    @hapic.input_body(UserCreationSchema())
    @hapic.output_body(UserSchema())
    def create_user(self, context, request: TracimRequest, hapic_data=None):
        """
        Create new user. Note: One of username or email required.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        if hapic_data.body.profile:
            profile = Profile.get_profile_from_slug(hapic_data.body.profile)
        else:
            profile = None
        password = hapic_data.body.password
        if not password and hapic_data.body.email_notification:
            password = password_generator()

        user = uapi.create_user(
            auth_type=AuthType.UNKNOWN,
            email=hapic_data.body.email,
            password=password,
            timezone=hapic_data.body.timezone,
            lang=hapic_data.body.lang,
            name=hapic_data.body.public_name,
            username=hapic_data.body.username,
            do_notify=hapic_data.body.email_notification,
            allowed_space=hapic_data.body.allowed_space,
            profile=profile,
            do_save=True,
        )
        uapi.execute_created_user_actions(user)
        return uapi.get_user_with_context(user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENABLE_AND_DISABLE_ENDPOINTS])
    @check_right(is_administrator)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def enable_user(self, context, request: TracimRequest, hapic_data=None):
        """
        enable user
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        uapi.enable(user=request.candidate_user, do_save=True)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_TRASH_AND_RESTORE_ENDPOINTS])
    @hapic.handle_exception(UserCantDeleteHimself, HTTPStatus.BAD_REQUEST)
    @check_right(is_administrator)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def delete_user(self, context, request: TracimRequest, hapic_data=None):
        """
        delete user
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        uapi.delete(user=request.candidate_user, do_save=True)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_TRASH_AND_RESTORE_ENDPOINTS])
    @check_right(is_administrator)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def undelete_user(self, context, request: TracimRequest, hapic_data=None):
        """
        undelete user
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
            show_deleted=True,
        )
        uapi.undelete(user=request.candidate_user, do_save=True)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENABLE_AND_DISABLE_ENDPOINTS])
    @hapic.handle_exception(UserCantDisableHimself, HTTPStatus.BAD_REQUEST)
    @check_right(is_administrator)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def disable_user(self, context, request: TracimRequest, hapic_data=None):
        """
        disable user
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        uapi.disable(user=request.candidate_user, do_save=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @hapic.handle_exception(UserCantChangeIsOwnProfile, HTTPStatus.BAD_REQUEST)
    @check_right(is_administrator)
    @hapic.input_path(UserIdPathSchema())
    @hapic.input_body(SetUserProfileSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def set_profile(self, context, request: TracimRequest, hapic_data=None):
        """
        set user profile
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        profile = Profile.get_profile_from_slug(hapic_data.body.profile)
        uapi.update(
            user=request.candidate_user,
            auth_type=request.candidate_user.auth_type,
            profile=profile,
            do_save=True,
        )
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
    @check_right(is_administrator)
    @hapic.input_path(UserIdPathSchema())
    @hapic.input_body(SetUserAllowedSpaceSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def set_allowed_space(self, context, request: TracimRequest, hapic_data=None):
        """
        set user allowed_space
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(
            current_user=request.current_user, session=request.dbsession, config=app_config  # User
        )
        uapi.update(
            user=request.candidate_user,
            auth_type=request.candidate_user.auth_type,
            allowed_space=hapic_data.body.allowed_space,
            do_save=True,
        )
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceIdPathSchema())
    @hapic.input_query(ActiveContentFilterQuerySchema())
    @hapic.output_body(ContentDigestSchema(many=True))
    def last_active_content(self, context, request: TracimRequest, hapic_data=None):
        """
        Get last_active_content for user
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        content_filter = hapic_data.query
        api = ContentApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = None
        if hapic_data.path.workspace_id:
            workspace = wapi.get_one(hapic_data.path.workspace_id)
        before_content = None
        if content_filter.before_content_id:
            before_content = api.get_one(
                content_id=content_filter.before_content_id,
                workspace=workspace,
                content_type=content_type_list.Any_SLUG,
            )
        last_actives = api.get_last_active(
            workspace=workspace, limit=content_filter.limit or None, before_content=before_content
        )
        return [api.get_content_in_context(content) for content in last_actives]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceIdPathSchema())
    @hapic.input_query(ContentIdsQuerySchema())
    @hapic.output_body(ReadStatusSchema(many=True))
    def contents_read_status(self, context, request: TracimRequest, hapic_data=None):
        """
        get user_read status of contents
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = None
        if hapic_data.path.workspace_id:
            workspace = wapi.get_one(hapic_data.path.workspace_id)
        last_actives = api.get_last_active(
            workspace=workspace,
            limit=None,
            before_content=None,
            content_ids=hapic_data.query.content_ids or None,
        )
        return [api.get_content_in_context(content) for content in last_actives]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def set_content_as_read(self, context, request: TracimRequest, hapic_data=None):
        """
        set user_read status of content to read
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.candidate_user,
            session=request.dbsession,
            config=app_config,
        )
        api.mark_read(request.current_content, do_flush=True)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def set_content_as_unread(self, context, request: TracimRequest, hapic_data=None):
        """
        set user_read status of content to unread
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.candidate_user,
            session=request.dbsession,
            config=app_config,
        )
        api.mark_unread(request.current_content, do_flush=True)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def set_workspace_as_read(self, context, request: TracimRequest, hapic_data=None):
        """
        set user_read status of all content of workspace to read
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.candidate_user,
            session=request.dbsession,
            config=app_config,
        )
        api.mark_read__workspace(request.current_workspace)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_NOTIFICATION_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def enable_workspace_notification(self, context, request: TracimRequest, hapic_data=None):
        """
        enable workspace notification
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = wapi.get_one(hapic_data.path.workspace_id)
        wapi.enable_notifications(request.candidate_user, workspace)
        wapi.save(workspace)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_NOTIFICATION_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserWorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def disable_workspace_notification(self, context, request: TracimRequest, hapic_data=None):
        """
        disable workspace notification
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.candidate_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = wapi.get_one(hapic_data.path.workspace_id)
        wapi.disable_notifications(request.candidate_user, workspace)
        wapi.save(workspace)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_EVENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.input_query(GetLiveMessageQuerySchema())
    @hapic.output_body(LiveMessageSchemaPage())
    def get_user_messages(
        self, context, request: TracimRequest, hapic_data: HapicData
    ) -> PaginatedObject:
        """
        Returns user messages matching the given query
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        event_api = EventApi(request.current_user, request.dbsession, app_config)
        return PaginatedObject(
            event_api.get_paginated_messages_for_user(
                user_id=request.candidate_user.user_id,
                read_status=hapic_data.query.read_status,
                page_token=hapic_data.query.page_token,
                count=hapic_data.query.count,
                exclude_author_ids=hapic_data.query.exclude_author_ids,
                include_event_types=hapic_data.query.include_event_types,
                exclude_event_types=hapic_data.query.exclude_event_types,
                workspace_ids=hapic_data.query.workspace_ids,
                include_not_sent=hapic_data.query.include_not_sent,
                related_to_content_ids=hapic_data.query.related_to_content_ids,
            )
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_EVENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.input_query(UserMessagesSummaryQuerySchema())
    @hapic.output_body(UserMessagesSummarySchema())
    def get_user_messages_summary(
        self, context, request: TracimRequest, hapic_data: HapicData
    ) -> UserMessagesSummary:
        """
        Returns a summary about messages filtered
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        event_api = EventApi(request.current_user, request.dbsession, app_config)
        candidate_user = UserApi(
            request.current_user, request.dbsession, app_config
        ).get_user_with_context(request.candidate_user)
        unread_messages_count = event_api.get_messages_count(
            user_id=candidate_user.user_id,
            read_status=ReadStatus.UNREAD,
            include_event_types=hapic_data.query.include_event_types,
            exclude_event_types=hapic_data.query.exclude_event_types,
            exclude_author_ids=hapic_data.query.exclude_author_ids,
            include_not_sent=hapic_data.query.include_not_sent,
            workspace_ids=hapic_data.query.workspace_ids,
            related_to_content_ids=hapic_data.query.related_to_content_ids,
        )
        read_messages_count = event_api.get_messages_count(
            user_id=candidate_user.user_id,
            read_status=ReadStatus.READ,
            include_event_types=hapic_data.query.include_event_types,
            exclude_event_types=hapic_data.query.exclude_event_types,
            exclude_author_ids=hapic_data.query.exclude_author_ids,
            include_not_sent=hapic_data.query.include_not_sent,
            workspace_ids=hapic_data.query.workspace_ids,
            related_to_content_ids=hapic_data.query.related_to_content_ids,
        )
        return UserMessagesSummary(
            user=candidate_user,
            unread_messages_count=unread_messages_count,
            read_messages_count=read_messages_count,
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_EVENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def set_all_user_messages_as_read(
        self, context, request: TracimRequest, hapic_data: HapicData
    ) -> None:
        """
        Read all unread message for user
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        event_api = EventApi(request.current_user, request.dbsession, app_config)
        event_api.mark_user_messages_as_read(request.candidate_user.user_id)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_EVENT_ENDPOINTS])
    @hapic.handle_exception(MessageDoesNotExist, http_code=HTTPStatus.BAD_REQUEST)
    @check_right(has_personal_access)
    @hapic.input_path(MessageIdsPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def set_message_as_read(self, context, request: TracimRequest, hapic_data: HapicData) -> None:
        """
        Read one message
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        event_api = EventApi(request.current_user, request.dbsession, app_config)
        event_api.mark_user_message_as_read(
            event_id=hapic_data.path.event_id, user_id=request.candidate_user.user_id
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_EVENT_ENDPOINTS])
    @hapic.handle_exception(MessageDoesNotExist, http_code=HTTPStatus.BAD_REQUEST)
    @check_right(has_personal_access)
    @hapic.input_path(MessageIdsPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def set_message_as_unread(self, context, request: TracimRequest, hapic_data: HapicData) -> None:
        """
        unread one message
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        event_api = EventApi(request.current_user, request.dbsession, app_config)
        event_api.mark_user_message_as_unread(
            event_id=hapic_data.path.event_id, user_id=request.candidate_user.user_id
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_EVENT_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.input_headers(TracimLiveEventHeaderSchema())
    @hapic.input_query(TracimLiveEventQuerySchema())
    def open_message_stream(self, context, request: TracimRequest, hapic_data) -> Response:
        """
        Open the message stream for the given user.
        Tracim Live Message Events as ServerSide Event Stream
        """
        stream_opened_event = ":Tracim Live Messages for user {}\n\nevent: stream-open\ndata:\n\n".format(
            str(request.candidate_user.user_id)
        )

        after_event_id = hapic_data.query["after_event_id"]  # type: int
        if after_event_id:
            app_config = request.registry.settings["CFG"]  # type: CFG
            event_api = EventApi(request.current_user, request.dbsession, app_config)
            messages = event_api.get_messages_for_user(
                request.candidate_user.user_id, after_event_id=after_event_id
            )  # type: typing.List[Message]

            stream_opened_event += "".join(
                [
                    "data:" + json.dumps(LiveMessagesLib.message_as_dict(message)) + "\n\n"
                    for message in messages
                ]
            )

        escaped_keepalive_event = "event: keep-alive\\ndata:\\n\\n"
        user_channel_name = LiveMessagesLib.user_grip_channel(request.candidate_user.user_id)
        headers = [
            # Here we ask push pin to keep the connection open
            ("Grip-Hold", "stream"),
            # and register this connection on the given channel
            # multiple channels subscription is possible
            ("Grip-Channel", user_channel_name),
            ("Grip-Keep-Alive", "{}; format=cstring; timeout=30".format(escaped_keepalive_event)),
            # content type for SSE
            ("Content-Type", "text/event-stream"),
            # do not cache the events
            ("Cache-Control", "no-cache"),
        ]
        return Response(
            headerlist=headers, charset="utf-8", status_code=200, body=stream_opened_event
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONFIG_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(UserConfigSchema())
    def get_user_config(
        self, context, request: TracimRequest, hapic_data: HapicData
    ) -> typing.Dict:
        """
        get all the configuration parameters for the given user
        """
        config_api = UserConfigApi(current_user=request.candidate_user, session=request.dbsession)
        return {"parameters": config_api.get_all_params()}

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONFIG_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.input_body(SetConfigSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    def set_user_config(self, context, request: TracimRequest, hapic_data: HapicData) -> None:
        """
        Set or update the given configuration parameters for the given user
        The behavior of this endpoint is adding/updating key (patch-like) but not replacing the
        whole configuration, so it's not possible to remove keys through this endpoint.
        """
        config_api = UserConfigApi(current_user=request.candidate_user, session=request.dbsession)
        config_api.set_params(params=hapic_data.body["parameters"])

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONFIG_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(WorkspaceSchema(many=True))
    def get_accessible_workspaces(
        self, context, request: TracimRequest, hapic_data: HapicData
    ) -> typing.List[WorkspaceInContext]:
        """
        Return the list of accessible workspaces by the given user id.
        An accessible workspace is:
          - a workspace the user is not member of (`workspaces` API returns them)
          - has an OPEN or ON_REQUEST access type
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.candidate_user, session=request.dbsession, config=app_config,
        )

        workspaces = wapi.get_all_accessible_by_user(request.candidate_user)
        return [wapi.get_workspace_with_context(workspace) for workspace in workspaces]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_SUBSCRIPTIONS_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.input_path(UserIdPathSchema())
    @hapic.output_body(WorkspaceSubscriptionSchema(many=True))
    def user_subscriptions(
        self, context, request: TracimRequest, hapic_data: HapicData
    ) -> typing.List[WorkspaceSubscription]:
        subscription_lib = SubscriptionLib(
            current_user=request.current_user, session=request.dbsession, config=request.app_config,
        )
        return subscription_lib.get_user_subscription(request.candidate_user.user_id)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_SUBSCRIPTIONS_ENDPOINTS])
    @check_right(has_personal_access)
    @hapic.handle_exception(InvalidWorkspaceAccessType, HTTPStatus.BAD_REQUEST)
    @hapic.input_path(UserIdPathSchema())
    @hapic.input_body(WorkspaceIdSchema())
    @hapic.output_body(WorkspaceSubscriptionSchema())
    def submit_subscription(
        self, context, request: TracimRequest, hapic_data: HapicData
    ) -> typing.List[WorkspaceSubscription]:
        workspace = WorkspaceApi(
            current_user=None, session=request.dbsession, config=request.app_config
        ).get_one(hapic_data.body["workspace_id"])
        subscription_lib = SubscriptionLib(
            current_user=request.current_user, session=request.dbsession, config=request.app_config,
        )
        return subscription_lib.submit_subscription(workspace=workspace)

    def bind(self, configurator: Configurator) -> None:
        """
        Create all routes and views using pyramid configurator
        for this controller
        """

        # user workspace
        configurator.add_route(
            "get_user_workspace",
            "/users/{user_id:\d+}/workspaces",
            request_method="GET",  # noqa: W605
        )
        configurator.add_view(self.user_workspace, route_name="get_user_workspace")
        configurator.add_route(
            "post_user_workspace",
            "/users/{user_id:\d+}/workspaces",
            request_method="POST",  # noqa: W605
        )
        configurator.add_view(self.join_workspace, route_name="post_user_workspace")

        # user info
        configurator.add_route("user", "/users/{user_id:\d+}", request_method="GET")  # noqa: W605
        configurator.add_view(self.user, route_name="user")

        # user space info
        configurator.add_route(
            "user_disk_space", "/users/{user_id:\d+}/disk_space", request_method="GET"
        )  # noqa: W605
        configurator.add_view(self.user_disk_space, route_name="user_disk_space")

        # users lists
        configurator.add_route("users", "/users", request_method="GET")
        configurator.add_view(self.users, route_name="users")

        # known members lists
        configurator.add_route(
            "known_members",
            "/users/{user_id:\d+}/known_members",
            request_method="GET",  # noqa: W605
        )
        configurator.add_view(self.known_members, route_name="known_members")

        # set user email
        configurator.add_route(
            "set_user_email", "/users/{user_id:\d+}/email", request_method="PUT"
        )  # noqa: W605
        configurator.add_view(self.set_user_email, route_name="set_user_email")

        # set user username
        configurator.add_route(
            "set_user_username", "/users/{user_id:\d+}/username", request_method="PUT"
        )  # noqa: W605
        configurator.add_view(self.set_user_username, route_name="set_user_username")

        # set user password
        configurator.add_route(
            "set_user_password", "/users/{user_id:\d+}/password", request_method="PUT"  # noqa: W605
        )
        configurator.add_view(self.set_user_password, route_name="set_user_password")

        # set user_info
        configurator.add_route(
            "set_user_info", "/users/{user_id:\d+}", request_method="PUT"
        )  # noqa: W605
        configurator.add_view(self.set_user_infos, route_name="set_user_info")

        # create user
        configurator.add_route("create_user", "/users", request_method="POST")
        configurator.add_view(self.create_user, route_name="create_user")

        # enable user
        configurator.add_route(
            "enable_user", "/users/{user_id:\d+}/enabled", request_method="PUT"
        )  # noqa: W605
        configurator.add_view(self.enable_user, route_name="enable_user")

        # disable user
        configurator.add_route(
            "disable_user", "/users/{user_id:\d+}/disabled", request_method="PUT"  # noqa: W605
        )
        configurator.add_view(self.disable_user, route_name="disable_user")

        # delete user
        configurator.add_route(
            "delete_user", "/users/{user_id:\d+}/trashed", request_method="PUT"
        )  # noqa: W605
        configurator.add_view(self.delete_user, route_name="delete_user")

        # undelete user
        configurator.add_route(
            "undelete_user",
            "/users/{user_id:\d+}/trashed/restore",
            request_method="PUT",  # noqa: W605
        )
        configurator.add_view(self.undelete_user, route_name="undelete_user")

        # set user profile
        configurator.add_route(
            "set_user_profile", "/users/{user_id:\d+}/profile", request_method="PUT"  # noqa: W605
        )
        configurator.add_view(self.set_profile, route_name="set_user_profile")

        # set user allowed_space
        configurator.add_route(
            "set_user_allowed_space",
            "/users/{user_id:\d+}/allowed_space",
            request_method="PUT",  # noqa: W605
        )
        configurator.add_view(self.set_allowed_space, route_name="set_user_allowed_space")

        # user content
        configurator.add_route(
            "contents_read_status",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/read_status",  # noqa: W605
            request_method="GET",
        )
        configurator.add_view(self.contents_read_status, route_name="contents_read_status")
        # last active content for user
        configurator.add_route(
            "last_active_content",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/recently_active",  # noqa: W605
            request_method="GET",
        )
        configurator.add_view(self.last_active_content, route_name="last_active_content")

        # set content as read/unread
        configurator.add_route(
            "read_content",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/{content_id}/read",  # noqa: W605
            request_method="PUT",
        )
        configurator.add_view(self.set_content_as_read, route_name="read_content")
        configurator.add_route(
            "unread_content",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/{content_id}/unread",  # noqa: W605
            request_method="PUT",
        )
        configurator.add_view(self.set_content_as_unread, route_name="unread_content")

        # set workspace as read
        configurator.add_route(
            "read_workspace",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/read",  # noqa: W605
            request_method="PUT",
        )
        configurator.add_view(self.set_workspace_as_read, route_name="read_workspace")

        # enable workspace notification
        configurator.add_route(
            "enable_workspace_notification",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/notifications/activate",  # noqa: W605
            request_method="PUT",
        )
        configurator.add_view(
            self.enable_workspace_notification, route_name="enable_workspace_notification"
        )

        # enable workspace notification
        configurator.add_route(
            "disable_workspace_notification",
            "/users/{user_id:\d+}/workspaces/{workspace_id}/notifications/deactivate",  # noqa: W605
            request_method="PUT",
        )
        configurator.add_view(
            self.disable_workspace_notification, route_name="disable_workspace_notification"
        )
        # TracimLiveMessages notification
        configurator.add_route(
            "live_messages",
            "/users/{user_id:\d+}/live_messages",  # noqa: W605
            request_method="GET",
        )
        configurator.add_view(self.open_message_stream, route_name="live_messages")

        # Tracim user messages
        configurator.add_route(
            "messages", "/users/{user_id:\d+}/messages", request_method="GET",  # noqa: W605
        )
        configurator.add_view(self.get_user_messages, route_name="messages")

        configurator.add_route(
            "messages_summary",
            "/users/{user_id:\d+}/messages/summary",
            request_method="GET",  # noqa: W605
        )
        configurator.add_view(self.get_user_messages_summary, route_name="messages_summary")

        # read all unread messages for user
        configurator.add_route(
            "read_messages",
            "/users/{user_id:\d+}/messages/read",
            request_method="PUT",  # noqa: W605
        )
        configurator.add_view(self.set_all_user_messages_as_read, route_name="read_messages")

        # read all unread messages for user
        configurator.add_route(
            "read_message",
            "/users/{user_id:\d+}/messages/{event_id:\d+}/read",
            request_method="PUT",  # noqa: W605
        )
        configurator.add_view(self.set_message_as_read, route_name="read_message")

        # read all unread messages for user
        configurator.add_route(
            "unread_message",
            "/users/{user_id:\d+}/messages/{event_id:\d+}/unread",
            request_method="PUT",  # noqa: W605
        )
        configurator.add_view(self.set_message_as_unread, route_name="unread_message")

        # User configuration
        configurator.add_route(
            "config_get", "/users/{user_id:\d+}/config", request_method="GET",  # noqa: W605
        )
        configurator.add_view(self.get_user_config, route_name="config_get")

        configurator.add_route(
            "config_post", "/users/{user_id:\d+}/config", request_method="PUT",  # noqa: W605
        )
        configurator.add_view(self.set_user_config, route_name="config_post")

        # User accessible workspaces (not member of, but can see information about them to subscribe)
        configurator.add_route(
            "get_accessible_workspaces",
            "/users/{user_id:\d+}/accessible_workspaces",
            request_method="GET",  # noqa: W605
        )
        configurator.add_view(
            self.get_accessible_workspaces, route_name="get_accessible_workspaces"
        )

        # User subscriptions
        configurator.add_route(
            "subscriptions_get",
            "/users/{user_id:\d+}/workspace_subscriptions",
            request_method="GET",  # noqa: W605
        )
        configurator.add_view(self.user_subscriptions, route_name="subscriptions_get")

        configurator.add_route(
            "subscriptions_put",
            "/users/{user_id:\d+}/workspace_subscriptions",
            request_method="PUT",  # noqa: W605
        )
        configurator.add_view(self.submit_subscription, route_name="subscriptions_put")
class ResetPasswordController(Controller):
    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__AUTHENTICATION_RESET_PASSWORD_ENDPOINTS])  # nopep8
    @hapic.handle_exception(NotificationDisabledCantResetPassword,
                            http_code=HTTPStatus.BAD_REQUEST)  # nopep8
    @hapic.handle_exception(ExternalAuthUserPasswordModificationDisallowed,
                            http_code=HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UserAuthTypeDisabled,
                            http_code=HTTPStatus.BAD_REQUEST)
    @hapic.input_body(ResetPasswordRequestSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def reset_password_request(self,
                               context,
                               request: TracimRequest,
                               hapic_data=None):  # nopep8
        """
        Send a request to reset password. This will result in a new email sent to the user
        with a token to be used for password reset operation.
        """
        app_config = request.registry.settings['CFG']
        uapi = UserApi(
            None,
            session=request.dbsession,
            config=app_config,
        )
        user = uapi.get_one_by_email(hapic_data.body.email)
        uapi.reset_password_notification(user, do_save=True)
        return

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__AUTHENTICATION_RESET_PASSWORD_ENDPOINTS])  # nopep8
    @hapic.handle_exception(ExpiredResetPasswordToken,
                            http_code=HTTPStatus.BAD_REQUEST)  # nopep8
    @hapic.handle_exception(UnvalidResetPasswordToken,
                            http_code=HTTPStatus.BAD_REQUEST)  # nopep8
    @hapic.handle_exception(ExternalAuthUserPasswordModificationDisallowed,
                            http_code=HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UserAuthTypeDisabled,
                            http_code=HTTPStatus.BAD_REQUEST)
    @hapic.input_body(ResetPasswordCheckTokenSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def reset_password_check_token(self,
                                   context,
                                   request: TracimRequest,
                                   hapic_data=None):  # nopep8
        """
        Check reset_password token. The token sent by email has a limited life duration,
        this API allow to check that the token is existing and still valid.
        """
        app_config = request.registry.settings['CFG']
        uapi = UserApi(
            None,
            session=request.dbsession,
            config=app_config,
        )
        user = uapi.get_one_by_email(hapic_data.body.email)
        uapi.validate_reset_password_token(
            user, hapic_data.body.reset_password_token)  # nopep8
        return

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__AUTHENTICATION_RESET_PASSWORD_ENDPOINTS])  # nopep8
    @hapic.handle_exception(ExpiredResetPasswordToken,
                            http_code=HTTPStatus.BAD_REQUEST)  # nopep8
    @hapic.handle_exception(UnvalidResetPasswordToken,
                            http_code=HTTPStatus.BAD_REQUEST)  # nopep8
    @hapic.handle_exception(ExternalAuthUserPasswordModificationDisallowed,
                            http_code=HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(PasswordDoNotMatch,
                            http_code=HTTPStatus.BAD_REQUEST)  # nopep8
    @hapic.handle_exception(UserAuthTypeDisabled,
                            http_code=HTTPStatus.BAD_REQUEST)
    @hapic.input_body(ResetPasswordModifySchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def reset_password_modify(self,
                              context,
                              request: TracimRequest,
                              hapic_data=None):  # nopep8
        """
        Do change the password. This requires the token received by email.
        After this request returns a 200, the user password is effectively changed
        """
        app_config = request.registry.settings['CFG']
        uapi = UserApi(
            None,
            session=request.dbsession,
            config=app_config,
        )
        user = uapi.get_one_by_email(hapic_data.body.email)
        uapi.set_password_reset_token(
            new_password=hapic_data.body.new_password,
            new_password2=hapic_data.body.new_password2,
            reset_token=hapic_data.body.reset_password_token,
            user=user,
            do_save=True)
        return

    def bind(self, configurator: Configurator):
        # reset password request
        configurator.add_route('reset_password_request',
                               '/auth/password/reset/request',
                               request_method='POST')  # nopep8
        configurator.add_view(self.reset_password_request,
                              route_name='reset_password_request')  # nopep8
        # check reset password token
        configurator.add_route('reset_password_check_token',
                               '/auth/password/reset/token/check',
                               request_method='POST')  # nopep8
        configurator.add_view(
            self.reset_password_check_token,
            route_name='reset_password_check_token')  # nopep8
        # reset password, set password
        configurator.add_route('reset_password_modify',
                               '/auth/password/reset/modify',
                               request_method='POST')  # nopep8
        configurator.add_view(self.reset_password_modify,
                              route_name='reset_password_modify')  # nopep8
Example #10
0
class AccountController(Controller):
    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_ENDPOINTS])
    @check_right(is_user)
    @hapic.output_body(UserSchema())
    def account(self, context, request: TracimRequest, hapic_data=None):
        """
        Get user infos.
        """
        app_config = request.registry.settings['CFG']
        uapi = UserApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        return uapi.get_user_with_context(request.current_user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_ENDPOINTS])
    @check_right(is_user)
    @hapic.input_body(SetUserInfoSchema())
    @hapic.output_body(UserSchema())
    def set_account_infos(self,
                          context,
                          request: TracimRequest,
                          hapic_data=None):
        """
        Set user info data
        """
        app_config = request.registry.settings['CFG']
        uapi = UserApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        user = uapi.update(request.current_user,
                           name=hapic_data.body.public_name,
                           timezone=hapic_data.body.timezone,
                           lang=hapic_data.body.lang,
                           do_save=True)
        return uapi.get_user_with_context(user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_ENDPOINTS])
    @check_right(is_user)
    @hapic.input_query(KnownMemberQuerySchema())
    @hapic.output_body(UserDigestSchema(many=True))
    def account_known_members(self,
                              context,
                              request: TracimRequest,
                              hapic_data=None):
        """
        Get known users list
        """
        app_config = request.registry.settings['CFG']
        uapi = UserApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
            show_deactivated=False,
        )
        users = uapi.get_known_user(
            acp=hapic_data.query.acp,
            exclude_user_ids=hapic_data.query.exclude_user_ids,
            exclude_workspace_ids=hapic_data.query.exclude_workspace_ids,
        )
        context_users = [uapi.get_user_with_context(user) for user in users]
        return context_users

    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_ENDPOINTS])
    @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
    @hapic.handle_exception(EmailAlreadyExistInDb, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ExternalAuthUserEmailModificationDisallowed,
                            HTTPStatus.BAD_REQUEST)  # nopep8
    @check_right(is_user)
    @hapic.input_body(SetEmailSchema())
    @hapic.output_body(UserSchema())
    def set_account_email(self,
                          context,
                          request: TracimRequest,
                          hapic_data=None):
        """
        Set user Email
        """
        app_config = request.registry.settings['CFG']
        uapi = UserApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        user = uapi.set_email(request.current_user,
                              hapic_data.body.loggedin_user_password,
                              hapic_data.body.email,
                              do_save=True)
        return uapi.get_user_with_context(user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_ENDPOINTS])
    @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
    @hapic.handle_exception(PasswordDoNotMatch, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ExternalAuthUserPasswordModificationDisallowed,
                            HTTPStatus.BAD_REQUEST)  # nopep8
    @check_right(is_user)
    @hapic.input_body(SetPasswordSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def set_account_password(self,
                             context,
                             request: TracimRequest,
                             hapic_data=None):  # nopep8
        """
        Set user password
        """
        app_config = request.registry.settings['CFG']
        uapi = UserApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        uapi.set_password(request.current_user,
                          hapic_data.body.loggedin_user_password,
                          hapic_data.body.new_password,
                          hapic_data.body.new_password2,
                          do_save=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_CONTENT_ENDPOINTS])
    @check_right(is_user)
    @hapic.output_body(
        WorkspaceDigestSchema(many=True), )
    def account_workspace(self,
                          context,
                          request: TracimRequest,
                          hapic_data=None):
        """
        Get list of auth user workspaces
        """
        app_config = request.registry.settings['CFG']
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )

        workspaces = wapi.get_all_for_user(request.current_user)
        return [
            wapi.get_workspace_with_context(workspace)
            for workspace in workspaces
        ]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_CONTENT_ENDPOINTS])
    @check_right(is_user)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.input_query(ActiveContentFilterQuerySchema())
    @hapic.output_body(ContentDigestSchema(many=True))
    def account_last_active_content(self,
                                    context,
                                    request: TracimRequest,
                                    hapic_data=None):  # nopep8
        """
        Get last_active_content for user
        """
        app_config = request.registry.settings['CFG']
        content_filter = hapic_data.query
        api = ContentApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = None
        if hapic_data.path.workspace_id:
            workspace = wapi.get_one(hapic_data.path.workspace_id)
        before_content = None
        if content_filter.before_content_id:
            before_content = api.get_one(
                content_id=content_filter.before_content_id,
                workspace=workspace,
                content_type=content_type_list.Any_SLUG)
        last_actives = api.get_last_active(
            workspace=workspace,
            limit=content_filter.limit or None,
            before_content=before_content,
        )
        return [
            api.get_content_in_context(content) for content in last_actives
        ]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_CONTENT_ENDPOINTS])
    @check_right(is_user)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.input_query(ContentIdsQuerySchema())
    @hapic.output_body(ReadStatusSchema(many=True))  # nopep8
    def account_contents_read_status(self,
                                     context,
                                     request: TracimRequest,
                                     hapic_data=None):  # nopep8
        """
        get user_read status of contents
        """
        app_config = request.registry.settings['CFG']
        content_filter = hapic_data.query
        api = ContentApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = None
        if hapic_data.path.workspace_id:
            workspace = wapi.get_one(hapic_data.path.workspace_id)
        last_actives = api.get_last_active(
            workspace=workspace,
            limit=None,
            before_content=None,
            content_ids=hapic_data.query.content_ids or None)
        return [
            api.get_content_in_context(content) for content in last_actives
        ]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_CONTENT_ENDPOINTS])
    @check_right(is_user)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def set_account_content_as_read(self,
                                    context,
                                    request: TracimRequest,
                                    hapic_data=None):  # nopep8
        """
        set user_read status of content to read
        """
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        api.mark_read(request.current_content, do_flush=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_CONTENT_ENDPOINTS])
    @check_right(is_user)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def set_account_content_as_unread(self,
                                      context,
                                      request: TracimRequest,
                                      hapic_data=None):  # nopep8
        """
        set user_read status of content to unread
        """
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        api.mark_unread(request.current_content, do_flush=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_CONTENT_ENDPOINTS])
    @check_right(is_user)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def set_account_workspace_as_read(self,
                                      context,
                                      request: TracimRequest,
                                      hapic_data=None):  # nopep8
        """
        set user_read status of all content of workspace to read
        """
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        api.mark_read__workspace(request.current_workspace)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_NOTIFICATION_ENDPOINTS])
    @check_right(is_user)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def enable_account_workspace_notification(self,
                                              context,
                                              request: TracimRequest,
                                              hapic_data=None):  # nopep8
        """
        enable workspace notification
        """
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = wapi.get_one(hapic_data.path.workspace_id)
        wapi.enable_notifications(request.current_user, workspace)
        rapi = RoleApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        role = rapi.get_one(request.current_user.user_id,
                            workspace.workspace_id)  # nopep8
        wapi.save(workspace)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_NOTIFICATION_ENDPOINTS])
    @check_right(is_user)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def disable_account_workspace_notification(self,
                                               context,
                                               request: TracimRequest,
                                               hapic_data=None):  # nopep8
        """
        disable workspace notification
        """
        app_config = request.registry.settings['CFG']
        api = ContentApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
        )
        workspace = wapi.get_one(hapic_data.path.workspace_id)
        wapi.disable_notifications(request.current_user, workspace)
        wapi.save(workspace)
        return

    def bind(self, configurator: Configurator) -> None:
        """
        Create all routes and views using pyramid configurator
        for this controller
        """

        # account workspace
        configurator.add_route('account_workspace',
                               '/users/me/workspaces',
                               request_method='GET')  # nopep8
        configurator.add_view(self.account_workspace,
                              route_name='account_workspace')

        # account info
        configurator.add_route('account', '/users/me',
                               request_method='GET')  # nopep8
        configurator.add_view(self.account, route_name='account')
        #
        #
        # known members lists
        configurator.add_route('account_known_members',
                               '/users/me/known_members',
                               request_method='GET')  # nopep8
        configurator.add_view(self.account_known_members,
                              route_name='account_known_members')
        #
        # set account email
        configurator.add_route('set_account_email',
                               '/users/me/email',
                               request_method='PUT')  # nopep8
        configurator.add_view(self.set_account_email,
                              route_name='set_account_email')

        # set account password
        configurator.add_route('set_account_password',
                               '/users/me/password',
                               request_method='PUT')  # nopep8
        configurator.add_view(self.set_account_password,
                              route_name='set_account_password')  # nopep8

        # set account_infos
        configurator.add_route('set_account_info',
                               '/users/me',
                               request_method='PUT')  # nopep8
        configurator.add_view(self.set_account_infos,
                              route_name='set_account_info')

        # account content
        configurator.add_route(
            'account_contents_read_status',
            '/users/me/workspaces/{workspace_id}/contents/read_status',
            request_method='GET')  # nopep8
        configurator.add_view(
            self.account_contents_read_status,
            route_name='account_contents_read_status')  # nopep8
        # last active content for user
        configurator.add_route(
            'account_last_active_content',
            '/users/me/workspaces/{workspace_id}/contents/recently_active',
            request_method='GET')  # nopep8
        configurator.add_view(
            self.account_last_active_content,
            route_name='account_last_active_content')  # nopep8

        # set content as read/unread
        configurator.add_route(
            'account_read_content',
            '/users/me/workspaces/{workspace_id}/contents/{content_id}/read',
            request_method='PUT')  # nopep8
        configurator.add_view(self.set_account_content_as_read,
                              route_name='account_read_content')  # nopep8

        configurator.add_route(
            'account_unread_content',
            '/users/me/workspaces/{workspace_id}/contents/{content_id}/unread',
            request_method='PUT')  # nopep8
        configurator.add_view(self.set_account_content_as_unread,
                              route_name='account_unread_content')  # nopep8

        # set workspace as read
        configurator.add_route('account_read_workspace',
                               '/users/me/workspaces/{workspace_id}/read',
                               request_method='PUT')  # nopep8
        configurator.add_view(self.set_account_workspace_as_read,
                              route_name='account_read_workspace')  # nopep8

        # enable workspace notification
        configurator.add_route(
            'enable_account_workspace_notification',
            '/users/me/workspaces/{workspace_id}/notifications/activate',
            request_method='PUT')  # nopep8
        configurator.add_view(
            self.enable_account_workspace_notification,
            route_name='enable_account_workspace_notification')  # nopep8

        # disable workspace notification
        configurator.add_route(
            'disable_account_workspace_notification',
            '/users/me/workspaces/{workspace_id}/notifications/deactivate',
            request_method='PUT')  # nopep8
        configurator.add_view(
            self.disable_account_workspace_notification,
            route_name='disable_account_workspace_notification')  # nopep8
Example #11
0
class SessionController(Controller):
    @hapic.with_api_doc(tags=[SWAGGER_TAG__AUTHENTICATION_ENDPOINTS])
    @hapic.input_headers(LoginOutputHeaders())
    @hapic.input_body(BasicAuthSchema())
    @hapic.output_body(UserSchema())
    def login(self, context, request: TracimRequest,
              hapic_data: HapicData) -> UserInContext:
        """
        Logs the user into the system.
        In case of success, the JSON returned is the user profile.
        In that case, a cookie is created with a session_key and an expiration date.
        Eg. : `session_key=932d2ad68f3a094c2d4da563ccb921e6479729f5b5f707eba91d4194979df20831be48a0; expires=Mon, 22-Oct-2018 19:37:02 GMT; Path=/; SameSite=Lax`
        """

        login = hapic_data.body  # type: LoginCredentials
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(None, session=request.dbsession, config=app_config)
        ldap_connector = None
        if AuthType.LDAP in app_config.AUTH_TYPES:
            ldap_connector = get_ldap_connector(request)

        user = None
        if login.email:
            try:
                user = uapi.authenticate(
                    login=login.email,
                    password=login.password,
                    ldap_connector=ldap_connector,
                )
            except AuthenticationFailed as exc:
                if not login.username:
                    raise exc

        if user is None:
            user = uapi.authenticate(
                login=login.username,
                password=login.password,
                ldap_connector=ldap_connector,
            )

        remember(request, user.user_id)
        return uapi.get_user_with_context(user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__AUTHENTICATION_ENDPOINTS])
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def logout(self, context, request: TracimRequest, hapic_data=None):
        """
        Logs out current logged in user. This also trashes the associated session and the
        live message connections of the current user.
        """
        if request.authenticated_userid:
            app_config = request.registry.settings["CFG"]  # type: CFG
            LiveMessagesLib(app_config).close_channel_connections(
                LiveMessagesLib.user_grip_channel(
                    request.current_user.user_id))
        forget(request)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__AUTHENTICATION_ENDPOINTS])
    @hapic.output_body(UserSchema())
    def whoami(self, context, request: TracimRequest, hapic_data=None):
        """
        Return current logged-in user.
        If user is not authenticated or the session has expired, a 401 is returned.
        This is the recommanded way to check if the user is already authenticated
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        uapi = UserApi(request.current_user,
                       session=request.dbsession,
                       config=app_config)
        user = uapi.get_current_user()  # User
        return uapi.get_user_with_context(user)

    def bind(self, configurator: Configurator):

        # Login
        configurator.add_route("login", "/auth/login", request_method="POST")
        configurator.add_view(self.login, route_name="login")
        # Logout
        configurator.add_route("logout", "/auth/logout", request_method="POST")
        configurator.add_view(self.logout, route_name="logout")
        configurator.add_route("logout_get",
                               "/auth/logout",
                               request_method="GET")
        configurator.add_view(self.logout, route_name="logout_get")
        # Whoami
        configurator.add_route("whoami", "/auth/whoami", request_method="GET")
        configurator.add_view(self.whoami, route_name="whoami")
Example #12
0
class HTMLDocumentController(Controller):
    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_HTML_DOCUMENT_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_html_document_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(TextBasedContentSchema())
    def get_html_document(
        self, context, request: TracimRequest, hapic_data=None
    ) -> ContentInContext:
        """
        Get html document content
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        return api.get_content_in_context(content)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_HTML_DOCUMENT_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_html_document_content)
    @hapic.input_query(FileQuerySchema())
    @hapic.input_path(FilePathSchema())
    @hapic.output_file([])
    def get_html_document_preview(
        self, context, request: TracimRequest, hapic_data=None
    ) -> HapicFile:
        """
           Download preview of html document
           Good pratice for filename is filename is `{label}{file_extension}` or `{filename}`.
           Default filename value is 'raw' (without file extension) or nothing.
           """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        file = BytesIO()
        byte_size = file.write(content.description.encode("utf-8"))
        file.seek(0)
        filename = hapic_data.path.filename
        # INFO - G.M - 2019-08-08 - use given filename in all case but none or
        # "raw", where filename returned will be original file one.
        if not filename or filename == "raw":
            filename = content.file_name
        return HapicFile(
            file_object=file,
            mimetype=CONTENT_TYPE_TEXT_HTML,
            filename=filename,
            as_attachment=hapic_data.query.force_download,
            content_length=byte_size,
            last_modified=content.updated,
        )

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_HTML_DOCUMENT_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UserNotMemberOfWorkspace, HTTPStatus.BAD_REQUEST)
    @check_right(is_contributor)
    @check_right(is_html_document_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.input_body(TextBasedContentModifySchema())
    @hapic.output_body(TextBasedContentSchema())
    def update_html_document(
        self, context, request: TracimRequest, hapic_data=None
    ) -> ContentInContext:
        """
        update_html_document
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        with new_revision(session=request.dbsession, tm=transaction.manager, content=content):
            api.update_content(
                item=content,
                new_label=hapic_data.body.label,
                new_content=hapic_data.body.raw_content,
            )
            api.save(content)
            api.execute_update_content_actions(content)
        return api.get_content_in_context(content)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_HTML_DOCUMENT_ENDPOINTS])
    @check_right(is_reader)
    @check_right(is_html_document_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(TextBasedRevisionSchema(many=True))
    def get_html_document_revisions(
        self, context, request: TracimRequest, hapic_data=None
    ) -> typing.List[RevisionInContext]:
        """
        get html_document revisions
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        revisions = content.revisions
        return [api.get_revision_in_context(revision) for revision in revisions]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_HTML_DOCUMENT_ENDPOINTS])
    @check_right(is_contributor)
    @check_right(is_html_document_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.input_body(SetContentStatusSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)
    @hapic.handle_exception(ContentStatusException, HTTPStatus.BAD_REQUEST)
    def set_html_document_status(self, context, request: TracimRequest, hapic_data=None) -> None:
        """
        set html_document status
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG)
        if content.status == request.json_body.get("status"):
            raise ContentStatusException(
                "Content id {} already have status {}".format(content.content_id, content.status)
            )
        with new_revision(session=request.dbsession, tm=transaction.manager, content=content):
            api.set_status(content, hapic_data.body.status)
            api.save(content)
            api.execute_update_content_actions(content)
        return

    def bind(self, configurator: Configurator) -> None:
        # Get html-document
        configurator.add_route(
            "html_document",
            "/workspaces/{workspace_id}/html-documents/{content_id}",
            request_method="GET",
        )
        configurator.add_view(self.get_html_document, route_name="html_document")

        # get html-document preview
        configurator.add_route(
            "preview_html",
            "/workspaces/{workspace_id}/html-documents/{content_id}/preview/html/{filename:[^/]*}",
            request_method="GET",
        )
        configurator.add_view(self.get_html_document_preview, route_name="preview_html")

        # update html-document
        configurator.add_route(
            "update_html_document",
            "/workspaces/{workspace_id}/html-documents/{content_id}",
            request_method="PUT",
        )
        configurator.add_view(self.update_html_document, route_name="update_html_document")

        # get html document revisions
        configurator.add_route(
            "html_document_revisions",
            "/workspaces/{workspace_id}/html-documents/{content_id}/revisions",
            request_method="GET",
        )
        configurator.add_view(
            self.get_html_document_revisions, route_name="html_document_revisions"
        )

        # get html document revisions
        configurator.add_route(
            "set_html_document_status",
            "/workspaces/{workspace_id}/html-documents/{content_id}/status",
            request_method="PUT",
        )
        configurator.add_view(self.set_html_document_status, route_name="set_html_document_status")
Example #13
0
class SessionController(Controller):
    @hapic.with_api_doc(tags=[SWAGGER_TAG__AUTHENTICATION_ENDPOINTS])
    @hapic.input_headers(LoginOutputHeaders())
    @hapic.input_body(BasicAuthSchema())
    @hapic.output_body(UserSchema())
    def login(self, context, request: TracimRequest, hapic_data=None):
        """
        Logs the user into the system.
        In case of success, the JSON returned is the user profile.
        In that case, a cookie is created with a session_key and an expiration date.
        Eg. : `session_key=932d2ad68f3a094c2d4da563ccb921e6479729f5b5f707eba91d4194979df20831be48a0; expires=Mon, 22-Oct-2018 19:37:02 GMT; Path=/; SameSite=Lax`
        """

        login = hapic_data.body
        app_config = request.registry.settings['CFG']
        uapi = UserApi(
            None,
            session=request.dbsession,
            config=app_config,
        )
        user = uapi.authenticate_user(login.email, login.password)
        remember(request, user.user_id)
        return uapi.get_user_with_context(user)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__AUTHENTICATION_ENDPOINTS])
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
    def logout(self, context, request: TracimRequest, hapic_data=None):
        """
        Logs out current logged in user. This also trashes the associated session
        """
        request.session.delete()
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__AUTHENTICATION_ENDPOINTS])
    @hapic.output_body(
        UserSchema(), )
    def whoami(self, context, request: TracimRequest, hapic_data=None):
        """
        Return current logged-in user.
        If user is not authenticated or the session has expired, a 401 is returned.
        This is the recommanded way to check if the user is already authenticated
        """
        app_config = request.registry.settings['CFG']
        uapi = UserApi(
            request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        user = uapi.get_current_user()  # User
        return uapi.get_user_with_context(user)

    def bind(self, configurator: Configurator):

        # Login
        configurator.add_route('login', '/auth/login',
                               request_method='POST')  # nopep8
        configurator.add_view(self.login, route_name='login')
        # Logout
        configurator.add_route('logout', '/auth/logout',
                               request_method='POST')  # nopep8
        configurator.add_view(self.logout, route_name='logout')
        configurator.add_route('logout_get',
                               '/auth/logout',
                               request_method='GET')  # nopep8
        configurator.add_view(self.logout, route_name='logout_get')
        # Whoami
        configurator.add_route('whoami', '/auth/whoami',
                               request_method='GET')  # nopep8
        configurator.add_view(
            self.whoami,
            route_name='whoami',
        )
Example #14
0
class WorkspaceController(Controller):
    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS])
    @check_right(can_see_workspace_information)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(WorkspaceSchema())
    def workspace(self, context, request: TracimRequest, hapic_data=None):
        """
        Get workspace informations
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        return wapi.get_workspace_with_context(request.current_workspace)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS])
    @check_right(can_see_workspace_information)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(WorkspaceDiskSpaceSchema())
    def workspace_disk_space(self,
                             context,
                             request: TracimRequest,
                             hapic_data=None):
        """
        Get workspace space info
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        return wapi.get_workspace_with_context(request.current_workspace)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS])
    @check_right(is_administrator)
    @hapic.input_query(WorkspaceFilterQuerySchema())
    @hapic.output_body(WorkspaceSchema(many=True))
    def workspaces(self, context, request: TracimRequest, hapic_data=None):
        """
        Returns the list of all workspaces. This route is for admin only.
        Standard users must use their own route: /api/users/me/workspaces
        Filtering by parent_ids is possible through this endpoint
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )

        workspaces = wapi.get_all_children(
            parent_ids=hapic_data.query.parent_ids)
        return [
            wapi.get_workspace_with_context(workspace)
            for workspace in workspaces
        ]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(DisallowedWorkspaceAccessType,
                            HTTPStatus.BAD_REQUEST)
    @check_right(can_modify_workspace)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.input_body(WorkspaceModifySchema())
    @hapic.output_body(WorkspaceSchema())
    def update_workspace(self,
                         context,
                         request: TracimRequest,
                         hapic_data=None):
        """
        Update a workspace. This route is for trusted users and administrators.
        Note : a trusted user can only update spaces on which he/she is space manager
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        wapi.update_workspace(
            request.current_workspace,
            label=hapic_data.body.label,
            description=hapic_data.body.description,
            agenda_enabled=hapic_data.body.agenda_enabled,
            public_download_enabled=hapic_data.body.public_download_enabled,
            public_upload_enabled=hapic_data.body.public_upload_enabled,
            default_user_role=hapic_data.body.default_user_role,
            save_now=True,
        )
        wapi.execute_update_workspace_actions(request.current_workspace)
        return wapi.get_workspace_with_context(request.current_workspace)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(DisallowedWorkspaceAccessType,
                            HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UserNotAllowedToCreateMoreWorkspace,
                            HTTPStatus.BAD_REQUEST)
    @check_right(is_trusted_user)
    @hapic.input_body(WorkspaceCreationSchema())
    @hapic.output_body(WorkspaceSchema())
    def create_workspace(self,
                         context,
                         request: TracimRequest,
                         hapic_data=None):
        """
        Create a workspace. This route is for trusted users and administrators.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        parent = None
        if hapic_data.body.parent_id:
            parent = wapi.get_one(workspace_id=hapic_data.body.parent_id)
        workspace = wapi.create_workspace(
            label=hapic_data.body.label,
            description=hapic_data.body.description,
            access_type=hapic_data.body.access_type,
            save_now=True,
            agenda_enabled=hapic_data.body.agenda_enabled,
            public_download_enabled=hapic_data.body.public_download_enabled,
            public_upload_enabled=hapic_data.body.public_upload_enabled,
            default_user_role=hapic_data.body.default_user_role,
            parent=parent,
        )
        wapi.execute_created_workspace_actions(workspace)
        return wapi.get_workspace_with_context(workspace)

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__WORKSPACE_TRASH_AND_RESTORE_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @check_right(can_delete_workspace)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def delete_workspace(self,
                         context,
                         request: TracimRequest,
                         hapic_data=None):
        """
        Delete a workspace. This route is for trusted users and administrators.
        Note : a trusted user can only delete spaces on which he/she is space manager
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config  # User
        )
        wapi.delete(request.current_workspace, flush=True)
        return

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__WORKSPACE_TRASH_AND_RESTORE_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @check_right(can_delete_workspace)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def undelete_workspace(self,
                           context,
                           request: TracimRequest,
                           hapic_data=None):
        """
        Restore a deleted space.
        Note : a trusted user can only restore spaces on which he/she is space manager
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        wapi = WorkspaceApi(
            current_user=request.current_user,  # User
            session=request.dbsession,
            config=app_config,
            show_deleted=True,
        )
        wapi.undelete(request.current_workspace, flush=True)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS])
    @check_right(can_see_workspace_information)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.input_query(WorkspaceMemberFilterQuerySchema())
    @hapic.output_body(WorkspaceMemberSchema(many=True))
    def workspaces_members(
            self,
            context,
            request: TracimRequest,
            hapic_data=None) -> typing.List[UserRoleWorkspaceInContext]:
        """
        Returns the list of space members with their role, avatar, etc.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        rapi = RoleApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
            show_disabled_user=hapic_data.query.show_disabled_user,
        )

        roles = rapi.get_all_for_workspace(workspace=request.current_workspace)
        return [
            rapi.get_user_role_workspace_with_context(user_role)
            for user_role in roles
        ]

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS])
    @check_right(can_see_workspace_information)
    @hapic.input_path(WorkspaceAndUserIdPathSchema())
    @hapic.output_body(WorkspaceMemberSchema())
    def workspaces_member_role(self,
                               context,
                               request: TracimRequest,
                               hapic_data=None) -> UserRoleWorkspaceInContext:
        """
        Returns given space member with its role, avatar, etc.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        rapi = RoleApi(current_user=request.current_user,
                       session=request.dbsession,
                       config=app_config)

        role = rapi.get_one(user_id=hapic_data.path.user_id,
                            workspace_id=hapic_data.path.workspace_id)
        return rapi.get_user_role_workspace_with_context(role)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS])
    @hapic.handle_exception(UserRoleNotFound, HTTPStatus.BAD_REQUEST)
    @check_right(can_modify_workspace)
    @hapic.handle_exception(LastWorkspaceManagerRoleCantBeModified,
                            HTTPStatus.BAD_REQUEST)
    @hapic.input_path(WorkspaceAndUserIdPathSchema())
    @hapic.input_body(RoleUpdateSchema())
    @hapic.output_body(WorkspaceMemberSchema())
    def update_workspaces_members_role(
            self,
            context,
            request: TracimRequest,
            hapic_data=None) -> UserRoleWorkspaceInContext:
        """
        Update role of the given space member.
        This feature is for workspace managers, trusted users and administrators.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        rapi = RoleApi(current_user=request.current_user,
                       session=request.dbsession,
                       config=app_config)

        role = rapi.get_one(user_id=hapic_data.path.user_id,
                            workspace_id=hapic_data.path.workspace_id)
        role = rapi.update_role(role, role_level=hapic_data.body.role.level)
        return rapi.get_user_role_workspace_with_context(role)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS])
    @check_right(can_leave_workspace)
    @hapic.handle_exception(UserRoleNotFound, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(LastWorkspaceManagerRoleCantBeModified,
                            HTTPStatus.BAD_REQUEST)
    @hapic.input_path(WorkspaceAndUserIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def delete_workspaces_members_role(self,
                                       context,
                                       request: TracimRequest,
                                       hapic_data=None) -> None:
        """
        Remove the user from the space.
        This feature is for workspace managers and administrators.
        """

        app_config = request.registry.settings["CFG"]  # type: CFG
        rapi = RoleApi(current_user=request.current_user,
                       session=request.dbsession,
                       config=app_config)
        rapi.delete_one(user_id=hapic_data.path.user_id,
                        workspace_id=hapic_data.path.workspace_id)
        return

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS])
    @hapic.handle_exception(EmailValidationFailed, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UserIsNotActive, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UserIsDeleted, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(RoleAlreadyExistError, HTTPStatus.BAD_REQUEST)
    @check_right(can_modify_workspace)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.input_body(WorkspaceMemberInviteSchema())
    @hapic.output_body(WorkspaceMemberCreationSchema())
    def create_workspaces_members_role(
            self,
            context,
            request: TracimRequest,
            hapic_data=None) -> UserRoleWorkspaceInContext:
        """
        Add a member to this workspace.
        This feature is for workspace managers and administrators.
        """
        newly_created = False
        email_sent = False
        app_config = request.registry.settings["CFG"]  # type: CFG
        rapi = RoleApi(current_user=request.current_user,
                       session=request.dbsession,
                       config=app_config)
        uapi = UserApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
            show_deactivated=True,
            show_deleted=True,
        )
        try:
            if hapic_data.body.user_id:
                user = uapi.get_one(hapic_data.body.user_id)
            elif hapic_data.body.user_email:
                user = uapi.get_one_by_email(hapic_data.body.user_email)
            else:
                user = uapi.get_one_by_username(hapic_data.body.user_username)
            if user.is_deleted:
                raise UserIsDeleted(
                    "This user has been deleted. Unable to invite him.")
            if not user.is_active:
                raise UserIsNotActive(
                    "This user is not activated. Unable to invite him")
        except UserDoesNotExist as exc:
            if not uapi.allowed_to_invite_new_user(hapic_data.body.user_email):
                raise exc

            if app_config.NEW_USER__INVITATION__DO_NOTIFY:
                user = uapi.create_user(
                    auth_type=AuthType.UNKNOWN,
                    email=hapic_data.body.user_email,
                    password=password_generator(),
                    do_notify=True,
                )
                if (app_config.EMAIL__NOTIFICATION__ACTIVATED
                        and app_config.NEW_USER__INVITATION__DO_NOTIFY
                        and app_config.JOBS__PROCESSING_MODE
                        == app_config.CST.SYNC):
                    email_sent = True
            else:
                user = uapi.create_user(
                    auth_type=AuthType.UNKNOWN,
                    email=hapic_data.body.user_email,
                    password=None,
                    do_notify=False,
                )
            uapi.execute_created_user_actions(user)
            newly_created = True

        role = rapi.create_one(
            user=user,
            workspace=request.current_workspace,
            role_level=WorkspaceRoles.get_role_from_slug(
                hapic_data.body.role).level,
            with_notif=app_config.EMAIL__NOTIFICATION__ENABLED_ON_INVITATION,
            flush=True,
        )
        return rapi.get_user_role_workspace_with_context(
            role, newly_created=newly_created, email_sent=email_sent)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_SUBSCRIPTION_ENDPOINTS])
    @check_right(can_modify_workspace)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.output_body(WorkspaceSubscriptionSchema(many=True))
    def workspace_subscriptions(
            self,
            context,
            request: TracimRequest,
            hapic_data=None) -> typing.List[WorkspaceSubscription]:
        subscription_lib = SubscriptionLib(
            current_user=request.current_user,
            session=request.dbsession,
            config=request.app_config,
        )
        return subscription_lib.get_workspace_subscriptions(
            request.current_workspace.workspace_id)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_SUBSCRIPTION_ENDPOINTS])
    @check_right(can_modify_workspace)
    @hapic.handle_exception(RoleAlreadyExistError, HTTPStatus.BAD_REQUEST)
    @hapic.input_path(WorkspaceAndUserIdPathSchema())
    @hapic.input_body(RoleUpdateSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def accept_subscription(
            self,
            context,
            request: TracimRequest,
            hapic_data=None) -> typing.List[WorkspaceSubscription]:
        subscription_lib = SubscriptionLib(
            current_user=request.current_user,
            session=request.dbsession,
            config=request.app_config,
        )
        subscription = subscription_lib.get_one(
            author_id=hapic_data.path.user_id,
            workspace_id=hapic_data.path.workspace_id)
        subscription_lib.accept_subscription(subscription=subscription,
                                             user_role=hapic_data.body.role)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_SUBSCRIPTION_ENDPOINTS])
    @check_right(can_modify_workspace)
    @hapic.input_path(WorkspaceAndUserIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def reject_subscription(
            self,
            context,
            request: TracimRequest,
            hapic_data=None) -> typing.List[WorkspaceSubscription]:
        subscription_lib = SubscriptionLib(
            current_user=request.current_user,
            session=request.dbsession,
            config=request.app_config,
        )
        subscription = subscription_lib.get_one(
            author_id=hapic_data.path.user_id,
            workspace_id=hapic_data.path.workspace_id)
        subscription_lib.reject_subscription(subscription=subscription)

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS])
    @check_right(is_reader)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.input_query(FilterContentQuerySchema())
    @hapic.output_body(ContentDigestSchema(many=True))
    def workspace_content(self,
                          context,
                          request: TracimRequest,
                          hapic_data=None) -> typing.List[ContentInContext]:
        """
        return a list of contents of the space.
        This is NOT the full content list: by default, returned contents are the ones at root level.
        In order to get contents in a given folder, then use parent_id query filter.
        You can also show.hide archived/deleted contents.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        content_filter = hapic_data.query
        api = ContentApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
            namespaces_filter=content_filter.namespaces_filter
            or [ContentNamespaces.CONTENT],
            show_archived=content_filter.show_archived,
            show_deleted=content_filter.show_deleted,
            show_active=content_filter.show_active,
        )
        contents = api.get_all(
            parent_ids=content_filter.parent_ids,
            complete_path_to_id=content_filter.complete_path_to_id,
            workspace=request.current_workspace,
            content_type=content_filter.content_type
            or content_type_list.Any_SLUG,
            label=content_filter.label,
            order_by_properties=[Content.label],
        )
        contents = [
            api.get_content_in_context(content) for content in contents
        ]
        return contents

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS])
    @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UnallowedSubContent, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder,
                            HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ParentNotFound, HTTPStatus.BAD_REQUEST)
    @check_right(can_create_content)
    @hapic.input_path(WorkspaceIdPathSchema())
    @hapic.input_body(ContentCreationSchema())
    @hapic.output_body(ContentDigestSchema())
    def create_generic_empty_content(self,
                                     context,
                                     request: TracimRequest,
                                     hapic_data=None) -> ContentInContext:
        """
        Creates a generic empty content. The minimum viable content has a label and a content type.
        Creating a content generally starts with a request to this endpoint.
        For specific contents like files, it is recommended to use the dedicated endpoint.
        This feature is accessible to contributors and higher role only.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        creation_data = hapic_data.body
        api = ContentApi(current_user=request.current_user,
                         session=request.dbsession,
                         config=app_config)
        parent = None
        if creation_data.parent_id:
            try:
                parent = api.get_one(content_id=creation_data.parent_id,
                                     content_type=content_type_list.Any_SLUG)
            except ContentNotFound as exc:
                raise ParentNotFound(
                    "Parent with content_id {} not found".format(
                        creation_data.parent_id)) from exc
        content = api.create(
            label=creation_data.label,
            content_type_slug=creation_data.content_type,
            workspace=request.current_workspace,
            parent=parent,
        )
        api.save(content, ActionDescription.CREATION)
        api.execute_created_content_actions(content)
        content = api.get_content_in_context(content)
        return content

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS])
    @check_right(is_reader)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.FOUND)
    def get_content_from_workspace(self,
                                   context,
                                   request: TracimRequest,
                                   hapic_data=None) -> None:
        """
        Convenient route allowing to get detail about a content without to known routes associated to its content type.
        This route generate a HTTP 302 with the right url
        """
        content = request.current_content
        content_type = content_type_list.get_one_by_slug(content.type).slug
        # TODO - G.M - 2018-08-03 - Jsonify redirect response ?
        raise HTTPFound(
            "{base_url}workspaces/{workspace_id}/{content_type}s/{content_id}".
            format(
                base_url=BASE_API,
                workspace_id=content.workspace_id,
                content_type=content_type,
                content_id=content.content_id,
            ))

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS])
    @hapic.input_path(ContentIdPathSchema())
    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.FOUND)
    def get_content(self,
                    context,
                    request: TracimRequest,
                    hapic_data=None) -> None:
        """
        Convenient route allowing to get detail about a content without to known routes associated to its content type.
        This route generate a HTTP 302 with the right url
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(current_user=request.current_user,
                         session=request.dbsession,
                         config=app_config)
        content = api.get_one(content_id=hapic_data.path["content_id"],
                              content_type=content_type_list.Any_SLUG)
        content_type = content_type_list.get_one_by_slug(content.type).slug
        # TODO - G.M - 2018-08-03 - Jsonify redirect response ?
        raise HTTPFound(
            "{base_url}workspaces/{workspace_id}/{content_type}s/{content_id}".
            format(
                base_url=BASE_API,
                workspace_id=content.workspace_id,
                content_type=content_type,
                content_id=content.content_id,
            ))

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS])
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(ContentPathInfoSchema())
    def get_content_path(self,
                         context,
                         request: TracimRequest,
                         hapic_data=None) -> None:
        """
        Get Content Path : return all hierarchy of content from workspace root to content
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        api = ContentApi(current_user=request.current_user,
                         session=request.dbsession,
                         config=app_config)
        content = api.get_one(
            content_id=hapic_data.path.content_id,
            content_type=content_type_list.Any_SLUG,
            workspace=request.current_workspace,
        )
        return ListItemsObject([
            api.get_content_in_context(path_content)
            for path_content in content.content_path
        ])

    @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS])
    @hapic.handle_exception(WorkspacesDoNotMatch, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder,
                            HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(UnallowedSubContent, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ConflictingMoveInItself, HTTPStatus.BAD_REQUEST)
    @hapic.handle_exception(ConflictingMoveInChild, HTTPStatus.BAD_REQUEST)
    @check_right(can_move_content)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.input_body(ContentMoveSchema())
    @hapic.output_body(ContentDigestSchema())
    def move_content(self,
                     context,
                     request: TracimRequest,
                     hapic_data=None) -> ContentInContext:
        """
        Move a content to specified new place.
        This requires to be content manager in both input and output spaces (which may be the same)
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        path_data = hapic_data.path
        move_data = hapic_data.body

        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(path_data.content_id,
                              content_type=content_type_list.Any_SLUG)
        new_parent = api.get_one(move_data.new_parent_id,
                                 content_type=content_type_list.Any_SLUG)

        new_workspace = request.candidate_workspace

        with new_revision(session=request.dbsession,
                          tm=transaction.manager,
                          content=content):
            api.move(
                content,
                new_parent=new_parent,
                new_workspace=new_workspace,
                new_content_namespace=ContentNamespaces.CONTENT,
                must_stay_in_same_workspace=False,
            )
            api.execute_update_content_actions(content)
        updated_content = api.get_one(path_data.content_id,
                                      content_type=content_type_list.Any_SLUG)
        return api.get_content_in_context(updated_content)

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__CONTENT_ALL_TRASH_AND_RESTORE_ENDPOINTS])
    @check_right(is_content_manager)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def delete_content(self,
                       context,
                       request: TracimRequest,
                       hapic_data=None) -> None:
        """
        Move a content to the trash. After that, the content will be invisible by default.
        This action requires the user to be a content manager.
        Note: the content is still accessible but becomes read-only.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        path_data = hapic_data.path
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(path_data.content_id,
                              content_type=content_type_list.Any_SLUG)
        with new_revision(session=request.dbsession,
                          tm=transaction.manager,
                          content=content):
            api.delete(content)
            api.execute_update_content_actions(content)
        return

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__CONTENT_ALL_TRASH_AND_RESTORE_ENDPOINTS])
    @check_right(is_content_manager)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def undelete_content(self,
                         context,
                         request: TracimRequest,
                         hapic_data=None) -> None:
        """
        Restore a content from the trash. The content will be visible and editable again.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        path_data = hapic_data.path
        api = ContentApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
            show_deleted=True,
            show_archived=True,
        )
        content = api.get_one(path_data.content_id,
                              content_type=content_type_list.Any_SLUG)
        with new_revision(session=request.dbsession,
                          tm=transaction.manager,
                          content=content):
            api.undelete(content)
            api.execute_update_content_actions(content)
        return

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__CONTENT_ALL_ARCHIVE_AND_RESTORE_ENDPOINTS])
    @check_right(is_content_manager)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def archive_content(self,
                        context,
                        request: TracimRequest,
                        hapic_data=None) -> None:
        """
        Archives a content. The content will be invisible but still available.
        Difference with delete is that optimizing workspace will not delete archived contents
        This action requires the user to be a content manager.
        Note: the content is still accessible but becomes read-only.
        the difference with delete is that optimizing workspace will not delete archived contents
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        path_data = hapic_data.path
        api = ContentApi(
            show_archived=True,
            show_deleted=True,
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
        )
        content = api.get_one(path_data.content_id,
                              content_type=content_type_list.Any_SLUG)
        with new_revision(session=request.dbsession,
                          tm=transaction.manager,
                          content=content):
            api.archive(content)
            api.execute_update_content_actions(content)
        return

    @hapic.with_api_doc(
        tags=[SWAGGER_TAG__CONTENT_ALL_ARCHIVE_AND_RESTORE_ENDPOINTS])
    @check_right(is_content_manager)
    @hapic.input_path(WorkspaceAndContentIdPathSchema())
    @hapic.output_body(NoContentSchema(),
                       default_http_code=HTTPStatus.NO_CONTENT)
    def unarchive_content(self,
                          context,
                          request: TracimRequest,
                          hapic_data=None) -> None:
        """
        Restore a content from archive. The content will be visible and editable again.
        """
        app_config = request.registry.settings["CFG"]  # type: CFG
        path_data = hapic_data.path
        api = ContentApi(
            current_user=request.current_user,
            session=request.dbsession,
            config=app_config,
            show_archived=True,
            show_deleted=True,
        )
        content = api.get_one(path_data.content_id,
                              content_type=content_type_list.Any_SLUG)
        with new_revision(session=request.dbsession,
                          tm=transaction.manager,
                          content=content):
            api.unarchive(content)
            api.execute_update_content_actions(content)
        return

    def bind(self, configurator: Configurator) -> None:
        """
        Create all routes and views using
        pyramid configurator for this controller
        """

        # Workspaces
        configurator.add_route("workspaces",
                               "/workspaces",
                               request_method="GET")
        configurator.add_view(self.workspaces, route_name="workspaces")
        # Workspace
        configurator.add_route("workspace",
                               "/workspaces/{workspace_id}",
                               request_method="GET")
        configurator.add_view(self.workspace, route_name="workspace")
        # Workspace space
        configurator.add_route("workspace_disk_space",
                               "/workspaces/{workspace_id}/disk_space",
                               request_method="GET")
        configurator.add_view(self.workspace_disk_space,
                              route_name="workspace_disk_space")
        # Create workspace
        configurator.add_route("create_workspace",
                               "/workspaces",
                               request_method="POST")
        configurator.add_view(self.create_workspace,
                              route_name="create_workspace")
        # Delete/Undelete workpace
        configurator.add_route("delete_workspace",
                               "/workspaces/{workspace_id}/trashed",
                               request_method="PUT")
        configurator.add_view(self.delete_workspace,
                              route_name="delete_workspace")
        configurator.add_route("undelete_workspace",
                               "/workspaces/{workspace_id}/trashed/restore",
                               request_method="PUT")
        configurator.add_view(self.undelete_workspace,
                              route_name="undelete_workspace")
        # Update Workspace
        configurator.add_route("update_workspace",
                               "/workspaces/{workspace_id}",
                               request_method="PUT")
        configurator.add_view(self.update_workspace,
                              route_name="update_workspace")
        # Workspace Members (Roles)
        configurator.add_route("workspace_members",
                               "/workspaces/{workspace_id}/members",
                               request_method="GET")
        configurator.add_view(self.workspaces_members,
                              route_name="workspace_members")
        # Workspace Members (Role) Individual
        configurator.add_route(
            "workspace_member_role",
            "/workspaces/{workspace_id}/members/{user_id}",
            request_method="GET",
        )
        configurator.add_view(self.workspaces_member_role,
                              route_name="workspace_member_role")
        # Update Workspace Members roles
        configurator.add_route(
            "update_workspace_member",
            "/workspaces/{workspace_id}/members/{user_id}",
            request_method="PUT",
        )
        configurator.add_view(self.update_workspaces_members_role,
                              route_name="update_workspace_member")
        # Create Workspace Members roles
        configurator.add_route("create_workspace_member",
                               "/workspaces/{workspace_id}/members",
                               request_method="POST")
        configurator.add_view(self.create_workspaces_members_role,
                              route_name="create_workspace_member")
        # Delete Workspace Members roles
        configurator.add_route(
            "delete_workspace_member",
            "/workspaces/{workspace_id}/members/{user_id}",
            request_method="DELETE",
        )
        configurator.add_view(self.delete_workspaces_members_role,
                              route_name="delete_workspace_member")
        # Workspace Content
        configurator.add_route("workspace_content",
                               "/workspaces/{workspace_id}/contents",
                               request_method="GET")
        configurator.add_view(self.workspace_content,
                              route_name="workspace_content")
        # Create Generic Content
        configurator.add_route("create_generic_content",
                               "/workspaces/{workspace_id}/contents",
                               request_method="POST")
        configurator.add_view(self.create_generic_empty_content,
                              route_name="create_generic_content")
        # Get Content
        configurator.add_route("get_content",
                               "/contents/{content_id}",
                               request_method="GET")
        configurator.add_view(self.get_content, route_name="get_content")
        # Get Content From workspace
        configurator.add_route(
            "get_content_from_workspace",
            "/workspaces/{workspace_id}/contents/{content_id}",
            request_method="GET",
        )
        configurator.add_view(self.get_content_from_workspace,
                              route_name="get_content_from_workspace")
        # Move Content
        configurator.add_route(
            "move_content",
            "/workspaces/{workspace_id}/contents/{content_id}/move",
            request_method="PUT",
        )
        configurator.add_view(self.move_content, route_name="move_content")
        # Delete/Undelete Content
        configurator.add_route(
            "delete_content",
            "/workspaces/{workspace_id}/contents/{content_id}/trashed",
            request_method="PUT",
        )
        configurator.add_view(self.delete_content, route_name="delete_content")
        configurator.add_route(
            "undelete_content",
            "/workspaces/{workspace_id}/contents/{content_id}/trashed/restore",
            request_method="PUT",
        )
        configurator.add_view(self.undelete_content,
                              route_name="undelete_content")
        # # Archive/Unarchive Content
        configurator.add_route(
            "archive_content",
            "/workspaces/{workspace_id}/contents/{content_id}/archived",
            request_method="PUT",
        )
        configurator.add_view(self.archive_content,
                              route_name="archive_content")
        configurator.add_route(
            "unarchive_content",
            "/workspaces/{workspace_id}/contents/{content_id}/archived/restore",
            request_method="PUT",
        )
        configurator.add_view(self.unarchive_content,
                              route_name="unarchive_content")

        # Subscriptions
        configurator.add_route(
            "workspace_subscriptions",
            "/workspaces/{workspace_id}/subscriptions",
            request_method="GET",
        )
        configurator.add_view(self.workspace_subscriptions,
                              route_name="workspace_subscriptions")
        configurator.add_route(
            "accept_subscription",
            "/workspaces/{workspace_id}/subscriptions/{user_id}/accept",
            request_method="PUT",
        )
        configurator.add_view(self.accept_subscription,
                              route_name="accept_subscription")

        configurator.add_route(
            "reject_subscription",
            "/workspaces/{workspace_id}/subscriptions/{user_id}/reject",
            request_method="PUT",
        )
        configurator.add_view(self.reject_subscription,
                              route_name="reject_subscription")
        # Content path
        configurator.add_route(
            "get_content_path",
            "/workspaces/{workspace_id}/contents/{content_id}/path",
            request_method="GET",
        )
        configurator.add_view(self.get_content_path,
                              route_name="get_content_path")