Source code for crossbar.network._authenticator

# coding=utf8

##############################################################################
#
#                        Crossbar.io
#     Copyright (C) typedef int GmbH. All rights reserved.
#
##############################################################################

import binascii
import os
from typing import Optional

import zlmdb
from autobahn.twisted.wamp import ApplicationSession
from autobahn.util import generate_serial_number
from autobahn.wamp import register
from autobahn.wamp.exception import ApplicationError
from autobahn.wamp.types import CallDetails
from cfxdb.xbrnetwork.schema import Schema


[docs] class Authenticator(ApplicationSession):
[docs] ERROR_INVALID_AUTH_REQUEST = "xbr.network.error.invalid-auth-request"
[docs] ERROR_INVALID_AUTH_REQUEST_MSG = "invalid parameter(s) in authentication request: {}"
def __init__(self, config): ApplicationSession.__init__(self, config) # ZLMDB database configuration #
[docs] self._dbpath = os.path.abspath(config.extra.get("dbpath", "./.xbrnetwork"))
# self._db = zlmdb.Database(dbpath=self._dbpath, maxsize=2**30, readonly=False, sync=True, context=self)
[docs] self._db = zlmdb.Database.open(dbpath=self._dbpath, maxsize=2**30, readonly=False, sync=True, context=self)
self._db.__enter__()
[docs] self._schema = Schema.attach(self._db)
[docs] self._pubkey_by_session = {}
[docs] self._member_by_session = {}
[docs] self._sessions_by_member = {}
with self._db.begin() as txn: cnt_user_keys = self._schema.user_keys.count(txn) self.log.info( "Database opened from {dbpath} (cnt_user_keys={cnt_user_keys})", dbpath=self._dbpath, cnt_user_keys=cnt_user_keys, )
[docs] async def onJoin(self, details): regs = await self.register(self) for reg in regs: self.log.info("{klass} registered procedure {proc}", klass=self.__class__.__name__, proc=reg.procedure)
@register("xbr.network.authenticator.sessions_by_member") def _sessions_by_member(self, member_oid: bytes, details: Optional[CallDetails] = None) -> int: return self._sessions_by_member.get(member_oid, None) @register("xbr.network.authenticator.member_by_session") def _pubkey_by_session(self, session_id: int, details: Optional[CallDetails] = None) -> bytes: return self._member_by_session.get(session_id, None) @register("xbr.network.authenticator.pubkey_by_session") def _member_by_session(self, session_id: int, details: Optional[CallDetails] = None) -> bytes: return self._pubkey_by_session.get(session_id, None) @register("xbr.network.authenticator.authenticate")
[docs] async def _authenticate(self, realm, authid, details): self.log.info( '{klass}.authenticate(realm="{realm}", authid="{authid}", details={details})', klass=self.__class__.__name__, realm=realm, authid=authid, details=details, ) if "authmethod" not in details: msg = 'missing "authmethod" in authentication details (WAMP HELLO message details)' raise ApplicationError(self.ERROR_INVALID_AUTH_REQUEST, self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg)) authmethod = details["authmethod"] if authmethod not in ["cryptosign"]: msg = 'authmethod "{}" not permissible'.format(authmethod) raise ApplicationError(self.ERROR_INVALID_AUTH_REQUEST, self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg)) if "authextra" not in details or "pubkey" not in details["authextra"]: msg = "missing public key in authextra for authmethod cryptosign" raise ApplicationError(self.ERROR_INVALID_AUTH_REQUEST, self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg)) pubkey = details["authextra"]["pubkey"] pubkey_raw = binascii.a2b_hex(pubkey) assert isinstance(pubkey_raw, bytes) and len(pubkey_raw) == 32 session_id = details["session"] assert isinstance(session_id, int) with self._db.begin() as txn: # double check (again) for username collision, as the mailgun email submit happens async in above after # we initially checked for collision user_key = self._schema.user_keys[txn, pubkey_raw] if user_key: account = self._schema.accounts[txn, user_key.owner] authrole = "member" # authid = account.username authid = "member-{}".format(account.oid) else: account = None authrole = "anonymous" authid = "anonymous-{}".format(generate_serial_number()) self._pubkey_by_session[session_id] = pubkey_raw if account: self._member_by_session[session_id] = account.oid if account.oid not in self._sessions_by_member: self._sessions_by_member[account.oid] = [] self._sessions_by_member[account.oid].append(session_id) auth = {"pubkey": pubkey, "realm": realm, "authid": authid, "role": authrole, "extra": None, "cache": True} self.log.info("{klass}.authenticate(..) => {auth}", klass=self.__class__.__name__, auth=auth) return auth