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' error = self._init_dynamic_authenticator() if error: return error self._session_details[ 'authmethod'] = self._authmethod # from AUTHMETHOD, via base self._session_details['authextra'] = details.authextra return types.Challenge(self._authmethod) 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 onChallenge(self, challenge): if challenge.method == "cryptosign-proxy": return super(ProxyBackendSession, self).onChallenge( types.Challenge("cryptosign", extra=challenge.extra) ) return super(ProxyBackendSession, self).onChallenge(challenge)
def on_authenticate_ok(user): # the authid the session will be authenticated as is from the dynamic # authenticator response, or when the response doesn't contain an authid, # from the HELLO message the client sent # authid = user.get( "authid", details.authid) # construct a pending WAMP-CRA authentication # self._pending_auth = PendingAuthWampCra( details.pending_session, authid, user['role'], u'dynamic', user['secret'].encode('utf8')) # send challenge to client # extra = { u'challenge': self._pending_auth.challenge } # when using salted passwords, provide the client with # the salt and the PBKDF2 parameters used # if 'salt' in user: extra[u'salt'] = user['salt'] extra[u'iterations'] = user.get( 'iterations', 1000) extra[u'keylen'] = user.get( 'keylen', 32) return types.Challenge( u'wampcra', extra)
def test_testvectors(self): session = Mock() session._transport.transport_details = self.transport_details for testvec in testvectors: priv_key = SigningKey.from_key_bytes( binascii.a2b_hex(testvec['priv_key'])) challenge = types.Challenge("ticket", dict(challenge=testvec['challenge'])) f_signed = priv_key.sign_challenge(session, challenge) def success(signed): self.assertEqual( 192, len(signed), ) self.assertEqual( testvec['signature'], signed, ) def failed(err): self.fail(str(err)) txaio.add_callbacks(f_signed, success, failed)
def test_no_memory_arg(self): scram = auth.AuthScram( nonce=u'1234567890abcdef', kdf=u'argon2id13', salt=binascii.b2a_hex(b'1234567890abcdef').decode('ascii'), iterations=4096, memory=512, password=u'p4ssw0rd', authid=u'username', ) scram.authextra with self.assertRaises(ValueError) as ctx: challenge = types.Challenge(u'scram', { 'nonce': u'1234567890abcdeffedcba0987654321', 'kdf': u'argon2id-13', 'salt': binascii.b2a_hex(b'1234567890abcdef'), 'iterations': 4096, # no 'memory' key }) scram.on_challenge(Mock(), challenge) self.assertIn( "requires 'memory' parameter", str(ctx.exception) )
def onHello(self, realm, details): print "onHello: {} {}".format(realm, details) if self._transport._authenticated is not None: return types.Accept(authid=self._transport._authenticated) else: return types.Challenge("mozilla-persona") return accept
def on_authenticate_ok(user): ## construct a pending WAMP-CRA authentication ## self._pending_auth = PendingAuthWampCra( details.pending_session, details.authid, user['role'], u'dynamic', user['secret'].encode('utf8')) ## send challenge to client ## extra = { u'challenge': self._pending_auth.challenge } ## when using salted passwords, provide the client with ## the salt and the PBKDF2 parameters used ## if 'salt' in user: extra[u'salt'] = user['salt'] extra[u'iterations'] = user.get( 'iterations', 1000) extra[u'keylen'] = user.get( 'keylen', 32) return types.Challenge( u'wampcra', extra)
def init(error): if error: return error self._session_details[ 'authmethod'] = self._authmethod # from AUTHMETHOD, via base self._session_details['authextra'] = details.authextra return types.Challenge(self._authmethod)
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 on_authenticate_ok(principal): error = self._assign_principal(principal) if error: return error # now compute CHALLENGE.Extra and signature expected extra, self._signature = self._compute_challenge(principal) return types.Challenge(self._authmethod, extra)
def onHello(self, realm, details): """On receiving a hello, issue a Challenge to the client""" if getattr(self._transport, '_authenticated', None) is not None: return types.Accept(authid=self._transport._authenticated) else: return types.Challenge('userpass', { 'timestamp': time.time(), })
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)
def on_authenticate_ok(principal): error = self._assign_principal(principal) if error: return error self._verify_key = VerifyKey(principal[u'pubkey'], encoder=nacl.encoding.HexEncoder) extra = self._compute_challenge(channel_binding) return types.Challenge(self._authmethod, extra)
def test_valid(self): session = Mock() session._transport.get_channel_id = Mock(return_value=self.channel_id) challenge = types.Challenge(u"ticket", dict(challenge="ff" * 32)) signed = yield self.key.sign_challenge(session, challenge) self.assertEqual( u'9b6f41540c9b95b4b7b281c3042fa9c54cef43c842d62ea3fd6030fcb66e70b3e80d49d44c29d1635da9348d02ec93f3ed1ef227dfb59a07b580095c2b82f80f9d16ca518aa0c2b707f2b2a609edeca73bca8dd59817a633f35574ac6fd80d00', signed.result, )
def on_authenticate_ok(principal): self.log.info('{klass}.hello(realm="{realm}", details={details}) -> on_authenticate_ok(principal={principal})', klass=self.__class__.__name__, realm=realm, details=details, principal=principal) error = self._assign_principal(principal) if error: return error self._verify_key = VerifyKey(principal['pubkey'], encoder=nacl.encoding.HexEncoder) extra = self._compute_challenge(channel_binding) return types.Challenge(self._authmethod, extra)
def test_authenticator(self): authenticator = create_authenticator( u"cryptosign", authid="someone", privkey=self.privkey_hex, ) session = Mock() session._transport.get_channel_id = Mock(return_value=self.channel_id) challenge = types.Challenge(u"cryptosign", dict(challenge="ff" * 32)) reply = yield authenticator.on_challenge(session, challenge) self.assertEqual( reply.result, u'9b6f41540c9b95b4b7b281c3042fa9c54cef43c842d62ea3fd6030fcb66e70b3e80d49d44c29d1635da9348d02ec93f3ed1ef227dfb59a07b580095c2b82f80f9d16ca518aa0c2b707f2b2a609edeca73bca8dd59817a633f35574ac6fd80d00', )
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 on_authenticate_ok(principal): self._salt = binascii.a2b_hex(principal['salt']) # error if no salt per-user self._iterations = principal['iterations'] self._memory = principal['memory'] self._kdf = principal['kdf'] self._stored_key = binascii.a2b_hex(principal['stored-key']) # do we actually need the server-key? can we compute it ourselves? self._server_key = binascii.a2b_hex(principal['server-key']) error = self._assign_principal(principal) if error: return error # XXX TODO this needs to include (optional) channel-binding extra = self._compute_challenge() return types.Challenge(self._authmethod, extra)
def onHello(self, router_session, realm, details): for authmethod in details.authmethods: if authmethod == u"cookie": debug("Attemping cookie login for %s..." % details.authid) # Ideally, we would just check the cookie here, however # the HELLO message does not have any extra fields to store # it. # This is not a real challenge. It is used for bookkeeping, # however. We require to cookie owner to also know the # correct authid, so we store what they think it is here. # Create and store a one time challenge. challenge = { "authid": details.authid, "authrole": u"user", "authmethod": u"cookie", "authprovider": u"cookie", "session": details.pending_session, "nonce": util.utcnow(), "timestamp": util.newid() } router_session.challenge = challenge # If the user does not exist, we should still return a # consistent salt. This prevents the auth system from # becoming a username oracle. noise = hashlib.md5("super secret" + details.authid + "more secret") salt = noise.hexdigest()[:8] databases = self.manager.services[ "sputnik.webserver.plugins.db"] for db in databases: result = yield db.lookup(details.authid) if result is not None: break if result is not None: salt = result['password'].split(":")[0] # The client expects a unicode challenge string. challenge = json.dumps(challenge, ensure_ascii=False) extra = {u"challenge": challenge, u"salt": salt} debug("Cookie challenge issued for %s." % details.authid) returnValue(types.Challenge(u"cookie", extra))
def onHello(self, realm, details): """ Callback fired when client wants to attach session. """ log.msg("onHello: {} {}".format(realm, details)) self._pending_auth = None if details.authmethods: for authmethod in details.authmethods: if authmethod == u"wampcra": ## lookup user in user DB salt, key, role, uid = yield self.factory.userdb.get( details.authid) log.msg("salt, key, role: {} {} {} {}".format( salt, key, role, uid)) ## if user found .. if key: log.msg("found key") ## setup pending auth self._pending_auth = PendingAuth( key, details.pending_session, details.authid, role, authmethod, u"userdb", uid) log.msg("setting challenge") ## send challenge to client extra = {u'challenge': self._pending_auth.challenge} ## when using salted passwords, provide the client with ## the salt and then PBKDF2 parameters used if salt: extra[u'salt'] = salt extra[u'iterations'] = 1000 extra[u'keylen'] = 32 defer.returnValue(types.Challenge(u'wampcra', extra)) ## deny client defer.returnValue(types.Deny())
def test_valid(self): session = Mock() session._transport.get_channel_id = Mock(return_value=self.channel_id) challenge = types.Challenge("ticket", dict(challenge="ff" * 32)) f_signed = self.key.sign_challenge(session, challenge) def success(signed): self.assertEqual( 192, len(signed), ) self.assertEqual( '9b6f41540c9b95b4b7b281c3042fa9c54cef43c842d62ea3fd6030fcb66e70b3e80d49d44c29d1635da9348d02ec93f3ed1ef227dfb59a07b580095c2b82f80f9d16ca518aa0c2b707f2b2a609edeca73bca8dd59817a633f35574ac6fd80d00', signed, ) def failed(err): self.fail(str(err)) txaio.add_callbacks(f_signed, success, failed)
def test_authenticator(self): authenticator = create_authenticator( "cryptosign", authid="someone", privkey=self.privkey_hex, ) session = Mock() session._transport.transport_details = self.transport_details challenge = types.Challenge("cryptosign", dict(challenge="ff" * 32)) f_reply = authenticator.on_challenge(session, challenge) def success(reply): self.assertEqual( reply, '9b6f41540c9b95b4b7b281c3042fa9c54cef43c842d62ea3fd6030fcb66e70b3e80d49d44c29d1635da9348d02ec93f3ed1ef227dfb59a07b580095c2b82f80f9d16ca518aa0c2b707f2b2a609edeca73bca8dd59817a633f35574ac6fd80d00', ) def failed(err): self.fail(str(err)) txaio.add_callbacks(f_reply, success, failed)
def test_unknown_arg(self): scram = auth.AuthScram( nonce='1234567890abcdef', kdf='argon2id13', salt=binascii.b2a_hex(b'1234567890abcdef'), iterations=4096, memory=512, password=u'p4ssw0rd', authid=u'username', ) scram.authextra with self.assertRaises(RuntimeError) as ctx: challenge = types.Challenge(u'scram', { 'nonce': u'1234567890abcdeffedcba0987654321', 'kdf': u'argon2id-13', 'salt': binascii.b2a_hex(b'1234567890abcdef'), 'iterations': 4096, 'memory': 512, 'an_invalid_key': None }) scram.on_challenge(Mock(), challenge) self.assertIn("an_invalid_key", str(ctx.exception))
def test_basic(self): scram = auth.AuthScram( nonce='1234567890abcdef', kdf='argon2id13', salt=binascii.b2a_hex(b'1234567890abcdef').decode('ascii'), iterations=32, # far too few; use 4096 or more for production memory=512, password='******', authid='username', ) # thought: if we could import crossbar code here, we could # test the "other side" of this with fewer mocks # (i.e. hard-coding the client nonce) scram._client_nonce = binascii.b2a_hex(b'1234567890abcdef').decode( 'ascii') self.assertEqual( {'nonce': '31323334353637383930616263646566'}, scram.authextra, ) challenge = types.Challenge( 'scram', { 'nonce': '1234567890abcdeffedcba0987654321', 'kdf': 'argon2id-13', 'salt': binascii.b2a_hex(b'1234567890abcdef').decode('ascii'), 'iterations': 32, 'memory': 512, }) reply = scram.on_challenge(Mock(), challenge) self.assertEqual( b'f5r3loERzGVSuimE+lvO0bWna2zyswBo0HrZkaaEy38=', reply, ) authextra = dict(scram_server_signature= b'f5r3loERzGVSuimE+lvO0bWna2zyswBo0HrZkaaEy38=', ) scram.on_welcome(Mock(), authextra)
def onMessage(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage` """ if self._session_id is None: ## the first message must be WELCOME, ABORT or CHALLENGE .. ## if isinstance(msg, message.Welcome): self._session_id = msg.session details = SessionDetails(self._realm, self._session_id, msg.authid, msg.authrole, msg.authmethod) self._as_future(self.onJoin, details) elif isinstance(msg, message.Abort): ## fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) elif isinstance(msg, message.Challenge): challenge = types.Challenge(msg.method, msg.extra) d = self._as_future(self.onChallenge, challenge) def success(signature): reply = message.Authenticate(signature) self._transport.send(reply) def error(err): reply = message.Abort(u"wamp.error.cannot_authenticate", u"{0}".format(err.value)) self._transport.send(reply) ## fire callback and close the transport self.onLeave(types.CloseDetails(reply.reason, reply.message)) self._add_future_callbacks(d, success, error) else: raise ProtocolError("Received {0} message, and session is not yet established".format(msg.__class__)) else: if isinstance(msg, message.Goodbye): if not self._goodbye_sent: ## the peer wants to close: send GOODBYE reply reply = message.Goodbye() self._transport.send(reply) self._session_id = None ## fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) ## consumer messages ## elif isinstance(msg, message.Event): if msg.subscription in self._subscriptions: handler = self._subscriptions[msg.subscription] if handler.details_arg: if not msg.kwargs: msg.kwargs = {} msg.kwargs[handler.details_arg] = types.EventDetails(publication = msg.publication, publisher = msg.publisher) try: if handler.obj: if msg.kwargs: if msg.args: handler.fn(handler.obj, *msg.args, **msg.kwargs) else: handler.fn(handler.obj, **msg.kwargs) else: if msg.args: handler.fn(handler.obj, *msg.args) else: handler.fn(handler.obj) else: if msg.kwargs: if msg.args: handler.fn(*msg.args, **msg.kwargs) else: handler.fn(**msg.kwargs) else: if msg.args: handler.fn(*msg.args) else: handler.fn() except Exception as e: if self.debug_app: print("Failure while firing event handler {0} subscribed under '{1}' ({2}): {3}".format(handler.fn, handler.topic, msg.subscription, e)) else: raise ProtocolError("EVENT received for non-subscribed subscription ID {0}".format(msg.subscription)) elif isinstance(msg, message.Published): if msg.request in self._publish_reqs: d, opts = self._publish_reqs.pop(msg.request) p = Publication(msg.publication) self._resolve_future(d, p) else: raise ProtocolError("PUBLISHED received for non-pending request ID {0}".format(msg.request)) elif isinstance(msg, message.Subscribed): if msg.request in self._subscribe_reqs: d, obj, fn, topic, options = self._subscribe_reqs.pop(msg.request) if options: self._subscriptions[msg.subscription] = Handler(obj, fn, topic, options.details_arg) else: self._subscriptions[msg.subscription] = Handler(obj, fn, topic) s = Subscription(self, msg.subscription) self._resolve_future(d, s) else: raise ProtocolError("SUBSCRIBED received for non-pending request ID {0}".format(msg.request)) elif isinstance(msg, message.Unsubscribed): if msg.request in self._unsubscribe_reqs: d, subscription = self._unsubscribe_reqs.pop(msg.request) if subscription.id in self._subscriptions: del self._subscriptions[subscription.id] subscription.active = False self._resolve_future(d, None) else: raise ProtocolError("UNSUBSCRIBED received for non-pending request ID {0}".format(msg.request)) elif isinstance(msg, message.Result): if msg.request in self._call_reqs: if msg.progress: ## progressive result ## _, opts = self._call_reqs[msg.request] if opts.onProgress: try: if msg.kwargs: if msg.args: opts.onProgress(*msg.args, **msg.kwargs) else: opts.onProgress(**msg.kwargs) else: if msg.args: opts.onProgress(*msg.args) else: opts.onProgress() except Exception as e: ## silently drop exceptions raised in progressive results handlers if self.debug: print("Exception raised in progressive results handler: {0}".format(e)) else: ## silently ignore progressive results pass else: ## final result ## d, opts = self._call_reqs.pop(msg.request) if msg.kwargs: if msg.args: res = types.CallResult(*msg.args, **msg.kwargs) else: res = types.CallResult(**msg.kwargs) self._resolve_future(d, res) else: if msg.args: if len(msg.args) > 1: res = types.CallResult(*msg.args) self._resolve_future(d, res) else: self._resolve_future(d, msg.args[0]) else: self._resolve_future(d, None) else: raise ProtocolError("RESULT received for non-pending request ID {0}".format(msg.request)) elif isinstance(msg, message.Invocation): if msg.request in self._invocations: raise ProtocolError("INVOCATION received for request ID {0} already invoked".format(msg.request)) else: if msg.registration not in self._registrations: raise ProtocolError("INVOCATION received for non-registered registration ID {0}".format(msg.registration)) else: endpoint = self._registrations[msg.registration] if endpoint.options and endpoint.options.details_arg: if not msg.kwargs: msg.kwargs = {} if msg.receive_progress: def progress(*args, **kwargs): progress_msg = message.Yield(msg.request, args = args, kwargs = kwargs, progress = True) self._transport.send(progress_msg) else: progress = None msg.kwargs[endpoint.options.details_arg] = types.CallDetails(progress, caller = msg.caller, caller_transport = msg.caller_transport, authid = msg.authid, authrole = msg.authrole, authmethod = msg.authmethod) if endpoint.obj: if msg.kwargs: if msg.args: d = self._as_future(endpoint.fn, endpoint.obj, *msg.args, **msg.kwargs) else: d = self._as_future(endpoint.fn, endpoint.obj, **msg.kwargs) else: if msg.args: d = self._as_future(endpoint.fn, endpoint.obj, *msg.args) else: d = self._as_future(endpoint.fn, endpoint.obj) else: if msg.kwargs: if msg.args: d = self._as_future(endpoint.fn, *msg.args, **msg.kwargs) else: d = self._as_future(endpoint.fn, **msg.kwargs) else: if msg.args: d = self._as_future(endpoint.fn, *msg.args) else: d = self._as_future(endpoint.fn) def success(res): del self._invocations[msg.request] if isinstance(res, types.CallResult): reply = message.Yield(msg.request, args = res.results, kwargs = res.kwresults) else: reply = message.Yield(msg.request, args = [res]) self._transport.send(reply) def error(err): if self.traceback_app: ## if asked to marshal the traceback within the WAMP error message, extract it # noinspection PyCallingNonCallable tb = StringIO() err.printTraceback(file = tb) tb = tb.getvalue().splitlines() else: tb = None if self.debug_app: print("Failure while invoking procedure {0} registered under '{1}' ({2}):".format(endpoint.fn, endpoint.procedure, msg.registration)) print(err) del self._invocations[msg.request] if hasattr(err, 'value'): exc = err.value else: exc = err reply = self._message_from_exception(message.Invocation.MESSAGE_TYPE, msg.request, exc, tb) self._transport.send(reply) self._invocations[msg.request] = d self._add_future_callbacks(d, success, error) elif isinstance(msg, message.Interrupt): if msg.request not in self._invocations: raise ProtocolError("INTERRUPT received for non-pending invocation {0}".format(msg.request)) else: # noinspection PyBroadException try: self._invocations[msg.request].cancel() except Exception: if self.debug: print("could not cancel call {0}".format(msg.request)) finally: del self._invocations[msg.request] elif isinstance(msg, message.Registered): if msg.request in self._register_reqs: d, obj, fn, procedure, options = self._register_reqs.pop(msg.request) self._registrations[msg.registration] = Endpoint(obj, fn, procedure, options) r = Registration(self, msg.registration) self._resolve_future(d, r) else: raise ProtocolError("REGISTERED received for non-pending request ID {0}".format(msg.request)) elif isinstance(msg, message.Unregistered): if msg.request in self._unregister_reqs: d, registration = self._unregister_reqs.pop(msg.request) if registration.id in self._registrations: del self._registrations[registration.id] registration.active = False self._resolve_future(d, None) else: raise ProtocolError("UNREGISTERED received for non-pending request ID {0}".format(msg.request)) elif isinstance(msg, message.Error): d = None ## ERROR reply to PUBLISH ## if msg.request_type == message.Publish.MESSAGE_TYPE and msg.request in self._publish_reqs: d = self._publish_reqs.pop(msg.request)[0] ## ERROR reply to SUBSCRIBE ## elif msg.request_type == message.Subscribe.MESSAGE_TYPE and msg.request in self._subscribe_reqs: d = self._subscribe_reqs.pop(msg.request)[0] ## ERROR reply to UNSUBSCRIBE ## elif msg.request_type == message.Unsubscribe.MESSAGE_TYPE and msg.request in self._unsubscribe_reqs: d = self._unsubscribe_reqs.pop(msg.request)[0] ## ERROR reply to REGISTER ## elif msg.request_type == message.Register.MESSAGE_TYPE and msg.request in self._register_reqs: d = self._register_reqs.pop(msg.request)[0] ## ERROR reply to UNREGISTER ## elif msg.request_type == message.Unregister.MESSAGE_TYPE and msg.request in self._unregister_reqs: d = self._unregister_reqs.pop(msg.request)[0] ## ERROR reply to CALL ## elif msg.request_type == message.Call.MESSAGE_TYPE and msg.request in self._call_reqs: d = self._call_reqs.pop(msg.request)[0] if d: self._reject_future(d, self._exception_from_message(msg)) else: raise ProtocolError("WampAppSession.onMessage(): ERROR received for non-pending request_type {0} and request ID {1}".format(msg.request_type, msg.request)) elif isinstance(msg, message.Heartbeat): pass ## FIXME else: raise ProtocolError("Unexpected message {0}".format(msg.__class__))
def hello(self, realm, details): # the channel binding requested by the client authenticating channel_binding = details.authextra.get(u'channel_binding', None) if channel_binding is not None and channel_binding not in [ u'tls-unique' ]: return types.Deny( message=u'invalid channel binding type "{}" requested'.format( channel_binding)) else: self.log.debug( "WAMP-cryptosign CHANNEL BINDING requested: channel_binding={channel_binding}, channel_id={channel_id}", channel_binding=channel_binding, channel_id=self._channel_id) # 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 = u'static' # get client's pubkey, if it was provided in authextra pubkey = None if details.authextra and u'pubkey' in details.authextra: pubkey = details.authextra[u'pubkey'] # if the client provides it's public key, that's enough to identify, # and we can infer the authid from that. BUT: that requires that # there is a 1:1 relation between authid's and pubkey's !! see below (*) if self._authid is None: if pubkey: # we do a naive search, but that is ok, since "static mode" is from # node configuration, and won't contain a lot principals anyway for _authid, _principal in self._config.get( u'principals', {}).items(): if pubkey in _principal[u'authorized_keys']: # (*): this is necessary to detect multiple authid's having the same pubkey # in which case we couldn't reliably map the authid from the pubkey if self._authid is None: self._authid = _authid else: return types.Deny( message= u'cannot infer client identity from pubkey: multiple authids in principal database have this pubkey' ) if self._authid is None: return types.Deny( message= u'cannot identify client: no authid requested and no principal found for provided extra.pubkey' ) else: return types.Deny( message= u'cannot identify client: no authid requested and no extra.pubkey provided' ) if self._authid in self._config.get(u'principals', {}): principal = self._config[u'principals'][self._authid] if pubkey and (pubkey not in principal[u'authorized_keys']): return types.Deny( message= u'extra.pubkey provided does not match any one of authorized_keys for the principal' ) error = self._assign_principal(principal) if error: return error self._verify_key = VerifyKey(pubkey, encoder=nacl.encoding.HexEncoder) extra = self._compute_challenge(channel_binding) return types.Challenge(self._authmethod, extra) else: return types.Deny( message=u'no principal with authid "{}" exists'.format( details.authid)) elif self._config[u'type'] == u'dynamic': self._authprovider = u'dynamic' error = self._init_dynamic_authenticator() if error: return error self._session_details[ u'authmethod'] = self._authmethod # from AUTHMETHOD, via base self._session_details[u'authid'] = details.authid self._session_details[u'authrole'] = details.authrole self._session_details[u'authextra'] = details.authextra d = self._authenticator_session.call(self._authenticator, realm, details.authid, self._session_details) def on_authenticate_ok(principal): error = self._assign_principal(principal) if error: return error self._verify_key = VerifyKey(principal[u'pubkey'], encoder=nacl.encoding.HexEncoder) extra = self._compute_challenge(channel_binding) return types.Challenge(self._authmethod, extra) 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 onMessage(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage` """ if self._session_id is None: # the first message must be WELCOME, ABORT or CHALLENGE .. if isinstance(msg, message.Welcome): self._session_id = msg.session details = SessionDetails(self._realm, self._session_id, msg.authid, msg.authrole, msg.authmethod) d = txaio.as_future(self.onJoin, details) def _error(e): return self._swallow_error(e, "While firing onJoin") txaio.add_callbacks(d, None, _error) elif isinstance(msg, message.Abort): # fire callback and close the transport details = types.CloseDetails(msg.reason, msg.message) d = txaio.as_future(self.onLeave, details) def _error(e): return self._swallow_error(e, "While firing onLeave") txaio.add_callbacks(d, None, _error) elif isinstance(msg, message.Challenge): challenge = types.Challenge(msg.method, msg.extra) d = txaio.as_future(self.onChallenge, challenge) def success(signature): if not isinstance(signature, six.text_type): raise Exception('signature must be unicode') reply = message.Authenticate(signature) self._transport.send(reply) def error(err): self.onUserError(err, "Authentication failed") reply = message.Abort(u"wamp.error.cannot_authenticate", u"{0}".format(err.value)) self._transport.send(reply) # fire callback and close the transport details = types.CloseDetails(reply.reason, reply.message) d = txaio.as_future(self.onLeave, details) def _error(e): return self._swallow_error(e, "While firing onLeave") txaio.add_callbacks(d, None, _error) # switching to the callback chain, effectively # cancelling error (which we've now handled) return d txaio.add_callbacks(d, success, error) else: raise ProtocolError( "Received {0} message, and session is not yet established". format(msg.__class__)) else: # self._session_id != None (aka "session established") if isinstance(msg, message.Goodbye): if not self._goodbye_sent: # the peer wants to close: send GOODBYE reply reply = message.Goodbye() self._transport.send(reply) self._session_id = None # fire callback and close the transport details = types.CloseDetails(msg.reason, msg.message) d = txaio.as_future(self.onLeave, details) def _error(e): errmsg = 'While firing onLeave for reason "{0}" and message "{1}"'.format( msg.reason, msg.message) return self._swallow_error(e, errmsg) txaio.add_callbacks(d, None, _error) elif isinstance(msg, message.Event): if msg.subscription in self._subscriptions: # fire all event handlers on subscription .. for subscription in self._subscriptions[msg.subscription]: handler = subscription.handler invoke_args = ( handler.obj, ) if handler.obj else tuple() if msg.args: invoke_args = invoke_args + tuple(msg.args) invoke_kwargs = msg.kwargs if msg.kwargs else dict() if handler.details_arg: invoke_kwargs[ handler.details_arg] = types.EventDetails( publication=msg.publication, publisher=msg.publisher, topic=msg.topic or subscription.topic) def _error(e): errmsg = 'While firing {0} subscribed under {1}.'.format( handler.fn, msg.subscription) return self._swallow_error(e, errmsg) future = txaio.as_future(handler.fn, *invoke_args, **invoke_kwargs) txaio.add_callbacks(future, None, _error) else: raise ProtocolError( "EVENT received for non-subscribed subscription ID {0}" .format(msg.subscription)) elif isinstance(msg, message.Published): if msg.request in self._publish_reqs: # get and pop outstanding publish request publish_request = self._publish_reqs.pop(msg.request) # create a new publication object publication = Publication(msg.publication) # resolve deferred/future for publishing successfully txaio.resolve(publish_request.on_reply, publication) else: raise ProtocolError( "PUBLISHED received for non-pending request ID {0}". format(msg.request)) elif isinstance(msg, message.Subscribed): if msg.request in self._subscribe_reqs: # get and pop outstanding subscribe request request = self._subscribe_reqs.pop(msg.request) # create new handler subscription list for subscription ID if not yet tracked if msg.subscription not in self._subscriptions: self._subscriptions[msg.subscription] = [] subscription = Subscription(msg.subscription, request.topic, self, request.handler) # add handler to existing subscription self._subscriptions[msg.subscription].append(subscription) # resolve deferred/future for subscribing successfully txaio.resolve(request.on_reply, subscription) else: raise ProtocolError( "SUBSCRIBED received for non-pending request ID {0}". format(msg.request)) elif isinstance(msg, message.Unsubscribed): if msg.request in self._unsubscribe_reqs: # get and pop outstanding subscribe request request = self._unsubscribe_reqs.pop(msg.request) # if the subscription still exists, mark as inactive and remove .. if request.subscription_id in self._subscriptions: for subscription in self._subscriptions[ request.subscription_id]: subscription.active = False del self._subscriptions[request.subscription_id] # resolve deferred/future for unsubscribing successfully txaio.resolve(request.on_reply, 0) else: raise ProtocolError( "UNSUBSCRIBED received for non-pending request ID {0}". format(msg.request)) elif isinstance(msg, message.Result): if msg.request in self._call_reqs: if msg.progress: # progressive result call_request = self._call_reqs[msg.request] if call_request.options.on_progress: kw = msg.kwargs or dict() args = msg.args or tuple() try: # XXX what if on_progress returns a Deferred/Future? call_request.options.on_progress(*args, **kw) except Exception: try: self.onUserError( txaio.create_failure(), "While firing on_progress", ) except: pass else: # silently ignore progressive results pass else: # final result # call_request = self._call_reqs.pop(msg.request) on_reply = call_request.on_reply if msg.kwargs: if msg.args: res = types.CallResult(*msg.args, **msg.kwargs) else: res = types.CallResult(**msg.kwargs) txaio.resolve(on_reply, res) else: if msg.args: if len(msg.args) > 1: res = types.CallResult(*msg.args) txaio.resolve(on_reply, res) else: txaio.resolve(on_reply, msg.args[0]) else: txaio.resolve(on_reply, None) else: raise ProtocolError( "RESULT received for non-pending request ID {0}". format(msg.request)) elif isinstance(msg, message.Invocation): if msg.request in self._invocations: raise ProtocolError( "INVOCATION received for request ID {0} already invoked" .format(msg.request)) else: if msg.registration not in self._registrations: raise ProtocolError( "INVOCATION received for non-registered registration ID {0}" .format(msg.registration)) else: registration = self._registrations[msg.registration] endpoint = registration.endpoint if endpoint.obj is not None: invoke_args = (endpoint.obj, ) else: invoke_args = tuple() if msg.args: invoke_args = invoke_args + tuple(msg.args) invoke_kwargs = msg.kwargs if msg.kwargs else dict() if endpoint.details_arg: if msg.receive_progress: def progress(*args, **kwargs): progress_msg = message.Yield(msg.request, args=args, kwargs=kwargs, progress=True) self._transport.send(progress_msg) else: progress = None invoke_kwargs[ endpoint.details_arg] = types.CallDetails( progress, caller=msg.caller, procedure=msg.procedure) on_reply = txaio.as_future(endpoint.fn, *invoke_args, **invoke_kwargs) def success(res): del self._invocations[msg.request] if isinstance(res, types.CallResult): reply = message.Yield(msg.request, args=res.results, kwargs=res.kwresults) else: reply = message.Yield(msg.request, args=[res]) try: self._transport.send(reply) except SerializationError as e: # the application-level payload returned from the invoked procedure can't be serialized reply = message.Error( message.Invocation.MESSAGE_TYPE, msg.request, ApplicationError.INVALID_PAYLOAD, args=[ u'success return value from invoked procedure "{0}" could not be serialized: {1}' .format(registration.procedure, e) ]) self._transport.send(reply) def error(err): errmsg = txaio.failure_message(err) try: self.onUserError(err, errmsg) except: pass formatted_tb = None if self.traceback_app: formatted_tb = txaio.failure_format_traceback( err) del self._invocations[msg.request] reply = self._message_from_exception( message.Invocation.MESSAGE_TYPE, msg.request, err.value, formatted_tb, ) try: self._transport.send(reply) except SerializationError as e: # the application-level payload returned from the invoked procedure can't be serialized reply = message.Error( message.Invocation.MESSAGE_TYPE, msg.request, ApplicationError.INVALID_PAYLOAD, args=[ u'error return value from invoked procedure "{0}" could not be serialized: {1}' .format(registration.procedure, e) ]) self._transport.send(reply) # we have handled the error, so we eat it return None self._invocations[msg.request] = InvocationRequest( msg.request, on_reply) txaio.add_callbacks(on_reply, success, error) elif isinstance(msg, message.Interrupt): if msg.request not in self._invocations: raise ProtocolError( "INTERRUPT received for non-pending invocation {0}". format(msg.request)) else: # noinspection PyBroadException try: self._invocations[msg.request].cancel() except Exception: # XXX can .cancel() return a Deferred/Future? try: self.onUserError( txaio.create_failure(), "While cancelling call.", ) except: pass finally: del self._invocations[msg.request] elif isinstance(msg, message.Registered): if msg.request in self._register_reqs: # get and pop outstanding register request request = self._register_reqs.pop(msg.request) # create new registration if not yet tracked if msg.registration not in self._registrations: registration = Registration(self, msg.registration, request.procedure, request.endpoint) self._registrations[msg.registration] = registration else: raise ProtocolError( "REGISTERED received for already existing registration ID {0}" .format(msg.registration)) txaio.resolve(request.on_reply, registration) else: raise ProtocolError( "REGISTERED received for non-pending request ID {0}". format(msg.request)) elif isinstance(msg, message.Unregistered): if msg.request in self._unregister_reqs: # get and pop outstanding subscribe request request = self._unregister_reqs.pop(msg.request) # if the registration still exists, mark as inactive and remove .. if request.registration_id in self._registrations: self._registrations[ request.registration_id].active = False del self._registrations[request.registration_id] # resolve deferred/future for unregistering successfully txaio.resolve(request.on_reply) else: raise ProtocolError( "UNREGISTERED received for non-pending request ID {0}". format(msg.request)) elif isinstance(msg, message.Error): # remove outstanding request and get the reply deferred/future on_reply = None # ERROR reply to CALL if msg.request_type == message.Call.MESSAGE_TYPE and msg.request in self._call_reqs: on_reply = self._call_reqs.pop(msg.request).on_reply # ERROR reply to PUBLISH elif msg.request_type == message.Publish.MESSAGE_TYPE and msg.request in self._publish_reqs: on_reply = self._publish_reqs.pop(msg.request).on_reply # ERROR reply to SUBSCRIBE elif msg.request_type == message.Subscribe.MESSAGE_TYPE and msg.request in self._subscribe_reqs: on_reply = self._subscribe_reqs.pop(msg.request).on_reply # ERROR reply to UNSUBSCRIBE elif msg.request_type == message.Unsubscribe.MESSAGE_TYPE and msg.request in self._unsubscribe_reqs: on_reply = self._unsubscribe_reqs.pop(msg.request).on_reply # ERROR reply to REGISTER elif msg.request_type == message.Register.MESSAGE_TYPE and msg.request in self._register_reqs: on_reply = self._register_reqs.pop(msg.request).on_reply # ERROR reply to UNREGISTER elif msg.request_type == message.Unregister.MESSAGE_TYPE and msg.request in self._unregister_reqs: on_reply = self._unregister_reqs.pop(msg.request).on_reply if on_reply: txaio.reject(on_reply, self._exception_from_message(msg)) else: raise ProtocolError( "WampAppSession.onMessage(): ERROR received for non-pending request_type {0} and request ID {1}" .format(msg.request_type, msg.request)) else: raise ProtocolError("Unexpected message {0}".format( msg.__class__))
def hello(self, realm: str, details: types.HelloDetails): self.log.debug( '{func}::hello(realm="{realm}", details.authid="{authid}", details.authrole="{authrole}")', func=hltype(self.hello), realm=hlid(realm), authid=hlid(details.authid), authrole=hlid(details.authrole)) # the channel binding requested by the client authenticating channel_binding = details.authextra.get( 'channel_binding', None) if details.authextra else None if channel_binding is not None and channel_binding not in [ 'tls-unique' ]: return types.Deny( message='invalid channel binding type "{}" requested'.format( channel_binding)) else: self.log.debug( "WAMP-cryptosign CHANNEL BINDING requested: channel_binding={channel_binding}, channel_id={channel_id}", channel_binding=channel_binding, channel_id=self._channel_id) # 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' # get client's pubkey, if it was provided in authextra pubkey = None if details.authextra and 'pubkey' in details.authextra: pubkey = details.authextra['pubkey'] # if the client provides it's public key, that's enough to identify, # and we can infer the authid from that. BUT: that requires that # there is a 1:1 relation between authid's and pubkey's !! see below (*) if self._authid is None: if pubkey: # we do a naive search, but that is ok, since "static mode" is from # node configuration, and won't contain a lot principals anyway for _authid, _principal in self._config.get( 'principals', {}).items(): if pubkey in _principal['authorized_keys']: # (*): this is necessary to detect multiple authid's having the same pubkey # in which case we couldn't reliably map the authid from the pubkey if self._authid is None: self._authid = _authid else: return types.Deny( message= 'cannot infer client identity from pubkey: multiple authids ' 'in principal database have this pubkey') if self._authid is None: return types.Deny( message= 'cannot identify client: no authid requested and no principal found ' 'for provided extra.pubkey') else: return types.Deny( message= 'cannot identify client: no authid requested and no extra.pubkey provided' ) if self._authid in self._config.get('principals', {}): principal = self._config['principals'][self._authid] if pubkey and (pubkey not in principal['authorized_keys']): return types.Deny( message= 'extra.pubkey provided does not match any one of authorized_keys for the principal' ) error = self._assign_principal(principal) if error: return error self._verify_key = VerifyKey(pubkey, encoder=nacl.encoding.HexEncoder) extra = self._compute_challenge(channel_binding) return types.Challenge(self._authmethod, extra) else: return types.Deny( message='no principal with authid "{}" exists'.format( details.authid)) elif self._config['type'] == 'dynamic': self._authprovider = 'dynamic' d = Deferred() d1 = txaio.as_future(self._init_dynamic_authenticator) def initialized(error=None): if error: d.errback(error) return self._session_details[ 'authmethod'] = self._authmethod # from AUTHMETHOD, via base self._session_details['authid'] = details.authid self._session_details['authrole'] = details.authrole self._session_details['authextra'] = details.authextra self.log.debug( 'Calling dynamic authenticator [proc="{proc}", realm="{realm}", session={session}, authid="{authid}", authrole="{authrole}"]', proc=self._authenticator, realm=self._authenticator_session._realm, session=self._authenticator_session._session_id, authid=self._authenticator_session._authid, authrole=self._authenticator_session._authrole) d2 = self._authenticator_session.call(self._authenticator, realm, details.authid, self._session_details) def on_authenticate_ok(principal): self.log.debug( '{klass}.hello(realm="{realm}", details={details}) -> on_authenticate_ok(principal={principal})', klass=self.__class__.__name__, realm=realm, details=details, principal=principal) error = self._assign_principal(principal) if error: d.callback(error) return self._verify_key = VerifyKey( principal['pubkey'], encoder=nacl.encoding.HexEncoder) extra = self._compute_challenge(channel_binding) d.callback(types.Challenge(self._authmethod, extra)) def on_authenticate_error(err): self.log.debug( '{klass}.hello(realm="{realm}", details={details}) -> on_authenticate_error(err={err})', klass=self.__class__.__name__, realm=realm, details=details, err=err) try: d.callback( self._marshal_dynamic_authenticator_error(err)) except: self.log.failure() d.callback(error) d2.addCallbacks(on_authenticate_ok, on_authenticate_error) return d2 def initialized_error(fail): self.log.failure('Internal error (3): {log_failure.value}', failure=fail) d.errback(fail) d1.addCallbacks(initialized, initialized_error) return d elif self._config['type'] == 'function': self._authprovider = 'function' init_d = txaio.as_future(self._init_function_authenticator) def init(error): if error: return error self._session_details[ 'authmethod'] = self._authmethod # from AUTHMETHOD, via base self._session_details['authid'] = details.authid self._session_details['authrole'] = details.authrole self._session_details['authextra'] = details.authextra auth_d = txaio.as_future(self._authenticator, realm, details.authid, self._session_details) def on_authenticate_ok(principal): self.log.info( '{klass}.hello(realm="{realm}", details={details}) -> on_authenticate_ok(principal={principal})', klass=self.__class__.__name__, realm=realm, details=details, principal=principal) error = self._assign_principal(principal) if error: return error self._verify_key = VerifyKey( principal['pubkey'], encoder=nacl.encoding.HexEncoder) extra = self._compute_challenge(channel_binding) return types.Challenge(self._authmethod, extra) 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) auth_d.addCallbacks(on_authenticate_ok, on_authenticate_error) return auth_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']))
def onHello(self, realm, details): try: # check if the realm the session wants to join actually exists # if realm not in self._router_factory: return types.Deny( ApplicationError.NO_SUCH_REALM, message="no realm '{}' exists on this router".format( realm)) authmethods = details.authmethods or ["anonymous"] # 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( authid=self._transport._authid, authrole=self._transport._authrole, authmethod=self._transport._authmethod, authprovider=self._transport._authprovider) 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: # if authentication is enabled on the transport .. # if "auth" in self._transport_config: # iterate over authentication methods announced by client .. # for authmethod in authmethods: # .. and if the configuration has an entry for the authmethod # announced, process .. if authmethod in self._transport_config["auth"]: # "WAMP-Challenge-Response" authentication # if authmethod == u"wampcra": cfg = self._transport_config['auth']['wampcra'] if cfg['type'] == 'static': if details.authid in cfg.get('users', {}): user = cfg['users'][details.authid] # the authid the session will be authenticated as is from the user data, or when # the user data doesn't contain an authid, from the HELLO message the client sent # authid = user.get( "authid", details.authid) # construct a pending WAMP-CRA authentication # self._pending_auth = PendingAuthWampCra( details.pending_session, authid, user['role'], u'static', user['secret'].encode('utf8')) # send challenge to client # extra = { u'challenge': self._pending_auth.challenge } # when using salted passwords, provide the client with # the salt and then PBKDF2 parameters used # if 'salt' in user: extra[u'salt'] = user['salt'] extra[u'iterations'] = user.get( 'iterations', 1000) extra[u'keylen'] = user.get( 'keylen', 32) return types.Challenge( u'wampcra', extra) else: return types.Deny( message= "no user with authid '{}' in user database" .format(details.authid)) elif cfg['type'] == 'dynamic': # call the configured dynamic authenticator procedure # via the router's service session # service_session = self._router_factory.get( realm)._realm.session session_details = { # forward transport level details of the WAMP session that # wishes to authenticate 'transport': self._transport._transport_info, # the following WAMP session ID will be assigned to the session # if (and only if) the subsequent authentication succeeds. 'session': self._pending_session_id } d = service_session.call( cfg['authenticator'], realm, details.authid, session_details) def on_authenticate_ok(user): # the authid the session will be authenticated as is from the dynamic # authenticator response, or when the response doesn't contain an authid, # from the HELLO message the client sent # authid = user.get( "authid", details.authid) # construct a pending WAMP-CRA authentication # self._pending_auth = PendingAuthWampCra( details.pending_session, authid, user['role'], u'dynamic', user['secret'].encode('utf8')) # send challenge to client # extra = { u'challenge': self._pending_auth.challenge } # when using salted passwords, provide the client with # the salt and the PBKDF2 parameters used # if 'salt' in user: extra[u'salt'] = user['salt'] extra[u'iterations'] = user.get( 'iterations', 1000) extra[u'keylen'] = user.get( 'keylen', 32) return types.Challenge( u'wampcra', extra) 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) d.addCallbacks(on_authenticate_ok, on_authenticate_error) return d else: return types.Deny( message= "illegal WAMP-CRA authentication config (type '{0}' is unknown)" .format(cfg['type'])) # WAMP-Ticket authentication # elif authmethod == u"ticket": cfg = self._transport_config['auth']['ticket'] # use static principal database from configuration # if cfg['type'] == 'static': if details.authid in cfg.get( 'principals', {}): principal = cfg['principals'][ details.authid] # the authid the session will be authenticated as is from the principal data, or when # the principal data doesn't contain an authid, from the HELLO message the client sent # authid = principal.get( "authid", details.authid) self._pending_auth = PendingAuthTicket( realm, authid, principal['role'], u'static', principal['ticket'].encode('utf8')) return types.Challenge(u'ticket') else: return types.Deny( message= "no principal with authid '{}' in principal database" .format(details.authid)) # use configured procedure to dynamically get a ticket for the principal # elif cfg['type'] == 'dynamic': self._pending_auth = PendingAuthTicket( realm, details.authid, None, cfg['authenticator'], None) return types.Challenge(u'ticket') else: return types.Deny( message= "illegal WAMP-Ticket authentication config (type '{0}' is unknown)" .format(cfg['type'])) # "Mozilla Persona" authentication # elif authmethod == u"mozilla_persona": cfg = self._transport_config['auth'][ 'mozilla_persona'] audience = cfg.get('audience', self._transport._origin) provider = cfg.get( 'provider', "https://verifier.login.persona.org/verify" ) # authrole mapping # authrole = cfg.get('role', 'anonymous') # check if role exists on realm anyway # if not self._router_factory[realm].has_role( authrole): return types.Deny( ApplicationError.NO_SUCH_ROLE, message= "authentication failed - realm '{}' has no role '{}'" .format(realm, authrole)) # ok, now challenge the client for doing Mozilla Persona auth. # self._pending_auth = PendingAuthPersona( provider, audience, authrole) return types.Challenge("mozilla-persona") # "Anonymous" authentication # elif authmethod == u"anonymous": cfg = self._transport_config['auth'][ 'anonymous'] # authrole mapping # authrole = cfg.get('role', 'anonymous') # check if role exists on realm anyway # if not self._router_factory[realm].has_role( authrole): return types.Deny( ApplicationError.NO_SUCH_ROLE, message= "authentication failed - realm '{}' has no role '{}'" .format(realm, authrole)) # authid generation 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.newid(24) self._transport._authid = authid self._transport._authrole = authrole self._transport._authmethod = authmethod return types.Accept( authid=authid, authrole=authrole, authmethod=self._transport._authmethod) # "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. pass # Unknown authentication method # else: self.log.info("unknown authmethod '{}'".format( authmethod)) return types.Deny( message="unknown authentication method {}". format(authmethod)) # if authentication is configured, by default, deny. # return types.Deny( message= "authentication using method '{}' denied by configuration" .format(authmethod)) else: # if authentication is _not_ configured, by default, allow anyone. # # authid generation 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.newid(24) return types.Accept(authid=authid, authrole="anonymous", authmethod="anonymous") except Exception as e: traceback.print_exc() return types.Deny(message="internal error: {}".format(e))
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 def on_authenticate_ok(principal): error = self._assign_principal(principal) if error: return error # now compute CHALLENGE.Extra and signature expected extra, self._signature = self._compute_challenge(principal) return types.Challenge(self._authmethod, extra) def on_authenticate_error(err): return self._marshal_dynamic_authenticator_error(err) # use static principal database from configuration if self._config['type'] == 'static': self._authprovider = 'static' if self._authid in self._config.get('users', {}): principal = self._config['users'][self._authid] error = self._assign_principal(principal) if error: return error # now compute CHALLENGE.Extra and signature as # expected for WAMP-CRA extra, self._signature = self._compute_challenge(principal) return types.Challenge(self._authmethod, extra) else: return types.Deny( message='no principal with authid "{}" exists'.format( details.authid)) # use configured procedure to dynamically get a ticket for the principal elif self._config['type'] == 'dynamic': self._authprovider = 'dynamic' init_d = txaio.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['authid'] = details.authid self._session_details['authrole'] = details.authrole self._session_details['authextra'] = details.authextra d = self._authenticator_session.call(self._authenticator, realm, details.authid, self._session_details) d.addCallbacks(on_authenticate_ok, on_authenticate_error) return d init_d.addBoth(init) return init_d elif self._config['type'] == 'function': self._authprovider = 'function' init_d = txaio.as_future(self._init_function_authenticator) def init(result): if result: return result self._session_details[ 'authmethod'] = self._authmethod # from AUTHMETHOD, via base self._session_details['authid'] = details.authid self._session_details['authrole'] = details.authrole self._session_details['authextra'] = details.authextra auth_d = txaio.as_future(self._authenticator, realm, details.authid, self._session_details) auth_d.addCallbacks(on_authenticate_ok, on_authenticate_error) return auth_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']))