async def get_user_devices_info(self, user_id: UserID = None) -> List[DeviceInfo]: """ Raises: BackendConnectionError """ user_id = user_id or self.device.user_id try: user_certif, revoked_user_certif, device_certifs = await self._remote_devices_manager.get_user_and_devices( user_id, no_cache=True ) except RemoteDevicesManagerBackendOfflineError as exc: raise BackendNotAvailable(str(exc)) from exc except RemoteDevicesManagerNotFoundError as exc: raise BackendNotFoundError(str(exc)) from exc except RemoteDevicesManagerError as exc: # TODO: we should be using our own kind of exception instead of borowing BackendConnectionError... raise BackendConnectionError( f"Error while fetching user {user_id} certificates" ) from exc results = [] for device_certif in device_certifs: results.append( DeviceInfo( device_id=device_certif.device_id, device_label=device_certif.device_label, created_on=device_certif.timestamp, ) ) return results
async def find_humans( self, query: str = None, page: int = 1, per_page: int = 100, omit_revoked: bool = False, omit_non_human: bool = False, ) -> Tuple[List[UserInfo], int]: """ Raises: BackendConnectionError """ rep = await self._backend_conn.cmds.human_find( query=query, page=page, per_page=per_page, omit_revoked=omit_revoked, omit_non_human=omit_non_human, ) if rep["status"] != "ok": raise BackendConnectionError(f"Backend error: {rep}") results = [] for item in rep["results"]: # Note `BackendNotFoundError` should never occurs (unless backend is broken !) # here given we are feeding the backend the user IDs it has provided us user_info = await self.get_user_info(item["user_id"]) results.append(user_info) return (results, rep["total"])
async def find_humans( self, query: str = None, page: int = 1, per_page: int = 100, omit_revoked: bool = False, omit_non_human: bool = False, ) -> Tuple[List[UserInfo], int]: """ Raises: BackendConnectionError """ rep = await self._backend_conn.cmds.human_find( query=query, page=page, per_page=per_page, omit_revoked=omit_revoked, omit_non_human=omit_non_human, ) if rep["status"] != "ok": raise BackendConnectionError(f"Backend error: {rep}") results = [] for item in rep["results"]: user_info = await self.get_user_info(item["user_id"]) results.append(user_info) return (results, rep["total"])
async def new_device_invitation( self, send_email: bool ) -> Tuple[BackendInvitationAddr, InvitationEmailSentStatus]: """ Raises: BackendConnectionError """ rep = await self._backend_conn.cmds.invite_new( type=InvitationType.DEVICE, send_email=send_email) if rep["status"] != "ok": raise BackendConnectionError(f"Backend error: {rep}") if not ("email_sent" in rep): email_sent = InvitationEmailSentStatus.SUCCESS else: email_sent = rep["email_sent"] return ( BackendInvitationAddr.build( backend_addr=self.device.organization_addr.get_backend_addr(), organization_id=self.device.organization_id, invitation_type=InvitationType.DEVICE, token=rep["token"], ), email_sent, )
async def new_user_invitation( self, email: str, send_email: bool ) -> Tuple[BackendInvitationAddr, InvitationEmailSentStatus]: """ Raises: BackendConnectionError """ rep = await self._backend_conn.cmds.invite_new( type=InvitationType.USER, claimer_email=email, send_email=send_email) if rep["status"] == "already_member": raise BackendInvitationOnExistingMember( "An user already exist with this email") elif rep["status"] != "ok": raise BackendConnectionError(f"Backend error: {rep}") if not ("email_sent" in rep): email_sent = InvitationEmailSentStatus.SUCCESS else: email_sent = rep["email_sent"] return ( BackendInvitationAddr.build( backend_addr=self.device.organization_addr.get_backend_addr(), organization_id=self.device.organization_id, invitation_type=InvitationType.USER, token=rep["token"], ), email_sent, )
async def list_invitations(self) -> List[dict]: # TODO: better return type """ Raises: BackendConnectionError """ rep = await self._backend_conn.cmds.invite_list() if rep["status"] != "ok": raise BackendConnectionError(f"Backend error: {rep}") return rep["invitations"]
async def delete_invitation( self, token: UUID, reason: InvitationDeletedReason = InvitationDeletedReason.CANCELLED ) -> None: """ Raises: BackendConnectionError """ rep = await self._backend_conn.cmds.invite_delete(token=token, reason=reason) if rep["status"] != "ok": raise BackendConnectionError(f"Backend error: {rep}")
async def get_organization_stats(self) -> OrganizationStats: """ Raises: BackendConnectionError """ rep = await self._backend_conn.cmds.organization_stats() if rep["status"] != "ok": raise BackendConnectionError(f"Backend error: {rep}") return OrganizationStats( users=rep["users"], data_size=rep["data_size"], metadata_size=rep["metadata_size"] )
async def _create_new_device_for_self( original_device: LocalDevice, new_device_label: DeviceLabel) -> LocalDevice: """ Raises: BackendConnectionError """ new_device = LocalDevice( organization_addr=original_device.organization_addr, device_id=DeviceID(f"{original_device.user_id}@{DeviceName.new()}"), device_label=new_device_label, human_handle=original_device.human_handle, profile=original_device.profile, private_key=original_device.private_key, signing_key=SigningKey.generate(), user_manifest_id=original_device.user_manifest_id, user_manifest_key=original_device.user_manifest_key, local_symkey=SecretKey.generate(), ) now = pendulum_now() device_certificate = DeviceCertificateContent( author=original_device.device_id, timestamp=now, device_id=new_device.device_id, device_label=new_device.device_label, verify_key=new_device.verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) device_certificate = device_certificate.dump_and_sign( original_device.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( original_device.signing_key) async with backend_authenticated_cmds_factory( addr=original_device.organization_addr, device_id=original_device.device_id, signing_key=original_device.signing_key, ) as cmds: rep = await cmds.device_create( device_certificate=device_certificate, redacted_device_certificate=redacted_device_certificate, ) if rep["status"] != "ok": raise BackendConnectionError(f"Cannot create recovery device: {rep}") return new_device
async def new_device_invitation(self, send_email: bool) -> BackendInvitationAddr: """ Raises: BackendConnectionError """ rep = await self._backend_conn.cmds.invite_new( type=InvitationType.DEVICE, send_email=send_email ) if rep["status"] != "ok": raise BackendConnectionError(f"Backend error: {rep}") return BackendInvitationAddr.build( backend_addr=self.device.organization_addr, organization_id=self.device.organization_id, invitation_type=InvitationType.DEVICE, token=rep["token"], )
async def revoke_user(self, user_id: UserID) -> None: """ Raises: BackendConnectionError """ now = pendulum_now() revoked_user_certificate = RevokedUserCertificateContent( author=self.device.device_id, timestamp=now, user_id=user_id ).dump_and_sign(self.device.signing_key) rep = await self._backend_conn.cmds.user_revoke( revoked_user_certificate=revoked_user_certificate ) if rep["status"] != "ok": raise BackendConnectionError(f"Error while trying to revoke user {user_id}: {rep}") # Invalidate potential cache to avoid displaying the user as not-revoked self._remote_devices_manager.invalidate_user_cache(user_id)
async def delete_invitation( self, token: UUID, reason: InvitationDeletedReason = InvitationDeletedReason.CANCELLED ) -> None: """ Raises: BackendConnectionError """ rep = await self._backend_conn.cmds.invite_delete(token=token, reason=reason) if rep["status"] == "not_found": raise BackendInvitationNotFound("Invitation not found") elif rep["status"] == "already_deleted": raise BackendInvitationAlreadyUsed("Invitation already used") elif rep["status"] != "ok": raise BackendConnectionError(f"Backend error: {rep}")
async def new_user_invitation(self, email: str, send_email: bool) -> BackendInvitationAddr: """ Raises: BackendConnectionError """ rep = await self._backend_conn.cmds.invite_new( type=InvitationType.USER, claimer_email=email, send_email=send_email ) if rep["status"] == "already_member": raise InviteAlreadyMemberError() elif rep["status"] != "ok": raise BackendConnectionError(f"Backend error: {rep}") return BackendInvitationAddr.build( backend_addr=self.device.organization_addr, organization_id=self.device.organization_id, invitation_type=InvitationType.USER, token=rep["token"], )
async def get_organization_stats(self) -> OrganizationStats: """ Raises: BackendConnectionError """ rep = await self._backend_conn.cmds.organization_stats() if rep["status"] != "ok": raise BackendConnectionError(f"Backend error: {rep}") return OrganizationStats( data_size=rep["data_size"], metadata_size=rep["metadata_size"], realms=rep["realms"], users=rep["users"], active_users=rep["active_users"], users_per_profile_detail=[ UsersPerProfileDetailItem(**x) for x in rep["users_per_profile_detail"] ], )
async def get_user_info(self, user_id: UserID) -> UserInfo: """ Raises: BackendConnectionError """ try: user_certif, revoked_user_certif = await self._remote_devices_manager.get_user(user_id) except RemoteDevicesManagerBackendOfflineError as exc: raise BackendNotAvailable(str(exc)) from exc except RemoteDevicesManagerNotFoundError as exc: raise BackendNotFoundError(str(exc)) from exc except RemoteDevicesManagerError as exc: # TODO: we should be using our own kind of exception instead of borowing BackendConnectionError... raise BackendConnectionError( f"Error while fetching user {user_id} certificates" ) from exc return UserInfo( user_id=user_certif.user_id, human_handle=user_certif.human_handle, profile=user_certif.profile, revoked_on=revoked_user_certif.timestamp if revoked_user_certif else None, created_on=user_certif.timestamp, )