def hello(self, realm, details): # remember the realm the client requested to join (if any) self._realm = realm # remember the authid the client wants to identify as (if any) self._authid = details.authid # WAMP-Ticket "static" if self._config[u"type"] == u"static": self._authprovider = u"static" self._authid = util.generate_serial_number() # FIXME: if cookie tracking is enabled, set authid to cookie value # self._authid = self._transport._cbtid principal = self._config error = self._assign_principal(principal) if error: return error return self._accept() # WAMP-Ticket "dynamic" elif self._config[u"type"] == u"dynamic": self._authprovider = u"dynamic" self._authid = util.generate_serial_number() error = self._init_dynamic_authenticator() if error: return error self._session_details[u"authmethod"] = self._authmethod # from AUTHMETHOD, via base self._session_details[u"authextra"] = details.authextra d = self._authenticator_session.call(self._authenticator, self._realm, self._authid, self._session_details) def on_authenticate_ok(principal): error = self._assign_principal(principal) if error: return error return self._accept() def on_authenticate_error(err): return self._marshal_dynamic_authenticator_error(err) d.addCallbacks(on_authenticate_ok, on_authenticate_error) return d else: # should not arrive here, as config errors should be caught earlier return types.Deny( message=u'invalid authentication configuration (authentication type "{}" is unknown)'.format( self._config["type"] ) )
def hello(self, realm, details): # remember the realm the client requested to join (if any) self._realm = realm # remember the authid the client wants to identify as (if any) self._authid = details.authid # WAMP-Ticket "static" if self._config[u'type'] == u'static': self._authprovider = u'static' self._authid = util.generate_serial_number() # FIXME: if cookie tracking is enabled, set authid to cookie value # self._authid = self._transport._cbtid principal = self._config error = self._assign_principal(principal) if error: return error return self._accept() # WAMP-Ticket "dynamic" elif self._config[u'type'] == u'dynamic': self._authprovider = u'dynamic' self._authid = util.generate_serial_number() error = self._init_dynamic_authenticator() if error: return error d = self._authenticator_session.call(self._authenticator, self._realm, self._authid, self._session_details) def on_authenticate_ok(principal): error = self._assign_principal(principal) if error: return error return self._accept() def on_authenticate_error(err): return self._marshal_dynamic_authenticator_error(err) d.addCallbacks(on_authenticate_ok, on_authenticate_error) return d else: # should not arrive here, as config errors should be caught earlier return types.Deny( message= u'invalid authentication configuration (authentication type "{}" is unknown)' .format(self._config['type']))
def fill_session(session): session.session = util.id() session.joined_at = time_ns() - 723 * 10**9 session.left_at = time_ns() session.realm = 'realm-{}'.format(uuid.uuid4()) session.authid = util.generate_serial_number() session.authrole = random.choice(['admin', 'user*', 'guest', 'anon*'])
def hello(self, realm, details): # remember the realm the client requested to join (if any) self._realm = realm # remember the authid the client wants to identify as (if any) self._authid = details.authid or util.generate_serial_number() self._session_details[u'authmethod'] = u'anonymous' self._session_details[u'authextra'] = details.authextra # WAMP-anonymous "static" if self._config[u'type'] == u'static': self._authprovider = u'static' # FIXME: if cookie tracking is enabled, set authid to cookie value # self._authid = self._transport._cbtid principal = { u'authid': self._authid, u'role': details.authrole or self._config.get(u'role', u'anonymous'), u'extra': details.authextra } error = self._assign_principal(principal) if error: return error return self._accept() # WAMP-Ticket "dynamic" elif self._config[u'type'] == u'dynamic': self._authprovider = u'dynamic' error = self._init_dynamic_authenticator() if error: return error d = self._authenticator_session.call(self._authenticator, self._realm, self._authid, self._session_details) def on_authenticate_ok(principal): error = self._assign_principal(principal) if error: return error return self._accept() def on_authenticate_error(err): return self._marshal_dynamic_authenticator_error(err) d.addCallbacks(on_authenticate_ok, on_authenticate_error) return d else: # should not arrive here, as config errors should be caught earlier return types.Deny(message=u'invalid authentication configuration (authentication type "{}" is unknown)'.format(self._config['type']))
def onHello(self, realm, details): try: # default authentication method is "WAMP-Anonymous" if client doesn't specify otherwise authmethods = details.authmethods or [u'anonymous'] # if the client had a reassigned realm during authentication, restore it from the cookie if hasattr(self._transport, '_authrealm') and self._transport._authrealm: realm = self._transport._authrealm # perform authentication if self._transport._authid is not None and ( self._transport._authmethod == u'trusted' or self._transport._authprovider in authmethods): # already authenticated .. e.g. via HTTP Cookie or TLS client-certificate # check if role still exists on realm allow = self._router_factory[realm].has_role( self._transport._authrole) if allow: return types.Accept( realm=realm, authid=self._transport._authid, authrole=self._transport._authrole, authmethod=self._transport._authmethod, authprovider=self._transport._authprovider, authextra=None) else: return types.Deny( ApplicationError.NO_SUCH_ROLE, message= "session was previously authenticated (via transport), but role '{}' no longer exists on realm '{}'" .format(self._transport._authrole, realm)) else: auth_config = self._transport_config.get(u'auth', None) if not auth_config: # if authentication is _not_ configured, allow anyone to join as "anonymous"! # .. but don't if the client isn't ready/willing to go on "anonymous" if u'anonymous' not in authmethods: return types.Deny( ApplicationError.NO_AUTH_METHOD, message= u'cannot authenticate using any of the offered authmethods {}' .format(authmethods)) if not realm: return types.Deny(ApplicationError.NO_SUCH_REALM, message=u'no realm requested') if realm not in self._router_factory: return types.Deny( ApplicationError.NO_SUCH_REALM, message=u'no realm "{}" exists on this router'. format(realm)) # we ignore any details.authid the client might have announced, and use # a cookie value or a random value if hasattr(self._transport, "_cbtid") and self._transport._cbtid: # if cookie tracking is enabled, set authid to cookie value authid = self._transport._cbtid else: # if no cookie tracking, generate a random value for authid authid = util.generate_serial_number() return types.Accept(realm=realm, authid=authid, authrole=u'anonymous', authmethod=u'anonymous', authprovider=u'static', authextra=None) else: # iterate over authentication methods announced by client .. for authmethod in authmethods: # invalid authmethod if authmethod not in AUTHMETHODS: return types.Deny( message=u'invalid authmethod "{}"'.format( authmethod)) # authmethod not configured if authmethod not in auth_config: self.log.debug( "client requested valid, but unconfigured authentication method {authmethod}", authmethod=authmethod) continue # authmethod not available if authmethod not in AUTHMETHOD_MAP: self.log.debug( "client requested valid, but unavailable authentication method {authmethod}", authmethod=authmethod) continue # WAMP-Anonymous, WAMP-Ticket, WAMP-CRA, WAMP-TLS, WAMP-Cryptosign if authmethod in [ u'anonymous', u'ticket', u'wampcra', u'tls', u'cryptosign' ]: PendingAuthKlass = AUTHMETHOD_MAP[authmethod] self._pending_auth = PendingAuthKlass( self, auth_config[authmethod]) return self._pending_auth.hello(realm, details) # WAMP-Cookie authentication elif authmethod == u'cookie': # the client requested cookie authentication, but there is 1) no cookie set, # or 2) a cookie set, but that cookie wasn't authenticated before using # a different auth method (if it had been, we would never have entered here, since then # auth info would already have been extracted from the transport) # consequently, we skip this auth method and move on to next auth method. continue else: # should not arrive here raise Exception("logic error") # no suitable authmethod found! return types.Deny( ApplicationError.NO_AUTH_METHOD, message= u'cannot authenticate using any of the offered authmethods {}' .format(authmethods)) except Exception as e: self.log.failure('internal error: {log_failure.value}') self.log.critical("internal error: {msg}", msg=str(e)) return types.Deny(message=u'internal error: {}'.format(e))
def onHello(self, realm, details): try: # default authentication method is "WAMP-Anonymous" if client doesn't specify otherwise authmethods = details.authmethods or [u'anonymous'] # if the client had a reassigned realm during authentication, restore it from the cookie if hasattr(self._transport, '_authrealm') and self._transport._authrealm: realm = self._transport._authrealm # perform authentication if self._transport._authid is not None and (self._transport._authmethod == u'trusted' or self._transport._authprovider in authmethods): # already authenticated .. e.g. via HTTP Cookie or TLS client-certificate # check if role still exists on realm allow = self._router_factory[realm].has_role(self._transport._authrole) if allow: return types.Accept(realm=realm, authid=self._transport._authid, authrole=self._transport._authrole, authmethod=self._transport._authmethod, authprovider=self._transport._authprovider, authextra=None) else: return types.Deny(ApplicationError.NO_SUCH_ROLE, message="session was previously authenticated (via transport), but role '{}' no longer exists on realm '{}'".format(self._transport._authrole, realm)) else: auth_config = self._transport_config.get(u'auth', None) if not auth_config: # if authentication is _not_ configured, allow anyone to join as "anonymous"! # .. but don't if the client isn't ready/willing to go on "anonymous" if u'anonymous' not in authmethods: return types.Deny(ApplicationError.NO_AUTH_METHOD, message=u'cannot authenticate using any of the offered authmethods {}'.format(authmethods)) if not realm: return types.Deny(ApplicationError.NO_SUCH_REALM, message=u'no realm requested') if realm not in self._router_factory: return types.Deny(ApplicationError.NO_SUCH_REALM, message=u'no realm "{}" exists on this router'.format(realm)) # we ignore any details.authid the client might have announced, and use # a cookie value or a random value if self._transport._cbtid: # if cookie tracking is enabled, set authid to cookie value authid = self._transport._cbtid else: # if no cookie tracking, generate a random value for authid authid = util.generate_serial_number() return types.Accept(realm=realm, authid=authid, authrole=u'anonymous', authmethod=u'anonymous', authprovider=u'static', authextra=None) else: # iterate over authentication methods announced by client .. for authmethod in authmethods: # invalid authmethod if authmethod not in AUTHMETHODS: return types.Deny(message=u'invalid authmethod "{}"'.format(authmethod)) # authmethod not configured if authmethod not in auth_config: self.log.debug("client requested valid, but unconfigured authentication method {authmethod}", authmethod=authmethod) continue # authmethod not available if authmethod not in AUTHMETHOD_MAP: self.log.debug("client requested valid, but unavailable authentication method {authmethod}", authmethod=authmethod) continue # WAMP-Anonymous, WAMP-Ticket, WAMP-CRA, WAMP-TLS, WAMP-Cryptosign if authmethod in [u'anonymous', u'ticket', u'wampcra', u'tls', u'cryptosign']: PendingAuthKlass = AUTHMETHOD_MAP[authmethod] self._pending_auth = PendingAuthKlass(self, auth_config[authmethod]) return self._pending_auth.hello(realm, details) # WAMP-Cookie authentication elif authmethod == u'cookie': # the client requested cookie authentication, but there is 1) no cookie set, # or 2) a cookie set, but that cookie wasn't authenticated before using # a different auth method (if it had been, we would never have entered here, since then # auth info would already have been extracted from the transport) # consequently, we skip this auth method and move on to next auth method. continue else: # should not arrive here raise Exception("logic error") # no suitable authmethod found! return types.Deny(ApplicationError.NO_AUTH_METHOD, message=u'cannot authenticate using any of the offered authmethods {}'.format(authmethods)) except Exception as e: self.log.critical("Internal error: {msg}", msg=str(e)) return types.Deny(message=u'internal error: {}'.format(e))
def hello(self, realm: str, details: types.SessionDetails): self.log.info( '{func}(realm={realm}, details.realm={authrealm}, details.authid={authid}, details.authrole={authrole}) [config={config}]', func=hltype(self.hello), realm=hlid(realm), authrealm=hlid(details.realm), authid=hlid(details.authid), authrole=hlid(details.authrole), config=self._config) # remember the realm the client requested to join (if any) self._realm = realm self._authid = self._config.get('authid', util.generate_serial_number()) self._session_details['authmethod'] = 'anonymous' self._session_details['authextra'] = details.authextra # WAMP-anonymous "static" if self._config['type'] == 'static': self._authprovider = 'static' # FIXME: if cookie tracking is enabled, set authid to cookie value # self._authid = self._transport._cbtid principal = { 'realm': realm, 'authid': self._authid, 'role': self._config.get('role', 'anonymous'), 'extra': details.authextra } error = self._assign_principal(principal) if error: return error return self._accept() # WAMP-Ticket "dynamic" elif self._config['type'] == 'dynamic': self._authprovider = 'dynamic' init_d = as_future(self._init_dynamic_authenticator) def init(result): if result: return result d = self._authenticator_session.call(self._authenticator, self._realm, self._authid, self._session_details) def on_authenticate_ok(principal): error = self._assign_principal(principal) if error: return error return self._accept() def on_authenticate_error(err): return self._marshal_dynamic_authenticator_error(err) d.addCallbacks(on_authenticate_ok, on_authenticate_error) return d init_d.addBoth(init) return init_d else: # should not arrive here, as config errors should be caught earlier return types.Deny(message='invalid authentication configuration (authentication type "{}" is unknown)'. format(self._config['type']))
async def _authenticate(self, realm, authid, details, call_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 != '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: msg = 'Must provide authextra for authmethod cryptosign' raise ApplicationError( self.ERROR_INVALID_AUTH_REQUEST, self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg)) authextra = details['authextra'] if 'pubkey' not in 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 = authextra['pubkey'] if isinstance(pubkey, str): pubkey = binascii.a2b_hex(without_0x(pubkey)) assert is_cs_pubkey(pubkey) session_id = details['session'] assert type(session_id) == int # FIXME: find a more elegant way to query the db. def get_actor(_txn, address): _actor = self._schema.actors[_txn, (self._market_oid, address, ActorType.PROVIDER)] if _actor: return _actor _actor = self._schema.actors[_txn, (self._market_oid, address, ActorType.CONSUMER)] if _actor: return _actor _actor = self._schema.actors[_txn, (self._market_oid, address, ActorType.PROVIDER_CONSUMER)] if _actor: return _actor if ('wallet_address' not in authextra or not authextra['wallet_address']) and \ ('signature' not in authextra or not authextra['signature']): with self._db.begin() as txn: user_key = self._xbrmm.user_keys[txn, pubkey] actor = None if user_key: actor = get_actor(txn, bytes(user_key.wallet_address)) if actor: authrole = 'user' authid = 'member-{}'.format( binascii.b2a_hex(user_key.wallet_address).decode()) else: authrole = 'anonymous' authid = 'anonymous-{}'.format( generate_serial_number()) else: authrole = 'anonymous' authid = 'anonymous-{}'.format(generate_serial_number()) self._pubkey_by_session[session_id] = pubkey auth = { 'pubkey': binascii.b2a_hex(pubkey), 'realm': realm, 'authid': authid, 'role': authrole, 'cache': True, 'extra': { 'actor_type': actor.actor_type if actor else 0 } } self.log.info('{klass}.authenticate(..) => {auth}', klass=self.__class__.__name__, auth=auth) return auth if ('wallet_address' not in authextra or not authextra['wallet_address']) or \ ('signature' not in authextra or not authextra['signature']): msg = 'Should provide `pubkey`, `wallet_address` and `signature` in authextra ' \ 'to authenticate new member. To authenticate existing member, only provide ' \ '`pubkey`' raise ApplicationError( self.ERROR_INVALID_AUTH_REQUEST, self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg)) wallet_address = authextra['wallet_address'] assert is_address(wallet_address) signature = authextra['signature'] assert is_signature(signature) try: signer_address = recover_eip712_market_member_login( wallet_address, pubkey, signature) except Exception as e: self.log.warn( 'EIP712 signature recovery failed (wallet_adr={wallet_adr}): {err}', wallet_adr=wallet_address, err=str(e)) raise ApplicationError( 'xbr.error.invalid_signature', 'EIP712 signature recovery failed ({})'.format(e)) if signer_address != wallet_address: self.log.warn( 'EIP712 signature invalid: signer_address={signer_address}, wallet_adr={wallet_adr}', signer_address=signer_address, wallet_adr=wallet_address) raise ApplicationError('xbr.error.invalid_signature', 'EIP712 signature invalid') with self._db.begin(write=True) as txn: account = self._schema.members[txn, wallet_address] actor = None if account: actor = get_actor(txn, wallet_address) if actor: user_key = self._xbrmm.user_keys[txn, pubkey] if not user_key: user_key = cfxdb.xbrmm.UserKey() user_key.owner = account.account_oid user_key.pubkey = pubkey user_key.created = np.datetime64(txaio.time_ns(), 'ns') user_key.wallet_address = wallet_address user_key.signature = signature self._xbrmm.user_keys[txn, pubkey] = user_key self._pubkey_by_session[session_id] = pubkey authrole = 'user' # authid = 'member-{}'.format(account.account_oid) # account.account_oid returns a pseudo value because # the "emit" from the xbr contracts does not include # account_oid in it, hence we don't really have that. # To compensate that, we could include wallet address # in authid, so that API calls could validate # if the caller really is the "owner" of a resource. authid = 'member-{}'.format( binascii.b2a_hex(wallet_address).decode()) else: authrole = 'anonymous' authid = 'anonymous-{}'.format(generate_serial_number()) else: authrole = 'anonymous' authid = 'anonymous-{}'.format(generate_serial_number()) auth = { 'pubkey': binascii.b2a_hex(pubkey), 'realm': realm, 'authid': authid, 'role': authrole, 'cache': True, 'extra': { 'actor_type': actor.actor_type if actor else 0 } } self.log.info('{klass}.authenticate(..) => {auth}', klass=self.__class__.__name__, auth=auth) return auth
def start_link(self, link_id, link_config, caller): assert type(link_id) == str assert isinstance(link_config, RLinkConfig) assert isinstance(caller, SessionIdent) if link_id in self._links: raise ApplicationError('crossbar.error.already_running', 'router link {} already running'.format(link_id)) # setup local session # local_extra = { 'other': None, 'on_ready': Deferred(), 'rlink': link_id, 'forward_events': link_config.forward_local_events, 'forward_invocations': link_config.forward_local_invocations, } local_realm = self._realm.config['name'] local_authid = link_config.authid or util.generate_serial_number() local_authrole = 'trusted' local_config = ComponentConfig(local_realm, local_extra) local_session = RLinkLocalSession(local_config) # setup remote session # remote_extra = { 'rlink_manager': self, 'other': None, 'on_ready': Deferred(), 'authid': link_config.authid, 'exclude_authid': link_config.exclude_authid, 'forward_events': link_config.forward_remote_events, 'forward_invocations': link_config.forward_remote_invocations, } remote_realm = link_config.realm remote_config = ComponentConfig(remote_realm, remote_extra) remote_session = RLinkRemoteSession(remote_config) # cross-connect the two sessions # local_extra['other'] = remote_session remote_extra['other'] = local_session # the rlink # rlink = RLink(link_id, link_config) self._links[link_id] = rlink local_extra['tracker'] = rlink # create connecting client endpoint # connecting_endpoint = create_connecting_endpoint_from_config( link_config.transport['endpoint'], self._controller.cbdir, self._controller._reactor, self.log) try: # connect the local session # self._realm.controller._router_session_factory.add( local_session, self._realm.router, authid=local_authid, authrole=local_authrole, authextra=local_extra) yield local_extra['on_ready'] # connect the remote session # # remote connection parameters to ApplicationRunner: # # url: The WebSocket URL of the WAMP router to connect to (e.g. ws://somehost.com:8090/somepath) # realm: The WAMP realm to join the application session to. # extra: Optional extra configuration to forward to the application component. # serializers: List of :class:`autobahn.wamp.interfaces.ISerializer` (or None for default serializers). # ssl: None or :class:`twisted.internet.ssl.CertificateOptions` # proxy: Explicit proxy server to use; a dict with ``host`` and ``port`` keys # headers: Additional headers to send (only applies to WAMP-over-WebSocket). # max_retries: Maximum number of reconnection attempts. Unlimited if set to -1. # initial_retry_delay: Initial delay for reconnection attempt in seconds (Default: 1.0s). # max_retry_delay: Maximum delay for reconnection attempts in seconds (Default: 60s). # retry_delay_growth: The growth factor applied to the retry delay between reconnection attempts (Default 1.5). # retry_delay_jitter: A 0-argument callable that introduces nose into the delay. (Default random.random) # remote_runner = ApplicationRunner( url=link_config.transport['url'], realm=remote_realm, extra=remote_extra) yield remote_runner.run( remote_session, start_reactor=False, auto_reconnect=True, endpoint=connecting_endpoint, reactor=self._controller._reactor) yield remote_extra['on_ready'] except: # make sure to remove the half-initialized link from our map .. del self._links[link_id] # .. and then re-raise raise # the router link is established: store final infos rlink.started = time_ns() rlink.started_by = caller rlink.local = local_session rlink.remote = remote_session return rlink
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 type(pubkey_raw) == bytes and len(pubkey_raw) == 32 session_id = details['session'] assert type(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
def hello(self, realm, details): # remember the realm the client requested to join (if any) self._realm = realm self._authid = self._config.get('authid', util.generate_serial_number()) self._session_details['authmethod'] = 'anonymous' self._session_details['authextra'] = details.authextra # WAMP-anonymous "static" if self._config['type'] == 'static': self._authprovider = 'static' # FIXME: if cookie tracking is enabled, set authid to cookie value # self._authid = self._transport._cbtid principal = { 'authid': self._authid, 'role': self._config.get('role', 'anonymous'), 'extra': details.authextra } error = self._assign_principal(principal) if error: return error return self._accept() # WAMP-Ticket "dynamic" elif self._config['type'] == 'dynamic': self._authprovider = 'dynamic' error = self._init_dynamic_authenticator() if error: return error d = self._authenticator_session.call(self._authenticator, self._realm, self._authid, self._session_details) def on_authenticate_ok(principal): error = self._assign_principal(principal) if error: return error return self._accept() def on_authenticate_error(err): return self._marshal_dynamic_authenticator_error(err) d.addCallbacks(on_authenticate_ok, on_authenticate_error) return d else: # should not arrive here, as config errors should be caught earlier return types.Deny( message= 'invalid authentication configuration (authentication type "{}" is unknown)' .format(self._config['type']))
def _process_Hello(self, msg): """ We have received a Hello from the frontend client. Now we do any authentication necessary with them and connect to our backend. """ self.log.info('{klass}._process_Hello(msg={msg})', klass=self.__class__.__name__, msg=msg) self._pending_session_id = util.id() self._goodbye_sent = False extra_auth_methods = self._controller.personality.EXTRA_AUTH_METHODS # allow "Personality" classes to add authmethods authmethods = list(extra_auth_methods.keys()) + (msg.authmethods or ['anonymous']) # if the client had a reassigned realm during authentication, restore it from the cookie if hasattr(self.transport, '_authrealm') and self.transport._authrealm: if 'cookie' in authmethods: realm = self.transport._authrealm # noqa authextra = self.transport._authextra # noqa elif self.transport._authprovider == 'cookie': # revoke authentication and invalidate cookie (will be revalidated if following auth is successful) self.transport._authmethod = None self.transport._authrealm = None self.transport._authid = None if hasattr(self.transport, '_cbtid'): self.transport.factory._cookiestore.setAuth(self.transport._cbtid, None, None, None, None, None) else: pass # TLS authentication is not revoked here # already authenticated, eg via HTTP-cookie or TLS-client-certificate authentication if self.transport._authid is not None and (self.transport._authmethod == 'trusted' or self.transport._authprovider in authmethods): msg.realm = self.transport._realm msg.authid = self.transport._authid msg.authrole = self.transport._authrole details = types.HelloDetails( realm=msg.realm, authmethods=authmethods, authid=msg.authid, authrole=msg.authrole, authextra=msg.authextra, session_roles=msg.roles, pending_session=self._pending_session_id ) auth_config = self._transport_config.get('auth', None) # if authentication is _not_ configured, allow anyone to join as "anonymous"! if not auth_config: # we ignore any details.authid the client might have announced, and use # a cookie value or a random value if hasattr(self.transport, "_cbtid") and self.transport._cbtid: # if cookie tracking is enabled, set authid to cookie value authid = self.transport._cbtid else: # if no cookie tracking, generate a random value for authid authid = util.generate_serial_number() auth_config = { 'anonymous': { 'type': 'static', 'authrole': 'anonymous', 'authid': authid, } } self.log.warn('No authentication configured for proxy frontend: using default anonymous access policy for incoming proxy frontend session') for authmethod in authmethods: # invalid authmethod if authmethod not in AUTHMETHOD_MAP and authmethod not in extra_auth_methods: self.transport.send(message.Abort(ApplicationError.AUTHENTICATION_FAILED, message='authmethod "{}" not allowed'.format(authmethod))) return # authmethod is valid, but not configured: continue trying other authmethods the client is announcing if authmethod not in auth_config: continue # authmethod not available if authmethod not in AUTHMETHOD_MAP and authmethod not in extra_auth_methods: self.log.debug("client requested valid, but unavailable authentication method {authmethod}", authmethod=authmethod) continue # create instance of authenticator using authenticator class for the respective authmethod authklass = extra_auth_methods[authmethod] if authmethod in extra_auth_methods else AUTHMETHOD_MAP[authmethod] self._pending_auth = authklass( self._pending_session_id, self.transport._transport_info, self._controller, auth_config[authmethod], ) try: # call into authenticator for processing the HELLO message hello_result = self._pending_auth.hello(msg.realm, details) except Exception as e: self.log.failure() self.transport.send(message.Abort(ApplicationError.AUTHENTICATION_FAILED, message='Frontend connection accept failed ({})'.format(e))) return self.log.info('{klass}._process_Hello() processed authmethod "{authmethod}" using {authklass}: {hello_result}', klass=self.__class__.__name__, authmethod=authmethod, authklass=authklass, hello_result=hello_result) # if the frontend session is accepted right away (eg when doing "anonymous" authentication), process the # frontend accept .. if isinstance(hello_result, types.Accept): try: # get a backend session mapped to the incoming frontend session session = yield self._accept(hello_result) except Exception as e: self.log.failure() self.transport.send(message.Abort(ApplicationError.AUTHENTICATION_FAILED, message='Frontend connection accept failed ({})'.format(e))) return def _on_backend_joined(session, details): # we now got everything! the frontend is authenticated, and a backend session is associated. msg = message.Welcome(self._session_id, ProxyFrontendSession.ROLES, realm=details.realm, authid=details.authid, authrole=details.authrole, authmethod=hello_result.authmethod, authprovider=hello_result.authprovider, authextra=dict(details.authextra or {}, **self._custom_authextra)) self._backend_session = session self.transport.send(msg) self.log.info('Proxy frontend session WELCOME: session_id={session}, session={session}, session_details={details}', session_id=hlid(self._session_id), session=self, details=details) session.on('join', _on_backend_joined) # if the client is required to do an authentication message exchange, answer sending a CHALLENGE message elif isinstance(hello_result, types.Challenge): self.transport.send(message.Challenge(hello_result.method, extra=hello_result.extra)) # if the client is denied right away, answer by sending an ABORT message elif isinstance(hello_result, types.Deny): self.transport.send(message.Abort(hello_result.reason, message=hello_result.message)) # should not arrive here: internal (logic) error else: self.transport.send(message.Abort(ApplicationError.AUTHENTICATION_FAILED, message='internal error: unexpected authenticator return type {}'.format(type(hello_result)))) return self.transport.send(message.Abort(ApplicationError.NO_AUTH_METHOD, message='no suitable authmethod found'))