async def list(self, organization_id: OrganizationID, greeter: UserID) -> List[Invitation]: async with self.dbh.pool.acquire() as conn: rows = await conn.fetch(*_q_list_invitations( organization_id=organization_id, greeter_user_id=greeter)) invitations_with_claimer_online = self._claimers_ready[organization_id] invitations = [] for ( token, type, greeter, greeter_human_handle_email, greeter_human_handle_label, claimer_email, created_on, deleted_on, deleted_reason, ) in rows: if greeter_human_handle_email: greeter_human_handle = HumanHandle( email=greeter_human_handle_email, label=greeter_human_handle_label) else: greeter_human_handle = None if deleted_on: status = InvitationStatus.DELETED elif token in invitations_with_claimer_online: status = InvitationStatus.READY else: status = InvitationStatus.IDLE if type == InvitationType.USER.value: invitation = UserInvitation( greeter_user_id=UserID(greeter), greeter_human_handle=greeter_human_handle, claimer_email=claimer_email, token=token, created_on=created_on, status=status, ) else: # Device invitation = DeviceInvitation( greeter_user_id=UserID(greeter), greeter_human_handle=greeter_human_handle, token=token, created_on=created_on, status=status, ) invitations.append(invitation) return invitations
def _from_url_parse_and_consume_params(cls, params): kwargs = super()._from_url_parse_and_consume_params(params) value = params.pop("action", ()) if len(value) != 1: raise ValueError("Missing mandatory `action` param") if value[0] != "claim_user": raise ValueError("Expected `action=claim_user` value") value = params.pop("user_id", ()) if len(value) != 1: raise ValueError("Missing mandatory `user_id` param") try: kwargs["user_id"] = UserID(value[0]) except ValueError as exc: raise ValueError("Invalid `user_id` param value") from exc value = params.pop("token", ()) if len(value) > 0: try: kwargs["token"] = value[0] except ValueError: raise ValueError("Invalid `token` param value") return kwargs
def test_bad_organization_id_user_id_and_device_name(raw): with pytest.raises(ValueError): OrganizationID(raw) with pytest.raises(ValueError): UserID(raw) with pytest.raises(ValueError): DeviceName(raw)
async def query_retrieve_active_human_by_email(conn, organization_id: OrganizationID, email: str) -> Optional[UserID]: result = await conn.fetchrow(*_q_retrieve_active_human_by_email( organization_id=organization_id, now=pendulum_now(), email=email)) if result: return UserID(result["user_id"])
def validate(self, string, pos): try: if len(string) == 0: return QValidator.Intermediate, string, pos UserID(string) return QValidator.Acceptable, string, pos except ValueError: return QValidator.Invalid, string, pos
async def info(self, organization_id: OrganizationID, token: UUID) -> Invitation: async with self.dbh.pool.acquire() as conn: row = await conn.fetchrow(*_q_info_invitation( organization_id=organization_id, token=token)) if not row: raise InvitationNotFoundError(token) ( type, greeter, greeter_human_handle_email, greeter_human_handle_label, claimer_email, created_on, deleted_on, deleted_reason, ) = row if deleted_on: raise InvitationAlreadyDeletedError(token) if greeter_human_handle_email: greeter_human_handle = HumanHandle( email=greeter_human_handle_email, label=greeter_human_handle_label) else: greeter_human_handle = None if type == InvitationType.USER.value: return UserInvitation( greeter_user_id=UserID(greeter), greeter_human_handle=greeter_human_handle, claimer_email=claimer_email, token=token, created_on=created_on, status=InvitationStatus.READY, ) else: # Device return DeviceInvitation( greeter_user_id=UserID(greeter), greeter_human_handle=greeter_human_handle, token=token, created_on=created_on, status=InvitationStatus.READY, )
async def query_find(conn, organization_id: OrganizationID, query: str, page: int, per_page: int, omit_revoked: bool) -> Tuple[List[UserID], int]: if page > 1: offset = ((page - 1) * per_page) - 1 else: offset = 0 if query: try: UserID(query) except ValueError: # Contains invalid caracters, no need to go further return ([], 0) if omit_revoked: q = _q_factory(query=True, omit_revoked=True, limit=per_page, offset=offset) args = q(organization_id=organization_id, query="%" + query + "%", now=pendulum_now()) else: q = _q_factory(query=True, omit_revoked=False, limit=per_page, offset=offset) args = q(organization_id=organization_id, query="%" + query + "%") else: if omit_revoked: q = _q_factory(query=False, omit_revoked=True, limit=per_page, offset=offset) args = q(organization_id=organization_id, now=pendulum_now()) else: q = _q_factory(query=False, omit_revoked=False, limit=per_page, offset=offset) args = q(organization_id=organization_id) all_results = [user["user_id"] for user in await conn.fetch(*args)] q = _q_count_total_human(query, omit_revoked=omit_revoked, omit_non_human=True, in_find=True) if omit_revoked: total = await conn.fetchrow( *q(organization_id=organization_id, now=pendulum_now())) else: total = await conn.fetchrow(*q(organization_id=organization_id)) return all_results, total[0]
def test_organization_id_user_id_and_device_name(raw): organization_id = OrganizationID(raw) assert organization_id == raw user_id = UserID(raw) assert user_id == raw device_name = DeviceName(raw) assert device_name == raw
async def query_get_current_roles(conn, organization_id: OrganizationID, realm_id: UUID) -> Dict[UserID, RealmRole]: ret = await conn.fetch(*_q_get_current_roles( organization_id=organization_id, realm_id=realm_id)) if not ret: # Existing group must have at least one owner user raise RealmNotFoundError(f"Realm `{realm_id}` doesn't exist") return { UserID(user_id): STR_TO_REALM_ROLE[role] for user_id, role in ret if role is not None }
async def find( self, organization_id: OrganizationID, query: str = None, page: int = 0, per_page: int = 100, omit_revoked: bool = False, ): org = self._organizations[organization_id] users = org.users if query: try: UserID(query) except ValueError: # Contains invalid caracters, no need to go further return ([], 0) results = [ user_id for user_id in users.keys() if user_id.lower().find(query.lower()) != -1 ] else: results = users.keys() if omit_revoked: now = pendulum.now() def _user_is_revoked(user_id): revoked_on = org.users[user_id].revoked_on return revoked_on is not None and revoked_on <= now results = [ user_id for user_id in results if not _user_is_revoked(user_id) ] # PostgreSQL does case insensitive sort sorted_results = sorted(results, key=lambda s: s.lower()) return sorted_results[(page - 1) * per_page:page * per_page], len(results)
def next_user_id(self): nonlocal name_count name_count += 1 return UserID(f"user{name_count}")
async def test_organization_bootstrap_bad_data( backend_data_binder, apiv1_backend_sock_factory, organization_factory, local_device_factory, backend, coolorg, ): neworg = organization_factory("NewOrg") newalice = local_device_factory("alice@dev1", neworg) await backend_data_binder.bind_organization(neworg) bad_organization_id = coolorg.organization_id good_organization_id = neworg.organization_id root_signing_key = neworg.root_signing_key bad_root_signing_key = coolorg.root_signing_key good_bootstrap_token = neworg.bootstrap_token bad_bootstrap_token = coolorg.bootstrap_token good_rvk = neworg.root_verify_key bad_rvk = coolorg.root_verify_key good_device_id = newalice.device_id good_user_id = newalice.user_id bad_user_id = UserID("dummy") public_key = newalice.public_key verify_key = newalice.verify_key now = pendulum.now() bad_now = now.subtract(seconds=1) good_cu = UserCertificateContent( author=None, timestamp=now, user_id=good_user_id, public_key=public_key, profile=UserProfile.ADMIN, human_handle=newalice.human_handle, ) good_redacted_cu = good_cu.evolve(human_handle=None) good_cd = DeviceCertificateContent( author=None, timestamp=now, device_id=good_device_id, device_label=newalice.device_label, verify_key=verify_key, ) good_redacted_cd = good_cd.evolve(device_label=None) bad_now_cu = good_cu.evolve(timestamp=bad_now) bad_now_cd = good_cd.evolve(timestamp=bad_now) bad_now_redacted_cu = good_redacted_cu.evolve(timestamp=bad_now) bad_now_redacted_cd = good_redacted_cd.evolve(timestamp=bad_now) bad_id_cu = good_cu.evolve(user_id=bad_user_id) bad_not_admin_cu = good_cu.evolve(profile=UserProfile.STANDARD) bad_key_cu = good_cu.dump_and_sign(bad_root_signing_key) bad_key_cd = good_cd.dump_and_sign(bad_root_signing_key) good_cu = good_cu.dump_and_sign(root_signing_key) good_redacted_cu = good_redacted_cu.dump_and_sign(root_signing_key) good_cd = good_cd.dump_and_sign(root_signing_key) good_redacted_cd = good_redacted_cd.dump_and_sign(root_signing_key) bad_now_cu = bad_now_cu.dump_and_sign(root_signing_key) bad_now_cd = bad_now_cd.dump_and_sign(root_signing_key) bad_now_redacted_cu = bad_now_redacted_cu.dump_and_sign(root_signing_key) bad_now_redacted_cd = bad_now_redacted_cd.dump_and_sign(root_signing_key) bad_id_cu = bad_id_cu.dump_and_sign(root_signing_key) bad_not_admin_cu = bad_not_admin_cu.dump_and_sign(root_signing_key) for i, (status, organization_id, *params) in enumerate([ ("not_found", good_organization_id, bad_bootstrap_token, good_cu, good_cd, good_rvk), ( "already_bootstrapped", bad_organization_id, good_bootstrap_token, good_cu, good_cd, good_rvk, ), ( "invalid_certification", good_organization_id, good_bootstrap_token, good_cu, good_cd, bad_rvk, ), ( "invalid_data", good_organization_id, good_bootstrap_token, bad_now_cu, good_cd, good_rvk, ), ( "invalid_data", good_organization_id, good_bootstrap_token, bad_id_cu, good_cd, good_rvk, ), ( "invalid_certification", good_organization_id, good_bootstrap_token, bad_key_cu, good_cd, good_rvk, ), ( "invalid_data", good_organization_id, good_bootstrap_token, good_cu, bad_now_cd, good_rvk, ), ( "invalid_certification", good_organization_id, good_bootstrap_token, good_cu, bad_key_cd, good_rvk, ), ( "invalid_data", good_organization_id, good_bootstrap_token, bad_not_admin_cu, good_cd, good_rvk, ), # Tests with redacted certificates ( "invalid_data", good_organization_id, good_bootstrap_token, good_cu, good_cd, good_rvk, good_cu, # Not redacted ! good_redacted_cd, ), ( "invalid_data", good_organization_id, good_bootstrap_token, good_cu, good_cd, good_rvk, good_redacted_cu, good_cd, # Not redacted ! ), ( "bad_message", good_organization_id, good_bootstrap_token, good_cu, good_cd, good_rvk, None, # None not allowed good_redacted_cd, ), ( "bad_message", good_organization_id, good_bootstrap_token, good_cu, good_cd, good_rvk, good_redacted_cu, None, # None not allowed ), ( "invalid_data", good_organization_id, good_bootstrap_token, good_cu, good_cd, good_rvk, bad_now_redacted_cu, good_redacted_cd, ), ( "invalid_data", good_organization_id, good_bootstrap_token, good_cu, good_cd, good_rvk, good_redacted_cu, bad_now_redacted_cd, ), ( "invalid_data", good_organization_id, good_bootstrap_token, good_cu, good_cd, good_rvk, good_redacted_cu, _missing, # Must proved redacted_device if redacted user is present ), ( "invalid_data", good_organization_id, good_bootstrap_token, good_cu, good_cd, good_rvk, _missing, # Must proved redacted_device if redacted user is present good_redacted_cd, ), ]): print(f"sub test {i}") async with apiv1_backend_sock_factory(backend, organization_id) as sock: rep = await organization_bootstrap(sock, *params) assert rep["status"] == status # Finally cheap test to make sure our "good" data were really good async with apiv1_backend_sock_factory(backend, good_organization_id) as sock: rep = await organization_bootstrap( sock, good_bootstrap_token, good_cu, good_cd, good_rvk, good_redacted_cu, good_redacted_cd, ) assert rep["status"] == "ok"
async def query_find_humans( conn, organization_id: OrganizationID, query: str, page: int, per_page: int, omit_revoked: bool, omit_non_human: bool, ) -> Tuple[List[HumanFindResultItem], int]: if page >= 1: offset = (page - 1) * per_page else: return ([], 0) if query: if omit_revoked: q = _q_human_factory( query=True, omit_revoked=True, omit_non_human=omit_non_human, limit=per_page, offset=offset, ) args = q(organization_id=organization_id, now=pendulum_now(), query="%" + query + "%") else: q = _q_human_factory( query=True, omit_revoked=False, omit_non_human=omit_non_human, offset=offset, limit=per_page, ) args = q(organization_id=organization_id, now=pendulum_now(), query="%" + query + "%") else: if omit_revoked: q = _q_human_factory( query=False, omit_revoked=True, omit_non_human=omit_non_human, limit=per_page, offset=offset, ) args = q(organization_id=organization_id, now=pendulum_now()) else: q = _q_human_factory( query=False, omit_revoked=False, omit_non_human=omit_non_human, offset=offset, limit=per_page, ) args = q(organization_id=organization_id, now=pendulum_now()) raw_results = await conn.fetch(*args) humans = [ HumanFindResultItem( user_id=UserID(user_id), human_handle=HumanHandle(email=email, label=label) if email is not None else None, revoked=revoked, ) for user_id, email, label, revoked in raw_results ] q = _q_count_total_human(query, omit_revoked=omit_revoked, omit_non_human=omit_non_human) if omit_revoked: total = await conn.fetchrow( *q(organization_id=organization_id, now=pendulum_now())) else: total = await conn.fetchrow(*q(organization_id=organization_id)) return (humans, total[0])
) def test_backend_organization_bootstrap_addr_bad_value(url, exc_msg): with pytest.raises(ValueError) as exc: BackendOrganizationBootstrapAddr.from_url(url) assert str(exc.value) == exc_msg @pytest.fixture(scope="session") def organization_addr(exported_verify_key): url = "parsec://foo/org?rvk=<rvk>".replace("<rvk>", exported_verify_key) return BackendOrganizationAddr.from_url(url) @pytest.mark.parametrize( "user_id,token", [(UserID("alice"), "123"), (UserID("alice"), None)] # Token is not mandatory ) def test_backend_organization_claim_user_addr_good(organization_addr, user_id, token): addr = BackendOrganizationClaimUserAddr.build(organization_addr, user_id, token) assert addr.hostname == organization_addr.hostname assert addr.port == organization_addr.port assert addr.use_ssl == organization_addr.use_ssl assert addr.organization_id == organization_addr.organization_id assert addr.root_verify_key == organization_addr.root_verify_key assert isinstance(addr.user_id, UserID) assert addr.user_id == user_id