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)
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, )
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()
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()
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, ), )
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, ), )
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))
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"{}")
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()
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)
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, )