async def _get_device_invitation(self, conn, organization_id: OrganizationID, device_id: DeviceID): if await self._device_exists(conn, organization_id, device_id): raise UserAlreadyExistsError( f"Device `{device_id}` already exists") result = await conn.fetchrow( """ SELECT device_invitations.device_id, devices.device_id, device_invitations.created_on FROM device_invitations LEFT JOIN devices ON device_invitations.creator = devices._id WHERE device_invitations.organization = ( SELECT _id from organizations WHERE organization_id = $1 ) AND device_invitations.device_id = $2 """, organization_id, device_id, ) if not result: raise UserNotFoundError(device_id) return DeviceInvitation(device_id=DeviceID(result[0]), creator=DeviceID(result[1]), created_on=result[2])
def list_available_devices( self) -> List[Tuple[OrganizationID, DeviceID, str]]: try: candidate_pathes = list(self.devices_config_path.iterdir()) except FileNotFoundError: return [] # Sanity checks devices = [] for device_path in candidate_pathes: try: raw_org_id, raw_device_id = _unslugify(device_path.name) organization_id = OrganizationID(raw_org_id) device_id = DeviceID(raw_device_id) except ValueError: continue try: cipher = self.get_cipher_info(organization_id, device_id) devices.append((organization_id, device_id, cipher)) except DeviceManagerError: continue return devices
def claim_clicked(self): use_pkcs11 = True if self.check_box_use_pkcs11.checkState() == Qt.Unchecked: use_pkcs11 = False if (len(self.line_edit_password.text()) > 0 or len(self.line_edit_password_check.text()) > 0): if self.line_edit_password.text( ) != self.line_edit_password_check.text(): show_error( self, QCoreApplication.translate("ClaimUserWidget", "Passwords don't match")) return try: backend_addr = BackendOrganizationAddr(self.line_edit_url.text()) device_id = DeviceID("{}@{}".format(self.line_edit_login.text(), self.line_edit_device.text())) except: show_error( self, QCoreApplication.translate("ClaimUserWidget", "URL or device is invalid.")) return try: args = None if use_pkcs11: args = ( backend_addr, device_id, self.line_edit_token.text(), True, None, int(self.line_edit_pkcs11_token.text()), int(self.line_edit_pkcs11.key.text()), ) else: args = ( backend_addr, device_id, self.line_edit_token.text(), False, self.line_edit_password.text(), ) self.button_cancel.show() self.claim_thread = threading.Thread( target=self._thread_claim_user, args=args) self.claim_thread.start() self.trio_portal = self.claim_queue.get() self.cancel_scope = self.claim_queue.get() self.button_claim.setDisabled(True) except: import traceback traceback.print_exc() show_error( self, QCoreApplication.translate("ClaimUserWidget", "Can not register the new user."), )
async def get(self, organization_id: OrganizationID, recipient: UserID, offset: int) -> List[Tuple[DeviceID, bytes]]: async with self.dbh.pool.acquire() as conn: data = await conn.fetch( """ SELECT devices.device_id, messages.body FROM messages LEFT JOIN devices ON messages.sender = devices._id WHERE recipient = ( SELECT _id FROM users WHERE organization = ( SELECT _id from organizations WHERE organization_id = $1 ) AND user_id = $2 ) ORDER BY messages._id ASC OFFSET $3 """, organization_id, recipient, offset, ) # TODO: we should configure a DeviceID custom serializer in dbh return [(DeviceID(d[0]), d[1]) for d in data]
async def _get_user_invitation(self, conn, organization_id: OrganizationID, user_id: UserID): if await self._user_exists(conn, organization_id, user_id): raise UserAlreadyExistsError(f"User `{user_id}` already exists") result = await conn.fetchrow( """ SELECT user_invitations.user_id, devices.device_id, user_invitations.created_on FROM user_invitations LEFT JOIN devices ON user_invitations.creator = devices._id WHERE user_invitations.organization = ( SELECT _id from organizations WHERE organization_id = $1 ) AND user_invitations.user_id = $2 """, organization_id, user_id, ) if not result: raise UserNotFoundError(user_id) return UserInvitation(user_id=UserID(result[0]), creator=DeviceID(result[1]), created_on=result[2])
def validate(self, string, pos): try: if len(string) == 0: return QValidator.Intermediate, string, pos DeviceID(string) return QValidator.Acceptable, string, pos except ValueError: return QValidator.Invalid, string, pos
async def api_device_cancel_invitation(self, client_ctx, msg): msg = device_cancel_invitation_serializer.req_load(msg) invited_device_id = DeviceID( f"{client_ctx.device_id.user_id}@{msg['invited_device_name']}") await self.cancel_device_invitation(client_ctx.organization_id, invited_device_id) return device_cancel_invitation_serializer.rep_dump({"status": "ok"})
async def _process_message(self, sender_id: DeviceID, ciphered: bytes): """ Raises: SharingRecipientError SharingInvalidMessageError """ expected_user_id, expected_device_name = sender_id.split("@") try: real_sender_id, raw = await self.encryption_manager.decrypt_for_self( ciphered) except EncryptionManagerError as exc: raise SharingRecipientError( f"Cannot decrypt message from `{sender_id}`") from exc if real_sender_id != sender_id: raise SharingRecipientError( f"Message was said to be send by `{sender_id}`, " f" but is signed by {real_sender_id}") try: msg = generic_message_content_serializer.loads(raw) except SerdeError as exc: raise SharingInvalidMessageError( f"Not a valid message: {exc.errors}") from exc if msg["type"] == "share": user_manifest_access, user_manifest = await self._get_user_manifest( ) for child_access in user_manifest.children.values(): if child_access == msg["access"]: # Shared entry already present, nothing to do then return for i in count(1): if i == 1: sharing_name = msg["name"] else: sharing_name = f"{msg['name']} {i}" if sharing_name not in user_manifest.children: break user_manifest = user_manifest.evolve_children_and_mark_updated( {sharing_name: msg["access"]}) self.local_folder_fs.update_manifest(user_manifest_access, user_manifest) path = f"/{sharing_name}" self.event_bus.send("sharing.new", path=path, access=msg["access"]) self.event_bus.send("fs.entry.updated", id=user_manifest_access.id) self.event_bus.send("fs.entry.synced", id=msg["access"].id, path=str(path)) elif msg["type"] == "ping": self.event_bus.send("pinged")
async def api_device_invite(self, client_ctx, msg): msg = device_invite_serializer.req_load(msg) invited_device_id = DeviceID( f"{client_ctx.device_id.user_id}@{msg['invited_device_name']}") invitation = DeviceInvitation(invited_device_id, client_ctx.device_id) try: await self.create_device_invitation(client_ctx.organization_id, invitation) except UserAlreadyExistsError as exc: return {"status": "already_exists", "reason": str(exc)} # Wait for invited user to send `user_claim` claim_answered = trio.Event() _encrypted_claim = None def _on_device_claimed(event, organization_id, device_id, encrypted_claim): nonlocal _encrypted_claim if organization_id == client_ctx.organization_id and device_id == invitation.device_id: claim_answered.set() _encrypted_claim = encrypted_claim self.event_bus.connect("device.claimed", _on_device_claimed, weak=True) with trio.move_on_after(PEER_EVENT_MAX_WAIT) as cancel_scope: await claim_answered.wait() if cancel_scope.cancelled_caught: return { "status": "timeout", "reason": ("Timeout while waiting for new device to be claimed."), } return device_invite_serializer.rep_dump({ "status": "ok", "encrypted_claim": _encrypted_claim })
def _unslugify(slug): raworg, rawdev = slug.split("#") return (OrganizationID(raworg), DeviceID(rawdev))
async def _get_user(self, conn, organization_id: OrganizationID, user_id: UserID) -> User: user_result = await conn.fetchrow( """ SELECT is_admin, certified_user, (SELECT device_id FROM devices WHERE _id = user_certifier), created_on FROM users WHERE organization = ( SELECT _id from organizations WHERE organization_id = $1 ) AND user_id = $2 """, organization_id, user_id, ) if not user_result: raise UserNotFoundError(user_id) devices_results = await conn.fetch( """ SELECT d1.device_id, d1.certified_device, ( SELECT devices.device_id FROM devices WHERE devices._id = d1.device_certifier ) AS device_certifier, d1.created_on, d1.revocated_on, d1.certified_revocation, ( SELECT devices.device_id FROM devices WHERE devices._id = d1.revocation_certifier ) as revocation_certifier FROM devices as d1 WHERE user_ = ( SELECT _id FROM users WHERE organization = ( SELECT _id from organizations WHERE organization_id = $1 ) AND user_id = $2 ); """, organization_id, user_id, ) devices = DevicesMapping(*[ Device(DeviceID(device_result[0]), *device_result[1:]) for device_result in devices_results ]) return User( user_id=UserID(user_id), is_admin=user_result[0], certified_user=user_result[1], user_certifier=user_result[2], created_on=user_result[3], devices=devices, )
async def amain( backend_address="ws://*****:*****@laptop", bob_device_id="bob@laptop", other_device_name="pc", alice_workspace="alicews", bob_workspace="bobws", password="******", administrator_token=DEFAULT_ADMINISTRATOR_TOKEN, force=False, ): configure_logging("WARNING") config_dir = get_default_config_dir(os.environ) organization_id = OrganizationID(organization_id) backend_address = BackendAddr(backend_address) alice_device_id = DeviceID(alice_device_id) bob_device_id = DeviceID(bob_device_id) alice_slugid = f"{organization_id}:{alice_device_id}" bob_slugid = f"{organization_id}:{bob_device_id}" # Create organization async with backend_administrator_cmds_factory(backend_address, administrator_token) as cmds: bootstrap_token = await cmds.organization_create(organization_id) organization_bootstrap_addr = BackendOrganizationBootstrapAddr.build( backend_address, organization_id, bootstrap_token ) # Bootstrap organization and Alice user async with backend_anonymous_cmds_factory(organization_bootstrap_addr) as cmds: root_signing_key = SigningKey.generate() root_verify_key = root_signing_key.verify_key organization_addr = organization_bootstrap_addr.generate_organization_addr(root_verify_key) alice_device = generate_new_device(alice_device_id, organization_addr) save_device_with_password(config_dir, alice_device, password, force=force) now = pendulum.now() certified_user = certify_user( None, root_signing_key, alice_device.user_id, alice_device.public_key, now ) certified_device = certify_device( None, root_signing_key, alice_device_id, alice_device.verify_key, now ) await cmds.organization_bootstrap( organization_bootstrap_addr.organization_id, organization_bootstrap_addr.bootstrap_token, root_verify_key, certified_user, certified_device, ) # Create a workspace for Alice config = load_config(config_dir, debug="DEBUG" in os.environ) async with logged_core_factory(config, alice_device) as core: await core.fs.workspace_create(f"/{alice_workspace}") # Register a new device for Alice token = generate_invitation_token() other_alice_device_id = DeviceID("@".join((alice_device.user_id, other_device_name))) other_alice_slugid = f"{organization_id}:{other_alice_device_id}" async def invite_task(): async with backend_cmds_factory( alice_device.organization_addr, alice_device.device_id, alice_device.signing_key ) as cmds: await invite_and_create_device(alice_device, cmds, other_device_name, token) async def claim_task(): async with backend_anonymous_cmds_factory(alice_device.organization_addr) as cmds: other_alice_device = await retry(claim_device, cmds, other_alice_device_id, token) save_device_with_password(config_dir, other_alice_device, password, force=force) async with trio.open_nursery() as nursery: nursery.start_soon(invite_task) nursery.start_soon(claim_task) # Invite Bob in token = generate_invitation_token() async def invite_task(): async with backend_cmds_factory( alice_device.organization_addr, alice_device.device_id, alice_device.signing_key ) as cmds: await invite_and_create_user( alice_device, cmds, bob_device_id.user_id, token, is_admin=True ) async def claim_task(): async with backend_anonymous_cmds_factory(alice_device.organization_addr) as cmds: bob_device = await retry(claim_user, cmds, bob_device_id, token) save_device_with_password(config_dir, bob_device, password, force=force) async with trio.open_nursery() as nursery: nursery.start_soon(invite_task) nursery.start_soon(claim_task) # Create bob workspace and share with Alice bob_device = load_device_with_password( config.config_dir, organization_id, bob_device_id, password ) async with logged_core_factory(config, bob_device) as core: await core.fs.workspace_create(f"/{bob_workspace}") await core.fs.share(f"/{bob_workspace}", alice_device_id.user_id) # Share Alice workspace with bob async with logged_core_factory(config, alice_device) as core: await core.fs.share(f"/{alice_workspace}", bob_device_id.user_id) # Print out click.echo( f""" Mount alice and bob drives using: $ parsec core run -P {password} -D {alice_slugid} $ parsec core run -P {password} -D {other_alice_slugid} $ parsec core run -P {password} -D {bob_slugid} """ )
def unslug(val): if ":" not in val: raise ValueError( "Must follow format `<organization>:<user_id>@<device_name>`") raw_org, raw_device_id = val.split(":") return (OrganizationID(raw_org), DeviceID(raw_device_id), val)