Пример #1
0
def is_file_cap(capability):
    """
    :returns bool: True if `capability` is a mutable or immutable file
        capability (note this excludes all kinds of "verify"
        capabilities).
    """
    uri = tahoe_uri_from_string(capability)
    return IFileURI.providedBy(uri) or IImmutableFileURI.providedBy(uri)
Пример #2
0
def magic_folder_create(config, name, author_name, local_dir, poll_interval,
                        tahoe_client):
    """
    Create a magic-folder with the specified ``name`` and
    ``local_dir``.

    :param GlobalConfigDatabase config: Our configuration

    :param unicode name: The name of the magic-folder.

    :param unicode author_name: The name for our author

    :param FilePath local_dir: The directory on the filesystem that the user wants
        to sync between different computers.

    :param integer poll_interval: Periodic time interval after which the
        client polls for updates.

    :param TahoeClient tahoe_client: The client we use to make queries

    :return Deferred: ``None`` or an appropriate exception is raised.
    """

    if name in config.list_magic_folders():
        raise Exception("Already have a magic-folder named '{}'".format(name))

    # create our author
    author = create_local_author(author_name)

    # create an unlinked directory and get the dmd write-cap
    collective_write_cap = yield tahoe_client.create_mutable_directory()

    # create the personal dmd write-cap
    personal_write_cap = yield tahoe_client.create_mutable_directory()

    # 'attenuate' our personal dmd write-cap to a read-cap
    personal_readonly_cap = tahoe_uri_from_string(
        personal_write_cap).get_readonly().to_string().encode("ascii")

    # add ourselves to the collective
    yield tahoe_client.add_entry_to_mutable_directory(
        mutable_cap=collective_write_cap,
        path_name=author_name,
        entry_cap=personal_readonly_cap,
    )

    # create our "state" directory for this magic-folder (could be
    # configurable in the future)
    state_dir = config.get_default_state_path(name)
    config.create_magic_folder(
        name,
        local_dir,
        state_dir,
        author,
        collective_write_cap,
        personal_write_cap,
        poll_interval,
    )
Пример #3
0
 def is_admin(self):
     """
     :returns: True if this device can administer this folder. That is,
         if the collective capability we have is mutable.
     """
     # check if this folder has a writable collective dircap
     collective_dmd = tahoe_uri_from_string(
         self.collective_dircap.encode("utf8"))
     return not collective_dmd.is_readonly()
Пример #4
0
def to_readonly_capability(capability):
    """
    Converts a capability-string to a readonly capability-string. This
    may be the very same string if it is already read-only.
    """
    cap = tahoe_uri_from_string(capability)
    if cap.is_readonly():
        return capability
    return cap.get_readonly().to_string()
Пример #5
0
 def mutable_dirnode(self, attribute, value):
     """
     The Upload DMD must be a writable directory capability
     """
     uri = tahoe_uri_from_string(value)
     if IDirectoryURI.providedBy(uri):
         return
     raise TypeError(
         "Upload dirnode was {!r}, must be a read-write directory node.".
         format(value, ), )
Пример #6
0
 def any_dirnode(self, attribute, value):
     """
     The Collective DMD must be a directory capability (but could be a
     read-only one or a read-write one).
     """
     uri = tahoe_uri_from_string(value)
     if IReadonlyDirectoryURI.providedBy(uri) or IDirectoryURI.providedBy(
             uri):
         return
     raise TypeError(
         "Collective dirnode was {!r}, must be a directory node.".format(
             value, ), )
Пример #7
0
    def add(self, author, personal_dmd_cap):
        """
        IParticipants API
        """
        uri = tahoe_uri_from_string(personal_dmd_cap)
        if not IReadonlyDirectoryURI.providedBy(uri):
            raise ValueError(
                "New participant Personal DMD must be read-only dircap")
        if not isinstance(author, RemoteAuthor):
            raise ValueError("Author must be a RemoteAuthor instance")
        # semantically, it doesn't make sense to allow a second
        # participant with the very same Personal DMD as another (even
        # if the name/author is different). So, we check here .. but
        # there is a window for race between the check and when we add
        # the participant. The only real solution here would be a
        # magic-folder-wide write-lock or to serialize all Tahoe
        # operations (at least across one magic-folder).
        participants = yield self.list()
        if any(personal_dmd_cap == p.dircap for p in participants):
            raise ValueError(
                "Already have a participant with Personal DMD '{}'".format(
                    personal_dmd_cap))

        # NB: we could check here if there is already a participant
        # for this name .. however, there's a race between that check
        # succeeding and adding the participant so we just try to add
        # and let Tahoe send us an error by using "replace=False"
        try:
            yield self._tahoe_client.add_entry_to_mutable_directory(
                self._collective_cap,
                author.name,
                personal_dmd_cap.encode("ascii"),
                replace=False,
            )
        except CannotAddDirectoryEntryError:
            raise ValueError(u"Already have a participant called '{}'".format(
                author.name))
