コード例 #1
0
ファイル: controller.py プロジェクト: grignards/tracim
 def create_file(self, context, request: TracimRequest, hapic_data=None):
     """
     Create a file .This will create 2 new revision.
     """
     # INFO - G.M - 2019-09-03 - check validation of file here, because marshmallow
     # required doesn't work correctly with cgi_fieldstorage.
     # check is done with None because cgi_fieldstorage cannot be converted to bool
     if hapic_data.files.files is None:
         raise NoFileValidationError(
             'No file "files" given at input, validation failed.')
     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,
     )
     api.check_upload_size(request.content_length,
                           request.current_workspace)
     _file = hapic_data.files.files
     parent_id = hapic_data.forms.parent_id
     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,
         )
     api.execute_created_content_actions(content)
     return api.get_content_in_context(content)
コード例 #2
0
ファイル: controller.py プロジェクト: grignards/tracim
    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.
        """
        # INFO - G.M - 2019-09-03 - check validation of file here, because marshmallow
        # required doesn't work correctly with cgi_fieldstorage.
        # check is done with None because cgi_fieldstorage cannot be converted to bool
        if hapic_data.files.files is None:
            raise NoFileValidationError(
                'No file "files" given at input, validation failed.')
        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)
        api.check_upload_size(request.content_length, content.workspace)
        _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)
        api.execute_update_content_actions(content)
        return
コード例 #3
0
ファイル: resources.py プロジェクト: inkhey/tracim
class FileResource(DAVNonCollection):
    """
    FileResource resource corresponding to tracim's files
    """
    def __init__(self, path: str, environ: dict, content: Content,
                 tracim_context: "WebdavTracimContext") -> None:
        super(FileResource, self).__init__(path, environ)
        self.tracim_context = tracim_context
        self.content = content
        self.user = tracim_context.current_user
        self.session = tracim_context.dbsession
        self.content_api = ContentApi(
            current_user=self.user,
            config=tracim_context.app_config,
            session=self.session,
            namespaces_filter=[self.content.content_namespace],
        )

        # this is the property that windows client except to check if the file is read-write or read-only,
        # but i wasn't able to set this property so you'll have to look into it >.>
        # self.setPropertyValue('Win32FileAttributes', '00000021')

    def __repr__(self) -> str:
        return "<DAVNonCollection: FileResource (%d)>" % self.content.cached_revision_id

    @webdav_check_right(is_reader)
    def getContentLength(self) -> int:
        return self.content.depot_file.file.content_length

    @webdav_check_right(is_reader)
    def getContentType(self) -> str:
        return self.content.file_mimetype

    @webdav_check_right(is_reader)
    def getCreationDate(self) -> float:
        return mktime(self.content.created.timetuple())

    @webdav_check_right(is_reader)
    def getDisplayName(self) -> str:
        return webdav_convert_file_name_to_display(self.content.file_name)

    @webdav_check_right(is_reader)
    def getDisplayInfo(self):
        return {"type": self.content.type.capitalize()}

    def getLastModified(self) -> float:
        return mktime(self.content.updated.timetuple())

    @webdav_check_right(is_reader)
    def getContent(self) -> typing.BinaryIO:
        return self.content.depot_file.file

    @webdav_check_right(is_contributor)
    def beginWrite(self, contentType: str = None) -> FakeFileStream:
        try:
            self.content_api.check_upload_size(
                int(self.environ["CONTENT_LENGTH"]), self.content.workspace)
        except (
                FileSizeOverMaxLimitation,
                FileSizeOverWorkspaceEmptySpace,
                FileSizeOverOwnerEmptySpace,
        ) as exc:
            raise DAVError(HTTP_REQUEST_ENTITY_TOO_LARGE, contextinfo=str(exc))
        return FakeFileStream(
            content=self.content,
            content_api=self.content_api,
            file_name=self.content.file_name,
            workspace=self.content.workspace,
            path=self.path,
            session=self.session,
        )

    def moveRecursive(self, destpath):
        """As we support recursive move, copymovesingle won't be called, though with copy it'll be called
            but i have to check if the client ever call that function..."""
        destpath = normpath(destpath)
        self.tracim_context.set_destpath(destpath)
        if normpath(dirname(destpath)) == normpath(dirname(self.path)):
            # INFO - G.M - 2018-12-12 - renaming case
            checker = is_contributor
        else:
            # INFO - G.M - 2018-12-12 - move case
            checker = can_move_content

        try:
            checker.check(self.tracim_context)
        except TracimException as exc:
            raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc))

        invalid_path = False
        invalid_path = dirname(
            destpath) == self.environ["http_authenticator.realm"]

        if not invalid_path:
            self.move_file(destpath)

        if invalid_path:
            raise DAVError(HTTP_FORBIDDEN)

    def move_file(self, destpath: str) -> None:
        """
        Move file mean changing the path to access to a file. This can mean
        simple renaming(1), moving file from a directory to one another(2)
        but also renaming + moving file from a directory to one another at
        the same time (3).

        (1): move /dir1/file1 -> /dir1/file2
        (2): move /dir1/file1 -> /dir2/file1
        (3): move /dir1/file1 -> /dir2/file2
        :param destpath: destination path of webdav move
        :return: nothing
        """

        workspace = self.content.workspace
        parent = self.content.parent
        destpath = normpath(destpath)
        self.tracim_context.set_destpath(destpath)
        if normpath(dirname(destpath)) == normpath(dirname(self.path)):
            # INFO - G.M - 2018-12-12 - renaming case
            checker = is_contributor
        else:
            # INFO - G.M - 2018-12-12 - move case
            checker = can_move_content

        try:
            checker.check(self.tracim_context)
        except TracimException as exc:
            raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc))

        try:
            with new_revision(content=self.content,
                              tm=transaction.manager,
                              session=self.session):
                # INFO - G.M - 2018-03-09 - First, renaming file if needed
                if basename(destpath) != self.getDisplayName():

                    new_filename = webdav_convert_file_name_to_bdd(
                        basename(destpath))
                    regex_file_extension = re.compile("(?P<label>.*){}".format(
                        re.escape(self.content.file_extension)))
                    same_extension = regex_file_extension.match(new_filename)
                    if same_extension:
                        new_label = same_extension.group("label")
                        new_file_extension = self.content.file_extension
                    else:
                        new_label, new_file_extension = os.path.splitext(
                            new_filename)

                    self.content_api.update_content(self.content,
                                                    new_label=new_label)
                    self.content.file_extension = new_file_extension
                    self.content_api.save(self.content)

                # INFO - G.M - 2018-03-09 - Moving file if needed
                destination_workspace = self.tracim_context.candidate_workspace
                try:
                    destination_parent = self.tracim_context.candidate_parent_content
                except ContentNotFound:
                    destination_parent = None
                if destination_parent != parent or destination_workspace != workspace:
                    #  INFO - G.M - 12-03-2018 - Avoid moving the file "at the same place"
                    #  if the request does not result in a real move.
                    self.content_api.move(
                        item=self.content,
                        new_parent=destination_parent,
                        must_stay_in_same_workspace=False,
                        new_workspace=destination_workspace,
                    )
                self.content_api.execute_update_content_actions(self.content)
        except TracimException as exc:
            raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) from exc

        transaction.commit()

    def copyMoveSingle(self, destpath, isMove):
        if isMove:
            # INFO - G.M - 12-03-2018 - This case should not happen
            # As far as moveRecursive method exist, all move should not go
            # through this method. If such case appear, try replace this to :
            ####
            # self.move_file(destpath)
            # return
            ####
            raise NotImplementedError("Feature not available")

        destpath = normpath(destpath)
        self.tracim_context.set_destpath(destpath)
        try:
            can_move_content.check(self.tracim_context)
        except TracimException as exc:
            raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc))

        content_in_context = self.content_api.get_content_in_context(
            self.content)
        try:
            self.content_api.check_upload_size(content_in_context.size or 0,
                                               self.content.workspace)
        except (
                FileSizeOverMaxLimitation,
                FileSizeOverWorkspaceEmptySpace,
                FileSizeOverOwnerEmptySpace,
        ) as exc:
            raise DAVError(HTTP_REQUEST_ENTITY_TOO_LARGE, contextinfo=str(exc))
        new_filename = webdav_convert_file_name_to_bdd(basename(destpath))
        regex_file_extension = re.compile("(?P<label>.*){}".format(
            re.escape(self.content.file_extension)))
        same_extension = regex_file_extension.match(new_filename)
        if same_extension:
            new_label = same_extension.group("label")
            new_file_extension = self.content.file_extension
        else:
            new_label, new_file_extension = os.path.splitext(new_filename)

        self.tracim_context.set_destpath(destpath)
        destination_workspace = self.tracim_context.candidate_workspace
        try:
            destination_parent = self.tracim_context.candidate_parent_content
        except ContentNotFound:
            destination_parent = None
        try:
            new_content = self.content_api.copy(
                item=self.content,
                new_label=new_label,
                new_file_extension=new_file_extension,
                new_parent=destination_parent,
                new_workspace=destination_workspace,
            )
            self.content_api.execute_created_content_actions(new_content)
        except TracimException as exc:
            raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) from exc
        transaction.commit()

    def supportRecursiveMove(self, destpath):
        return True

    @webdav_check_right(is_content_manager)
    def delete(self):
        try:
            with new_revision(session=self.session,
                              tm=transaction.manager,
                              content=self.content):
                self.content_api.delete(self.content)
                self.content_api.execute_update_content_actions(self.content)
                self.content_api.save(self.content)
        except TracimException as exc:
            raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) from exc
        transaction.commit()
コード例 #4
0
ファイル: resources.py プロジェクト: inkhey/tracim
class ContentOnlyContainer(WebdavContainer):
    """
    Container that can get children content
    """
    def __init__(
        self,
        path: str,
        environ: dict,
        label: str,
        content: Content,
        provider: "TracimDavProvider",
        workspace: Workspace,
        tracim_context: "WebdavTracimContext",
    ) -> None:
        """
        Some rules:
        - if content given is None, return workspace root contents
        - if the given content is correct, return the subcontent of this content
         and user-known workspaces without any user-known parent to the list.
         - in case of content collision, only the first named content (sorted by content_id
         from lower to higher) will be returned.
        """
        self.path = path
        self.environ = environ
        self.workspace = workspace
        self.content = content
        self.tracim_context = tracim_context
        self.user = tracim_context.current_user
        self.session = tracim_context.dbsession
        self.label = label
        self.provider = provider
        self.content_api = ContentApi(
            current_user=self.user,
            session=tracim_context.dbsession,
            config=tracim_context.app_config,
            show_temporary=True,
            namespaces_filter=[ContentNamespaces.CONTENT],
        )

    # Internal methods
    def _get_members(
        self,
        already_existing_names: typing.Optional[typing.List[str]] = None
    ) -> typing.List[Content]:
        members_names = []
        members = []
        if self.content:
            parent_id = self.content.content_id
            children = self.content_api.get_all(
                content_type=content_type_list.Any_SLUG,
                workspace=self.workspace,
                parent_ids=[parent_id],
                order_by_properties=["content_id"],
            )
        else:
            children = self.content_api.get_all(
                content_type=content_type_list.Any_SLUG,
                workspace=self.workspace,
                parent_ids=[0],
                order_by_properties=["content_id"],
            )
        for child in children:
            if child.file_name in members_names:
                continue
            else:
                members_names.append(child.file_name)
                members.append(child)
        return members

    def _generate_child_content_resource(
            self, parent_path: str, child_content: Content) -> _DAVResource:
        content_path = "%s/%s" % (
            self.path,
            webdav_convert_file_name_to_display(child_content.file_name),
        )
        return get_content_resource(
            path=content_path,
            environ=self.environ,
            workspace=self.workspace,
            content=child_content,
            tracim_context=self.tracim_context,
        )

    # Container methods
    def createEmptyResource(self, file_name: str):
        """
        Create a new file on the current workspace/folder.
        """
        content = None
        fixed_file_name = webdav_convert_file_name_to_display(file_name)
        path = os.path.join(self.path, file_name)
        resource = self.provider.getResourceInst(path, self.environ)
        if resource:
            content = resource.content
        try:
            self.content_api.check_upload_size(
                int(self.environ["CONTENT_LENGTH"]), self.workspace)
        except (
                FileSizeOverMaxLimitation,
                FileSizeOverWorkspaceEmptySpace,
                FileSizeOverOwnerEmptySpace,
        ) as exc:
            raise DAVError(HTTP_REQUEST_ENTITY_TOO_LARGE, contextinfo=str(exc))
        # return item
        return FakeFileStream(
            session=self.session,
            file_name=fixed_file_name,
            content_api=self.content_api,
            workspace=self.workspace,
            content=content,
            parent=self.content,
            path=self.path + "/" + fixed_file_name,
        )

    def createCollection(self, label: str) -> "FolderResource":
        """
        Create a new folder for the current workspace/folder. As it's not possible for the user to choose
        which types of content are allowed in this folder, we allow allow all of them.

        This method return the DAVCollection created.
        """
        folder_label = webdav_convert_file_name_to_bdd(label)
        try:
            folder = self.content_api.create(
                content_type_slug=content_type_list.Folder.slug,
                workspace=self.workspace,
                label=folder_label,
                parent=self.content,
            )
            self.content_api.execute_created_content_actions(folder)
        except TracimException as exc:
            raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) from exc

        self.content_api.save(folder)

        transaction.commit()
        # fixed_path
        folder_path = "%s/%s" % (self.path,
                                 webdav_convert_file_name_to_display(label))
        # return item
        return FolderResource(
            folder_path,
            self.environ,
            content=folder,
            tracim_context=self.tracim_context,
            workspace=self.workspace,
        )

    def getMemberNames(self) -> [str]:
        """
        Access to the list of content names for current workspace/folder
        """
        # INFO - G.M - 2020-14-10 - Unclear if this method is really used by wsgidav
        retlist = []
        for content in self._get_members():
            retlist.append(
                webdav_convert_file_name_to_display(content.file_name))
        return retlist

    def getMember(self, label: str) -> _DAVResource:
        """
        Access to a specific members
        """
        return self.provider.getResourceInst(
            "%s/%s" % (self.path, webdav_convert_file_name_to_display(label)),
            self.environ)

    def getMemberList(self) -> [_DAVResource]:
        """
        Access to the list of content of current workspace/folder
        """
        members = []
        for content in self._get_members():
            members.append(
                self._generate_child_content_resource(parent_path=self.path,
                                                      child_content=content))
        return members
コード例 #5
0
class WorkspaceResource(DAVCollection):
    """
    Workspace resource corresponding to tracim's workspaces.
    Direct children can only be folders, though files might come later on and are supported
    """
    def __init__(
        self,
        label: str,
        path: str,
        environ: dict,
        workspace: Workspace,
        tracim_context: "WebdavTracimContext",
    ) -> None:
        super(WorkspaceResource, self).__init__(path, environ)

        self.workspace = workspace
        self.content = None
        self.tracim_context = tracim_context
        self.user = tracim_context.current_user
        self.session = tracim_context.dbsession
        self.label = label
        self.content_api = ContentApi(
            current_user=self.user,
            session=tracim_context.dbsession,
            config=tracim_context.app_config,
            show_temporary=True,
            namespaces_filter=[ContentNamespaces.CONTENT],
        )

        self._file_count = 0

    def __repr__(self) -> str:
        return "<DAVCollection: Workspace (%d)>" % self.workspace.workspace_id

    def getPreferredPath(self):
        return self.path

    def getCreationDate(self) -> float:
        return mktime(self.workspace.created.timetuple())

    def getDisplayName(self) -> str:
        return webdav_convert_file_name_to_display(self.label)

    def getDisplayInfo(self):
        return {"type": "workspace".capitalize()}

    def getLastModified(self) -> float:
        return mktime(self.workspace.updated.timetuple())

    def getMemberNames(self) -> [str]:
        retlist = []

        children = self.content_api.get_all(
            parent_ids=[self.content.id] if self.content is not None else None,
            workspace=self.workspace,
        )

        for content in children:
            # the purpose is to display .history only if there's at least one content's type that has a history
            if content.type != content_type_list.Folder.slug:
                self._file_count += 1
            retlist.append(
                webdav_convert_file_name_to_display(content.file_name))

        return retlist

    def getMember(self, content_label: str) -> _DAVResource:

        return self.provider.getResourceInst(
            "%s/%s" %
            (self.path, webdav_convert_file_name_to_display(content_label)),
            self.environ)

    @webdav_check_right(is_contributor)
    def createEmptyResource(self, file_name: str):
        """
        [For now] we don't allow to create files right under workspaces.
        Though if we come to allow it, deleting the error's raise will make it possible.
        """
        # TODO : remove commentary here raise DAVError(HTTP_FORBIDDEN)
        if "/.deleted/" in self.path or "/.archived/" in self.path:
            raise DAVError(HTTP_FORBIDDEN)

        content = None

        # Note: To prevent bugs, check here again if resource already exist
        # fixed path
        fixed_file_name = webdav_convert_file_name_to_display(file_name)
        path = os.path.join(self.path, file_name)
        resource = self.provider.getResourceInst(path, self.environ)
        if resource:
            content = resource.content
        try:
            self.content_api.check_upload_size(
                int(self.environ["CONTENT_LENGTH"]), self.workspace)
        except (
                FileSizeOverMaxLimitation,
                FileSizeOverWorkspaceEmptySpace,
                FileSizeOverOwnerEmptySpace,
        ) as exc:
            raise DAVError(HTTP_REQUEST_ENTITY_TOO_LARGE, contextinfo=str(exc))
        # return item
        return FakeFileStream(
            session=self.session,
            file_name=fixed_file_name,
            content_api=self.content_api,
            workspace=self.workspace,
            content=content,
            parent=self.content,
            path=self.path + "/" + fixed_file_name,
        )

    @webdav_check_right(is_content_manager)
    def createCollection(self, label: str) -> "FolderResource":
        """
        Create a new folder for the current workspace. As it's not possible for the user to choose
        which types of content are allowed in this folder, we allow allow all of them.

        This method return the DAVCollection created.
        """

        if "/.deleted/" in self.path or "/.archived/" in self.path:
            raise DAVError(HTTP_FORBIDDEN)
        folder_label = webdav_convert_file_name_to_bdd(label)
        try:
            folder = self.content_api.create(
                content_type_slug=content_type_list.Folder.slug,
                workspace=self.workspace,
                label=folder_label,
                parent=self.content,
            )
            self.content_api.execute_created_content_actions(folder)
        except TracimException as exc:
            raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc)) from exc

        self.content_api.save(folder)

        transaction.commit()
        # fixed_path
        folder_path = "%s/%s" % (self.path,
                                 webdav_convert_file_name_to_display(label))
        # return item
        return FolderResource(
            folder_path,
            self.environ,
            content=folder,
            tracim_context=self.tracim_context,
            workspace=self.workspace,
        )

    def delete(self):
        """For now, it is not possible to delete a workspace through the webdav client."""
        # FIXME - G.M - 2018-12-11 - For an unknown reason current_workspace
        # of tracim_context is here invalid.
        self.tracim_context._current_workspace = self.workspace
        try:
            can_delete_workspace.check(self.tracim_context)
        except TracimException as exc:
            raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc))
        raise DAVError(HTTP_FORBIDDEN,
                       "Workspace deletion is not allowed through webdav")

    def supportRecursiveMove(self, destpath):
        return True

    def moveRecursive(self, destpath):
        # INFO - G.M - 2018-12-11 - We only allow renaming
        if dirname(normpath(
                destpath)) == self.environ["http_authenticator.realm"]:
            # FIXME - G.M - 2018-12-11 - For an unknown reason current_workspace
            # of tracim_context is here invalid.
            self.tracim_context._current_workspace = self.workspace
            try:
                can_modify_workspace.check(self.tracim_context)
            except TracimException as exc:
                raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc))

            try:
                workspace_api = WorkspaceApi(current_user=self.user,
                                             session=self.session,
                                             config=self.provider.app_config)
                workspace_api.update_workspace(
                    workspace=self.workspace,
                    label=webdav_convert_file_name_to_bdd(
                        basename(normpath(destpath))),
                    description=self.workspace.description,
                )
                self.session.add(self.workspace)
                self.session.flush()
                workspace_api.execute_update_workspace_actions(self.workspace)
                transaction.commit()
            except TracimException as exc:
                raise DAVError(HTTP_FORBIDDEN, contextinfo=str(exc))

    def getMemberList(self) -> [_DAVResource]:
        members = []

        children = self.content_api.get_all(False, content_type_list.Any_SLUG,
                                            self.workspace)

        for content in children:
            content_path = "%s/%s" % (
                self.path,
                webdav_convert_file_name_to_display(content.file_name),
            )

            if content.type == content_type_list.Folder.slug:
                members.append(
                    FolderResource(
                        path=content_path,
                        environ=self.environ,
                        workspace=self.workspace,
                        content=content,
                        tracim_context=self.tracim_context,
                    ))
            elif content.type == content_type_list.File.slug:
                self._file_count += 1
                members.append(
                    FileResource(
                        path=content_path,
                        environ=self.environ,
                        content=content,
                        tracim_context=self.tracim_context,
                    ))
            else:
                self._file_count += 1
                members.append(
                    OtherFileResource(content_path,
                                      self.environ,
                                      content,
                                      tracim_context=self.tracim_context))
        return members