Source code for crossbar.master.node.user

###############################################################################
#
# Crossbar.io Master
# Copyright (c) typedef int GmbH. Licensed under EUPLv1.2.
#
###############################################################################

import uuid

from autobahn import wamp
from autobahn.wamp.exception import ApplicationError
from autobahn.wamp.types import CallDetails, PublishOptions
from cfxdb.user import Organization


[docs] class UserManager(object): """ User management API (domain-global). Prefix: ``crossbarfabriccenter.user.`` """
[docs] _PUBOPTS = PublishOptions(acknowledge=True)
def __init__(self, session, db, schema):
[docs] self._session = session
[docs] self.log = session.log
[docs] self.db = db
[docs] self.schema = schema
[docs] self._prefix = None
[docs] def register(self, session, prefix, options): self._prefix = prefix return session.register(self, prefix=prefix, options=options)
@wamp.register(None)
[docs] def list_organizations(self, details=None): """ List all organizations accessible for the calling user. An organization is accessible for a user when at least one role was granted. :procedure: ``crossbarfabriccenter.user.list_organizations`` Here is an example: .. code-block:: python async def test_list_organizations(session): oids = await session.call('crossbarfabriccenter.organization.list_organizations') print('got organizations:', oids) return oids :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` :returns: List of OIDs of management realms :rtype: list[str] """ assert details is None or isinstance(details, CallDetails) self.log.info("{klass}.list_organizations(details={details})", klass=self.__class__.__name__, details=details) with self.db.begin() as txn: org_oids = self.schema.organizations.select(txn, return_keys=True, return_values=False) if org_oids: # we now have a list of uuid.UUID objects: convert to strings return [str(oid) for oid in org_oids] else: return []
@wamp.register(None)
[docs] def get_organization(self, org_oid, details=None): """ Return the organization definition by object OID. When no such object exists, an error will be returned. :procedure: ``crossbarfabriccenter.user.get_organization`` :error: ``crossbar.error.no_such_object`` Here is a typical example, fetching all organization (accessible by the user): .. code-block:: python async def test_get_all_orgs(session): oids = await session.call('crossbarfabriccenter.user.get_organizations') orgs = [] if oids: for oid in oids: org = await session.call('crossbarfabriccenter.user.get_organization', oid) print('got organization {}: {}'.format(org.oid, org)) orgs.append(org) return orgs :param org_oid: OID of the organization to get :type org_oid: str :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` :returns: The organization database object (marshaled instance) :rtype: :class:`cfxdb.user.Organization` """ assert isinstance(org_oid, str) assert details is None or isinstance(details, CallDetails) self.log.info( "{klass}.get_organization(org_oid={org_oid}, details={details})", org_oid=org_oid, klass=self.__class__.__name__, details=details, ) org_oid = uuid.UUID(org_oid) with self.db.begin() as txn: org = self.schema.organizations[txn, org_oid] if org: self.log.info("Organization loaded:\n{org}", org=org) return org.marshal() else: raise ApplicationError("crossbar.error.no_such_object", "no organization with oid {}".format(org_oid))
@wamp.register(None)
[docs] async def create_organization(self, organization, details=None): """ Create and store a new organization. :param organization: :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` """ assert isinstance(organization, dict) assert details is None or isinstance(details, CallDetails) self.log.info( "{klass}.create_organization(organization={organization}, details={details})", klass=self.__class__.__name__, organization=organization, details=details, ) obj = Organization.parse(organization) obj.oid = uuid.uuid4() with self.db.begin(write=True) as txn: self.schema.organizations[txn, obj.oid] = obj self.log.info("new Organization object stored in database:\n{obj}", obj=obj) res_obj = obj.marshal() await self._session.publish("{}on_organization_created".format(self._prefix), res_obj, options=self._PUBOPTS) self.log.info("Management API event <on_organization_created> published:\n{res_obj}", res_obj=res_obj) return res_obj
@wamp.register(None)
[docs] async def modify_organization(self, org_oid, org_delta, details=None): """ Modify an existing organization. :param org_oid: :param org_delta: :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` """ raise NotImplementedError()
@wamp.register(None)
[docs] async def delete_organization(self, org_oid, cascade=False, details=None): """ Delete an organization. :param org_oid: :param cascade: :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` """ assert isinstance(org_oid, str) assert details is None or isinstance(details, CallDetails) self.log.info("{klass}.delete_organization(details={details})", klass=self.__class__.__name__, details=details) try: oid = uuid.UUID(org_oid) except Exception as e: raise ApplicationError("wamp.error.invalid_argument", 'invalid oid "{}"'.format(str(e))) with self.db.begin(write=True) as txn: obj = self.schema.organizations[txn, oid] if obj: del self.schema.organizations[txn, oid] else: raise ApplicationError("crossbar.error.no_such_object", "no object with oid {} found".format(oid)) self.log.info("Organization object deleted from database:\n{obj}", obj=obj) res_obj = obj.marshal() await self._session.publish("{}on_organization_deleted".format(self._prefix), res_obj, options=self._PUBOPTS) return res_obj
@wamp.register(None)
[docs] def get_user(self, user_id, details=None): """ Get a particular user by OID. :param user_id: OID of the user object to retrieve. :type user_id: str :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` """ assert isinstance(user_id, str) assert details is None or isinstance(details, CallDetails) self.log.info( "{klass}.get_user(user_id={user_id}, details={details})", user_id=user_id, klass=self.__class__.__name__, details=details, ) user_id = uuid.UUID(user_id) with self.db.begin() as txn: user = self.schema.users[txn, user_id] if user: return user.marshal() else: raise ApplicationError("crossbar.error.no_such_object", "no user with oid {}".format(user_id))
@wamp.register(None)
[docs] def get_user_by_pubkey(self, pubkey, details=None): """ Get a user by public key. :param pubkey: Public key of the user to retrieve. :type pubkey: str :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` """ assert isinstance(pubkey, str) assert details is None or isinstance(details, CallDetails) with self.db.begin() as txn: user_id = self.schema.idx_users_by_pubkey[txn, pubkey] if user_id: user = self.schema.users[txn, user_id] assert user return user.marshal() else: raise ApplicationError("crossbar.error.no_such_object", "no user with pubkey {}".format(pubkey))
@wamp.register(None)
[docs] def get_user_by_email(self, email, details=None): """ :param email: Email (name) of the user to retrieve. :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` """ assert isinstance(email, str) assert details is None or isinstance(details, CallDetails) with self.db.begin() as txn: user_id = self.schema.idx_users_by_email[txn, email] if user_id: user = self.schema.users[txn, user_id] assert user return user.marshal() else: raise ApplicationError("crossbar.error.no_such_object", 'no user with email "{}"'.format(email))
@wamp.register(None)
[docs] async def modify_user(self, user_id, user_delta, details=None): """ Modify an existing user. :procedure: ``crossbarfabriccenter.user.modify_user`` :event: ``crossbarfabriccenter.user.on_user_modified`` :error: ``crossbar.error.no_such_object`` :param user_id: The user object to modify. :type user_id: str :param user_delta: The modification object with attributes present for values to be modified. :type user_delta: marshaled instance of :class:`cfxdb.user.User` :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` :returns: A change object which with attributes and values present the reflect the actual modification that was made. :rtype: marshaled instance of :class:`cfxdb.user.User` """ raise NotImplementedError()
@wamp.register(None)
[docs] def list_users(self, details=None): """ List users. :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` """ assert details is None or isinstance(details, CallDetails) self.log.info("{klass}.list_users(details={details})", klass=self.__class__.__name__, details=details) with self.db.begin() as txn: users = [str(user_id) for user_id in self.schema.users.select(txn, return_values=False)] return users
@wamp.register(None)
[docs] async def list_users_by_organization(self, org_id, details=None): """ List users with at least one role assigned on an organization. :param org_id: The organization to list users for. :type org_id: str :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` """ raise NotImplementedError()
@wamp.register(None)
[docs] async def list_organizations_by_user(self, user_id, details=None): """ List organizations with roles assigned for a given user. :param user_id: The user to list organizations for. :type user_id: str :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` """ raise NotImplementedError()
@wamp.register(None)
[docs] async def set_roles_on_organization_for_user(self, org_id, user_id, roles, details=None): """ Set roles for a user on an organization. :param org_id: :type org_id: str :param user_id: :type user_id: str :param roles: :type roles: list[str] :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` """ raise NotImplementedError()
@wamp.register(None)
[docs] async def get_roles_on_organization_for_user(self, org_id, user_id, details=None): """ Get roles for a user on an organization. :param org_id: :type org_id: str :param user_id: :type user_id: str :param details: Call details :type details: :class:`autobahn.wamp.types.CallDetails` :returns: Roles a user currently has on the organization. :rtype: list[str] """ raise NotImplementedError()