Пример #8
0
    def add_participant(self, request, folder_name):
        """
        Add a new participant to this folder with details from the JSON-encoded body.
        """
        try:
            folder_config = self._global_config.get_magic_folder(folder_name)
        except ValueError:
            returnValue(NoResource(b"{}"))

        body = request.content.read()
        try:
            participant = json.loads(body)
            required_keys = {
                "author",
                "personal_dmd",
            }
            required_author_keys = {
                "name",
                # not yet
                # "public_key_base32",
            }
            if set(participant.keys()) != required_keys:
                raise _InputError("Require input: {}".format(", ".join(
                    sorted(required_keys))))
            if set(participant["author"].keys()) != required_author_keys:
                raise _InputError("'author' requires: {}".format(", ".join(
                    sorted(required_author_keys))))

            author = create_author(
                participant["author"]["name"],
                # we don't yet properly track keys but need one
                # here .. this won't be correct, but we won't use
                # it .. following code still only looks at the
                # .name attribute
                # see https://github.com/LeastAuthority/magic-folder/issues/331
                VerifyKey(os.urandom(32)),
            )

            dmd = tahoe_uri_from_string(participant["personal_dmd"])
            if not IDirnodeURI.providedBy(dmd):
                raise _InputError(
                    "personal_dmd must be a directory-capability")
            if not dmd.is_readonly():
                raise _InputError("personal_dmd must be read-only")
            personal_dmd_cap = participant["personal_dmd"]
        except _InputError as e:
            request.setResponseCode(http.BAD_REQUEST)
            returnValue(json.dumps({"reason": str(e)}))

        collective = participants_from_collective(
            folder_config.collective_dircap,
            folder_config.upload_dircap,
            self._tahoe_client,
        )
        try:
            yield collective.add(author, personal_dmd_cap)
        except Exception:
            request.setResponseCode(http.INTERNAL_SERVER_ERROR)
            _application_json(request)
            # probably should log this error, at least for developers (so eliot?)
            returnValue(
                json.dumps({"reason": "unexpected error processing request"}))

        request.setResponseCode(http.CREATED)
        _application_json(request)
        returnValue(b"{}")
Пример #9
0
 def _is_self(self, dirobj):
     return tahoe_uri_from_string(
         dirobj).get_readonly().to_string() == tahoe_uri_from_string(
             self._upload_cap).get_readonly().to_string()
Пример #10
0
def is_directory_cap(capability):
    """
    :returns: True if `capability` is a directory-cap of any sort
    """
    uri = tahoe_uri_from_string(capability)
    return IDirnodeURI.providedBy(uri)
Пример #11
0
    def from_config(cls, reactor, tahoe_client, name, config):
        """
        Create a ``MagicFolder`` from a client node and magic-folder
        configuration.

        :param IReactorTime reactor: the reactor to use

        :param magic_folder.cli.TahoeClient tahoe_client: Access the API of
            the Tahoe-LAFS client we're associated with.

        :param GlobalConfigurationDatabase config: our configuration
        """
        mf_config = config.get_magic_folder(name)

        from .cli import (
            Node,
            TahoeClient,
        )
        from .tahoe_client import (
            TahoeClient as OtherTahoeClient, )

        if not isinstance(tahoe_client, TahoeClient):
            raise TypeError(
                "tahoe_client must be an instance of {}, received instance of {} instead."
                .format(
                    TahoeClient,
                    type(tahoe_client),
                ), )

        initial_participants = participants_from_collective(
            Node(tahoe_client,
                 tahoe_uri_from_string(mf_config.collective_dircap)),
            Node(tahoe_client, tahoe_uri_from_string(mf_config.upload_dircap)),
        )

        # Make the *other* kind of TahoeClient ...
        other_tahoe_client = OtherTahoeClient(
            tahoe_client.node_uri,
            tahoe_client.treq,
        )

        return cls(
            client=tahoe_client,
            config=mf_config,
            name=name,
            local_snapshot_service=LocalSnapshotService(
                mf_config.magic_path,
                LocalSnapshotCreator(
                    mf_config,
                    mf_config.author,
                    mf_config.stash_path,
                ),
            ),
            uploader_service=UploaderService.from_config(
                clock=reactor,
                config=mf_config,
                remote_snapshot_creator=RemoteSnapshotCreator(
                    config=mf_config,
                    local_author=mf_config.author,
                    tahoe_client=other_tahoe_client,
                    upload_dircap=mf_config.upload_dircap,
                ),
            ),
            initial_participants=initial_participants,
            clock=reactor,
        )