def onAuthenticate(self, signature, extra): """ Callback fired when a client responds to an authentication CHALLENGE. """ self.log.debug("onAuthenticate: {signature} {extra}", signature=signature, extra=extra) # if there is a pending auth, check the challenge response. The specifics # of how to check depend on the authentication method if self._pending_auth: # WAMP-Ticket, WAMP-CRA, WAMP-Cryptosign if isinstance(self._pending_auth, PendingAuthTicket) or \ isinstance(self._pending_auth, PendingAuthWampCra) or \ isinstance(self._pending_auth, PendingAuthCryptosign): return self._pending_auth.authenticate(signature) # should not arrive here: logic error else: self.log.warn( 'unexpected pending authentication {pending_auth}', pending_auth=self._pending_auth) return types.Deny( message=u'internal error: unexpected pending authentication' ) # should not arrive here: client misbehaving! else: return types.Deny(message=u'no pending authentication')
def onAuthenticate(self, signature, extra): """ Callback fired when a client responds to an authentication challenge. """ print("onAuthenticate: {} {}".format(signature, extra)) # if there is a pending auth, and the signature provided by client matches .. if self._pending_auth: if signature == self._pending_auth.signature: # accept the client return types.Accept( authid=self._pending_auth.authid, authrole=self._pending_auth.authrole, authmethod=self._pending_auth.authmethod, authprovider=self._pending_auth.authprovider) else: # deny client return types.Deny(message=u"signature is invalid") else: # deny client return types.Deny(message=u"no pending authentication")
def done(res): res = json.loads(res) try: if res['status'] == 'okay': # awesome: Mozilla Persona successfully authenticated the user self._transport._authid = res['email'] self._transport._authrole = self._pending_auth.role self._transport._authmethod = 'mozilla_persona' log.msg("Authenticated user {} with role {}".format(self._transport._authid, self._transport._authrole)) dres.callback(types.Accept(authid=self._transport._authid, authrole=self._transport._authrole, authmethod=self._transport._authmethod)) # remember the user's auth info (this marks the cookie as authenticated) if self._transport._cbtid and self._transport.factory._cookiestore: cs = self._transport.factory._cookiestore cs.setAuth(self._transport._cbtid, self._transport._authid, self._transport._authrole, self._transport._authmethod) # kick all sessions using same cookie (but not _this_ connection) if True: for proto in cs.getProtos(self._transport._cbtid): if proto and proto != self._transport: try: proto.close() except Exception as e: pass else: log.msg("Authentication failed!") log.msg(res) dres.callback(types.Deny(reason="wamp.error.authorization_failed", message=res.get("reason", None))) except Exception as e: log.msg("internal error during authentication verification: {}".format(e)) dres.callback(types.Deny(reason="wamp.error.internal_error", message=str(e)))
def _marshal_dynamic_authenticator_error(self, err): if isinstance(err.value, ApplicationError): # forward the inner error URI and message (or coerce the first args item to str) return types.Deny(err.value.error, u'{}'.format(err.value.args[0])) else: # wrap the error error = ApplicationError.AUTHENTICATION_FAILED message = u'dynamic authenticator failed: {}'.format(err.value) return types.Deny(error, message)
def authenticate(self, signature): # WAMP-Ticket "static" if self._authprovider == 'static': # when doing WAMP-Ticket from static configuration, the ticket we # expect was previously stored in self._signature if signature == self._signature: # ticket was valid: accept the client self.log.debug("WAMP-Ticket: ticket was valid!") return self._accept() else: # ticket was invalid: deny client self.log.debug( 'WAMP-Ticket (static): expected ticket "{expected}"" ({expected_type}), but got "{sig}" ({sig_type})', expected=self._signature, expected_type=type(self._signature), sig=signature, sig_type=type(signature), ) return types.Deny( message= "ticket in static WAMP-Ticket authentication is invalid") # WAMP-Ticket "dynamic" elif self._authprovider == 'dynamic': self._session_details['ticket'] = signature d = self._authenticator_session.call(self._authenticator, self._realm, self._authid, self._session_details) def on_authenticate_ok(principal): # backwards compatibility: dynamic ticket authenticator # was expected to return a role directly if isinstance(principal, str): principal = {'role': 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 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 # use static principal database from configuration if self._config['type'] == 'static': self._authprovider = 'static' if self._authid in self._config.get('principals', {}): principal = self._config['principals'][self._authid] principal['extra'] = details.authextra error = self._assign_principal(principal) if error: return error # now set set signature as expected for WAMP-Ticket self._signature = principal['ticket'] return types.Challenge(self._authmethod) else: return types.Deny( message='no principal with authid "{}" exists'.format( self._authid)) # use configured procedure to dynamically get a ticket for the principal elif self._config['type'] == 'dynamic': self._authprovider = 'dynamic' init_d = as_future(self._init_dynamic_authenticator) def init(result): if result: return result self._session_details[ 'authmethod'] = self._authmethod # from AUTHMETHOD, via base self._session_details['authextra'] = details.authextra return types.Challenge(self._authmethod) 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']))
def hello(self, realm, details): self.log.debug('{klass}.hello(realm={realm}, details={details}) ...', klass=self.__class__.__name__, realm=realm, details=details) if not details.authextra: return types.Deny(message='missing required details.authextra') for attr in ['proxy_authid', 'proxy_authrole', 'proxy_realm']: if attr not in details.authextra: return types.Deny( message='missing required attribute {} in details.authextra' .format(attr)) if details.authrole is None: details.authrole = details.authextra.get('proxy_authrole', None) if details.authid is None: details.authid = details.authextra.get('proxy_authid', None) # with authentictors of type "*-proxy", the principal returned in authenticating the # incoming backend connection is ignored .. f = super(PendingAuthCryptosignProxy, self).hello(realm, details) def assign(res): """ .. and the incoming backend connection from the proxy frontend is authenticated as the principal the frontend proxy has _already_ authenticated the actual client (before even connecting and authenticating to the backend here) """ if isinstance(res, types.Deny): return res principal = {} principal['realm'] = details.authextra['proxy_realm'] principal['authid'] = details.authextra['proxy_authid'] principal['role'] = details.authextra['proxy_authrole'] principal['extra'] = details.authextra.get('proxy_authextra', None) self._assign_principal(principal) self.log.debug( '{klass}.hello(realm={realm}, details={details}) -> principal={principal}', klass=self.__class__.__name__, realm=realm, details=details, principal=principal, ) return self._accept() def error(f): return types.Deny("Internal error: {}".format(f)) txaio.add_callbacks(f, assign, error) return f
def _init_dynamic_authenticator(self): self._authenticator = self._config['authenticator'] authenticator_realm = None if u'authenticator-realm' in self._config: authenticator_realm = self._config[u'authenticator-realm'] if authenticator_realm not in self._router_factory: return types.Deny(ApplicationError.NO_SUCH_REALM, message=u"explicit realm <{}> configured for dynamic authenticator does not exist".format(authenticator_realm)) else: if not self._realm: return types.Deny(ApplicationError.NO_SUCH_REALM, message=u"client did not specify a realm to join (and no explicit realm was configured for dynamic authenticator)") authenticator_realm = self._realm self._authenticator_session = self._router_factory.get(authenticator_realm)._realm.session
def onAuthenticate(self, router_session, signature, extra): try: challenge = router_session.challenge if challenge == None: return if router_session.challenge.get("authmethod") != u"wampcra": return for field in ["authid", "authrole", "authmethod", "authprovider"]: if field not in challenge: # Challenge not in expected format. It was probably # created by another plugin. return if not router_session.challenge or not router_session.signature: log("Failed wampcra login for %s." % challenge["authid"]) return types.Deny(message=u"No pending authentication.") if len(signature) != len(router_session.signature): log("Failed wampcra login for %s." % challenge["authid"]) return types.Deny(message=u"Invalid signature.") success = True # Check each character to prevent HMAC timing attacks. This is # really not an issue since each challenge gets a new nonce, # but better safe than sorry. for i in range(len(router_session.signature)): if signature[i] != router_session.signature[i]: success = False # Reject the user if we did not actually find them in the database. if not router_session.exists: log("User %s not found." % challenge["authid"]) success = False if success: log("Successful wampcra login for %s." % challenge["authid"]) return types.Accept(authid=challenge["authid"], authrole=challenge["authrole"], authmethod=challenge["authmethod"], authprovider=challenge["authprovider"]) log("Failed wampcra login for %s." % challenge["authid"]) return types.Deny(message=u"Invalid signature.") except: # let another plugin handle this return
def onAuthenticate(self, router_session, signature, extra): if not hasattr(router_session, "totp"): return username = router_session.challenge["authid"].encode("utf8") totp = router_session.totp if totp: if "otp" not in extra: log("TOTP parameter is missing for %s." % username) returnValue(types.Deny(message=u"Missing TOTP.")) success = yield self.administrator.proxy.check_totp( username, extra["otp"].encode("utf-8")) if not success: log("TOTP parameter is invalid for %s." % username) returnValue(types.Deny(message=u"Invalid TOTP.")) log("Successfully verified TOTP for %s." % username)
def error(err): self.log.info("Authentication request failed: {}".format( err.value)) dres.callback( types.Deny( reason="wamp.error.authorization_request_failed", message=str(err.value)))
def hello(self, realm, details): self.log.debug('{klass}.hello(realm={realm}, details={details}) ...', klass=self.__class__.__name__, realm=realm, details=details) extra = details.authextra or {} for attr in ['proxy_authid', 'proxy_authrole', 'proxy_realm']: if attr not in extra: return types.Deny( message='missing required attribute {}'.format(attr)) realm = extra['proxy_realm'] details.authid = extra['proxy_authid'] details.authrole = extra['proxy_authrole'] details.authextra = extra.get('proxy_authextra', None) self.log.debug( '{klass}.hello(realm={realm}, details={details}) -> realm={realm}, authid={authid}, authrole={authrole}, authextra={authextra}', klass=self.__class__.__name__, realm=realm, details=details, authid=details.authid, authrole=details.authrole, authextra=details.authextra) return super(PendingAuthCryptosignProxy, self).hello(realm, details)
def onHello(self, realm, details): """ Callback fired when client wants to attach session. """ print("onHello: {} {}".format(realm, details)) try: self._pending_auth = None if details.authmethods: for authmethod in details.authmethods: if authmethod == u"totp": ## lookup user in user DB secret, authrole = yield self.factory.userdb.get( details.authid) ## if user found .. if secret: ## setup pending auth self._pending_auth = PendingAuth( secret, details.authid, authrole, authmethod, "userdb") defer.returnValue(types.Challenge('totp')) ## deny client defer.returnValue(types.Deny()) except Exception as e: print e
def hello(self, realm, details): self.log.debug('{func}(realm="{realm}", details={details})', func=hltype(self.hello), realm=hlval(realm), details=details) extra = details.authextra or {} for attr in ['proxy_authid', 'proxy_authrole', 'proxy_realm']: if attr not in extra: return types.Deny(message='missing required attribute {}'.format(attr)) realm = extra['proxy_realm'] details.authid = extra['proxy_authid'] details.authrole = extra['proxy_authrole'] details.authextra = extra.get('proxy_authextra', None) # remember the realm the client requested to join (if any) self._realm = realm self._authid = details.authid self._session_details['authmethod'] = 'anonymous' self._session_details['authextra'] = details.authextra self._authprovider = 'static' # FIXME: if cookie tracking is enabled, set authid to cookie value # self._authid = self._transport._cbtid principal = {'realm': realm, 'authid': details.authid, 'role': details.authrole, 'extra': details.authextra} error = self._assign_principal(principal) if error: return error return self._accept()
def onAuthenticate(self, router_session, signature, extra): try: challenge = router_session.challenge authid = challenge["authid"] if challenge == None: return if router_session.challenge.get("authmethod") != u"cookie": return for field in ["authrole", "authmethod", "authprovider"]: if field not in challenge: # Challenge not in expected format. It was probably # created by another plugin. return except Exception as e: # let another plugin handle this return cookie = self.cookies.get(authid) if cookie != signature: log("Failed cookie login for %s." % challenge["authid"]) return types.Deny(message=u"Invalid cookie.") log("Successful cookie login for %s." % challenge["authid"]) return types.Accept(authid=challenge["authid"], authrole=challenge["authrole"], authmethod=challenge["authmethod"], authprovider=challenge["authprovider"])
def _init_function_authenticator(self): self.log.debug('{klass}._init_function_authenticator', klass=self.__class__.__name__) # import the module for the function create_fqn = self._config['create'] if '.' not in create_fqn: return types.Deny( ApplicationError.NO_SUCH_PROCEDURE, "'function' authenticator has no module: '{}'".format(create_fqn) ) if self._config.get('expose_controller', None): from crossbar.worker.controller import WorkerController if not isinstance(self._realm_container, WorkerController): excp = Exception( "Internal Error: Our container '{}' is not a WorkerController".format( self._realm_container, ) ) self.log.failure('{klass} could not expose controller', klass=self.__class__.__name__, failure=excp) raise excp controller = self._realm_container else: controller = None create_d = txaio.as_future(_authenticator_for_name, self._config, controller=controller) def got_authenticator(authenticator): self._authenticator = authenticator create_d.addCallback(got_authenticator) return create_d
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 # use static principal database from configuration if self._config[u'type'] == u'static': self._authprovider = u'static' if self._authid in self._config.get(u'principals', {}): principal = self._config[u'principals'][self._authid] error = self._assign_principal(principal) if error: return error # now set set signature as expected for WAMP-Ticket self._signature = principal[u'ticket'] return types.Challenge(self._authmethod) else: return types.Deny( message=u'no principal with authid "{}" exists'.format( self._authid)) # use configured procedure to dynamically get a ticket for the principal elif self._config[u'type'] == u'dynamic': self._authprovider = u'dynamic' error = self._init_dynamic_authenticator() if error: return error return types.Challenge(self._authmethod) 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 on_success(response): content = json.loads(response) if content.get('success'): username = content.get('username') if username: return types.Accept(content['username']) log.msg('Rejected login: %s' % (content, )) return types.Deny()
def authenticate(self, signature): if signature == self._signature: # signature was valid: accept the client return self._accept() else: # signature was invalid: deny the client return types.Deny(message="WAMP-CRA signature is invalid")
def on_authenticate_error(err): self.log.info('{klass}.hello(realm="{realm}", details={details}) -> on_authenticate_error(err={err})', klass=self.__class__.__name__, realm=realm, details=details, err=err) try: return self._marshal_dynamic_authenticator_error(err) except Exception as e: error = ApplicationError.AUTHENTICATION_FAILED message = 'marshalling of function-based authenticator error return failed: {}'.format(e) self.log.warn('{klass}.hello.on_authenticate_error() - {msg}', msg=message) return types.Deny(error, message)
def on_authenticate_error(err): error = None message = "dynamic WAMP-Ticket credential getter failed: {}".format(err) if isinstance(err.value, ApplicationError): error = err.value.error if err.value.args and len(err.value.args): message = err.value.args[0] return types.Deny(error, message)
def _assign_principal(self, principal): # allow to override realm request, redirect realm or set default realm if u'realm' in principal: self._realm = principal['realm'] if not self._realm: return types.Deny(ApplicationError.NO_SUCH_REALM, message=u'no realm assigned') # check if effective realm exists on router if self._realm not in self._router_factory: return types.Deny( ApplicationError.NO_SUCH_REALM, message=u'no realm "{}" exists on this router'.format( self._realm)) # effective authrole if u'role' in principal: self._authrole = principal[u'role'] elif u'default-role' in self._config: self._authrole = self._config[u'default-role'] else: return types.Deny(ApplicationError.NO_SUCH_ROLE, message=u'no authrole assigned') # check if role exists on realm if not self._router_factory[self._realm].has_role(self._authrole): return types.Deny(ApplicationError.NO_SUCH_ROLE, message=u'realm "{}" has no role "{}"'.format( self._realm, self._authrole)) # allow overriding effectively assigned authid if u'authid' in principal: self._authid = principal[u'authid'] if not self._authid: return types.Deny(ApplicationError.NO_SUCH_PRINCIPAL, message=u'no authid assigned') # allow forwarding of application-specific "welcome data" if u'extra' in principal: self._authextra = principal[u'extra']
def onHello(self, realm, details): print("On hello: {}".format(details)) if u"exodoc-ticket" not in details.authmethods: return types.Deny( u"wamp.error.not_authorized", message=u"Only 'exodoc-ticket' supported as authmethods") if not details.authid.startswith("good-ticket-"): return types.Deny(u"wamp.error.not_authorized", message=u"Bad credential") self._authid = details.authid[len("good-ticket-"):] self._authmethod = u"exodoc-ticket" self._authrole = u"chatter" self._authprovider = u"redis-token" return types.Accept( authid=self._authid, authrole=self._authrole, authmethod=self._authmethod, authprovider=self._authprovider, )
def on_authenticate_error(err): error = None message = "dynamic WAMP-CRA credential getter failed: {}".format(err) if isinstance(err.value, ApplicationError): error = err.value.error if err.value.args and len(err.value.args): message = str(err.value.args[0]) # exception does not need to contain a string return types.Deny(error, message)
def authenticate(self, signature): # signatures in WAMP are strings, hence we roundtrip Hex signature = binascii.a2b_hex(signature) signed = SignedMessage(signature) try: # now verify the signed message versus the client public key self._verify_key.verify(signed) # signature was valid: accept the client return self._accept() except BadSignatureError: # signature was invalid: deny the client return types.Deny(message=u"invalid signature") except Exception as e: # should not arrive here .. but who knows return types.Deny(message=u"internal error: {}".format(e))
def _init_dynamic_authenticator(self): self._authenticator = self._config['authenticator'] authenticator_realm = None if 'authenticator-realm' in self._config: authenticator_realm = self._config['authenticator-realm'] if not self._realm_container.has_realm(authenticator_realm): return types.Deny( ApplicationError.NO_SUCH_REALM, message= "explicit realm <{}> configured for dynamic authenticator does not exist" .format(authenticator_realm)) else: if not self._realm: return types.Deny( ApplicationError.NO_SUCH_REALM, message= "client did not specify a realm to join (and no explicit realm was configured for dynamic authenticator)" ) authenticator_realm = self._realm self._authenticator_session = self._realm_container.get_service_session( authenticator_realm)
def authenticate(self, signed_message): """ Verify the signed message sent by the client. :param signed_message: the base64-encoded result "ClientProof" from the SCRAM protocol """ channel_binding = "" client_nonce = base64.b64encode(self._client_nonce).decode('ascii') server_nonce = base64.b64encode(self._server_nonce).decode('ascii') salt = base64.b64encode(self._salt).decode('ascii') auth_message = ( "{client_first_bare},{server_first},{client_final_no_proof}". format( client_first_bare="n={},r={}".format(saslprep(self._authid), client_nonce), server_first="r={},s={},i={}".format(server_nonce, salt, self._iterations), client_final_no_proof="c={},r={}".format( channel_binding, server_nonce), )) received_client_proof = base64.b64decode(signed_message) client_signature = hmac.new(self._stored_key, auth_message.encode('ascii'), hashlib.sha256).digest() recovered_client_key = util.xor(client_signature, received_client_proof) recovered_stored_key = hashlib.new('sha256', recovered_client_key).digest() # if we adjust self._authextra before _accept() it gets sent # back to the client server_signature = hmac.new(self._server_key, auth_message.encode('ascii'), hashlib.sha256).digest() if self._authextra is None: self._authextra = {} self._authextra['scram_server_signature'] = base64.b64encode( server_signature).decode('ascii') if hmac.compare_digest(recovered_stored_key, self._stored_key): return self._accept() self.log.error("SCRAM authentication failed for '{authid}'", authid=self._authid) return types.Deny(message='SCRAM authentication failed')
def onHello(self, realm, details): """ Callback fired when client wants to attach session. """ print("MyRouterSession.onHello: {} {}".format(realm, details)) for authmethod in details.authmethods: if authmethod == u"cookie" and self._transport._authenticated is not None: # already authenticated via Cookie on transport return types.Accept(authid=self._transport._authenticated, authrole="user", authmethod="cookie") elif authmethod == u"mozilla-persona": # not yet authenticated: send challenge return types.Challenge("mozilla-persona") return types.Deny()
def hello(self, realm, details): self.log.info('{klass}.hello(realm={realm}, details={details}) ...', klass=self.__class__.__name__, realm=realm, details=details) extra = details.authextra or {} for attr in ['proxy_authid', 'proxy_authrole', 'proxy_realm']: if attr not in extra: return types.Deny( message='missing required attribute {}'.format(attr)) realm = extra['proxy_realm'] details.authid = extra['proxy_authid'] details.authrole = extra['proxy_authrole'] details.authextra = extra.get('proxy_authextra', None) self.log.info( '{klass}.hello(realm={realm}, details={details}) -> realm={realm}, authid={authid}, authrole={authrole}, authextra={authextra}', klass=self.__class__.__name__, realm=realm, details=details, authid=details.authid, authrole=details.authrole, authextra=details.authextra) # remember the realm the client requested to join (if any) self._realm = realm self._authid = details.authid self._session_details['authmethod'] = 'anonymous' self._session_details['authextra'] = details.authextra self._authprovider = 'static' # FIXME: if cookie tracking is enabled, set authid to cookie value # self._authid = self._transport._cbtid principal = { 'realm': realm, 'authid': details.authid, 'role': details.authrole, 'extra': details.authextra } error = self._assign_principal(principal) if error: return error return self._accept()