Example #1
0
    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])
Example #2
0
    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."),
            )
Example #4
0
    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]
Example #5
0
    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])
Example #6
0
 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
Example #7
0
    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"})
Example #8
0
    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")
Example #9
0
    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
        })
Example #10
0
def _unslugify(slug):
    raworg, rawdev = slug.split("#")
    return (OrganizationID(raworg), DeviceID(rawdev))
Example #11
0
    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}
"""
    )
Example #13
0
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)