def onMessage(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage` """ if self._session_id is None: if not self._pending_session_id: self._pending_session_id = util.id() def welcome(realm, authid=None, authrole=None, authmethod=None, authprovider=None, authextra=None, custom=None): self._realm = realm self._session_id = self._pending_session_id self._pending_session_id = None self._goodbye_sent = False self._router = self._router_factory.get(realm) if not self._router: # should not arrive here raise Exception( "logic error (no realm at a stage were we should have one)" ) self._authid = authid self._authrole = authrole self._authmethod = authmethod self._authprovider = authprovider roles = self._router.attach(self) msg = message.Welcome(self._session_id, roles, realm=realm, authid=authid, authrole=authrole, authmethod=authmethod, authprovider=authprovider, authextra=authextra, custom=custom) self._transport.send(msg) self.onJoin( SessionDetails(self._realm, self._session_id, self._authid, self._authrole, self._authmethod, self._authprovider, self._authextra)) # the first message MUST be HELLO if isinstance(msg, message.Hello): self._session_roles = msg.roles details = types.HelloDetails( realm=msg.realm, authmethods=msg.authmethods, authid=msg.authid, authrole=msg.authrole, authextra=msg.authextra, session_roles=msg.roles, pending_session=self._pending_session_id) d = txaio.as_future(self.onHello, msg.realm, details) def success(res): msg = None if isinstance(res, types.Accept): custom = { # FIXME: # u'x_cb_node_id': self._router_factory._node_id u'x_cb_node_id': None } welcome(res.realm, res.authid, res.authrole, res.authmethod, res.authprovider, res.authextra, custom) elif isinstance(res, types.Challenge): msg = message.Challenge(res.method, res.extra) elif isinstance(res, types.Deny): msg = message.Abort(res.reason, res.message) else: pass if msg: self._transport.send(msg) txaio.add_callbacks(d, success, self._swallow_error_and_abort) elif isinstance(msg, message.Authenticate): d = txaio.as_future(self.onAuthenticate, msg.signature, {}) def success(res): msg = None if isinstance(res, types.Accept): custom = { # FIXME: # u'x_cb_node_id': self._router_factory._node_id u'x_cb_node_id': None } welcome(res.realm, res.authid, res.authrole, res.authmethod, res.authprovider, res.authextra, custom) elif isinstance(res, types.Deny): msg = message.Abort(res.reason, res.message) else: pass if msg: self._transport.send(msg) txaio.add_callbacks(d, success, self._swallow_error_and_abort) elif isinstance(msg, message.Abort): # fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) self._session_id = None self._pending_session_id = None # self._transport.close() else: # raise ProtocolError(u"PReceived {0} message while session is not joined".format(msg.__class__)) # self.log.warn('Protocol state error - received {message} while session is not joined') # swallow all noise like still getting PUBLISH messages from log event forwarding - maybe FIXME pass else: if isinstance(msg, message.Hello): raise ProtocolError( u"HELLO message received, while session is already established" ) elif isinstance(msg, message.Goodbye): if not self._goodbye_sent: # The peer wants to close: answer with GOODBYE reply. # Note: We MUST NOT send any WAMP message _after_ GOODBYE reply = message.Goodbye() self._transport.send(reply) self._goodbye_sent = True else: # This is the peer's GOODBYE reply to our own earlier GOODBYE pass # We need to first detach the session from the router before # erasing the session ID below .. try: self._router.detach(self) except Exception: self.log.failure("Internal error") # In order to send wamp.session.on_leave properly # (i.e. *with* the proper session_id) we save it previous_session_id = self._session_id # At this point, we've either sent GOODBYE already earlier, # or we have just responded with GOODBYE. In any case, we MUST NOT # send any WAMP message from now on: # clear out session ID, so that anything that might be triggered # in the onLeave below is prohibited from sending WAMP stuff. # E.g. the client might have been subscribed to meta events like # wamp.session.on_leave - and we must not send that client's own # leave to itself! self._session_id = None self._pending_session_id = None # publish event, *after* self._session_id is None so # that we don't publish to ourselves as well (if this # session happens to be subscribed to wamp.session.on_leave) if self._service_session: self._service_session.publish( u'wamp.session.on_leave', previous_session_id, ) # fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) # don't close the transport, as WAMP allows to reattach a session # to the same or a different realm without closing the transport # self._transport.close() else: self._router.process(self, msg)
def onMessage(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage` """ if self._session_id is None: if not self._pending_session_id: self._pending_session_id = util.id() def welcome(realm, authid=None, authrole=None, authmethod=None, authprovider=None): self._session_id = self._pending_session_id self._pending_session_id = None self._goodbye_sent = False self._router = self._router_factory.get(realm) if not self._router: raise Exception("no such realm") self._authid = authid self._authrole = authrole self._authmethod = authmethod self._authprovider = authprovider roles = self._router.attach(self) msg = message.Welcome(self._session_id, roles, authid=authid, authrole=authrole, authmethod=authmethod, authprovider=authprovider) self._transport.send(msg) self.onJoin( SessionDetails(self._realm, self._session_id, self._authid, self._authrole, self._authmethod, self._authprovider)) ## the first message MUST be HELLO if isinstance(msg, message.Hello): self._realm = msg.realm details = types.HelloDetails(msg.roles, msg.authmethods, msg.authid, self._pending_session_id) d = self._as_future(self.onHello, self._realm, details) def success(res): msg = None if isinstance(res, types.Accept): welcome(self._realm, res.authid, res.authrole, res.authmethod, res.authprovider) elif isinstance(res, types.Challenge): msg = message.Challenge(res.method, res.extra) elif isinstance(res, types.Deny): msg = message.Abort(res.reason, res.message) else: pass if msg: self._transport.send(msg) def failed(err): print(err.value) self._add_future_callbacks(d, success, failed) elif isinstance(msg, message.Authenticate): d = self._as_future(self.onAuthenticate, msg.signature, msg.extra) def success(res): msg = None if isinstance(res, types.Accept): welcome(self._realm, res.authid, res.authrole, res.authmethod, res.authprovider) elif isinstance(res, types.Deny): msg = message.Abort(res.reason, res.message) else: pass if msg: self._transport.send(msg) def failed(err): print(err.value) self._add_future_callbacks(d, success, failed) elif isinstance(msg, message.Abort): ## fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) self._session_id = None self._pending_session_id = None #self._transport.close() else: raise ProtocolError( "Received {0} message, and session is not yet established". format(msg.__class__)) else: if isinstance(msg, message.Hello): raise ProtocolError( u"HELLO message received, while session is already established" ) elif isinstance(msg, message.Goodbye): if not self._goodbye_sent: ## the peer wants to close: send GOODBYE reply reply = message.Goodbye() self._transport.send(reply) ## fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) self._router.detach(self) self._session_id = None self._pending_session_id = None #self._transport.close() elif isinstance(msg, message.Heartbeat): pass ## FIXME else: self._router.process(self, msg)
def onMessage(self, msg): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage` """ if self._session_id is None: if not self._pending_session_id: self._pending_session_id = util.id() def welcome(realm, authid=None, authrole=None, authmethod=None, authprovider=None): self._session_id = self._pending_session_id self._pending_session_id = None self._goodbye_sent = False self._router = self._router_factory.get(realm) if not self._router: raise Exception("no such realm") self._authid = authid self._authrole = authrole self._authmethod = authmethod self._authprovider = authprovider roles = self._router.attach(self) msg = message.Welcome(self._session_id, roles, authid=authid, authrole=authrole, authmethod=authmethod, authprovider=authprovider) self._transport.send(msg) self.onJoin( SessionDetails(self._realm, self._session_id, self._authid, self._authrole, self._authmethod, self._authprovider)) # the first message MUST be HELLO if isinstance(msg, message.Hello): self._realm = msg.realm self._session_roles = msg.roles details = types.HelloDetails(msg.roles, msg.authmethods, msg.authid, self._pending_session_id) d = txaio.as_future(self.onHello, self._realm, details) def success(res): msg = None if isinstance(res, types.Accept): welcome(self._realm, res.authid, res.authrole, res.authmethod, res.authprovider) elif isinstance(res, types.Challenge): msg = message.Challenge(res.method, res.extra) elif isinstance(res, types.Deny): msg = message.Abort(res.reason, res.message) else: pass if msg: self._transport.send(msg) txaio.add_callbacks(d, success, self._swallow_error_and_abort) elif isinstance(msg, message.Authenticate): d = txaio.as_future(self.onAuthenticate, msg.signature, {}) def success(res): msg = None if isinstance(res, types.Accept): welcome(self._realm, res.authid, res.authrole, res.authmethod, res.authprovider) elif isinstance(res, types.Deny): msg = message.Abort(res.reason, res.message) else: pass if msg: self._transport.send(msg) txaio.add_callbacks(d, success, self._swallow_error_and_abort) elif isinstance(msg, message.Abort): # fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) self._session_id = None self._pending_session_id = None # self._transport.close() else: raise ProtocolError( "Received {0} message, and session is not yet established". format(msg.__class__)) else: if isinstance(msg, message.Hello): raise ProtocolError( u"HELLO message received, while session is already established" ) elif isinstance(msg, message.Goodbye): if not self._goodbye_sent: # The peer wants to close: answer with GOODBYE reply. # Note: We MUST NOT send any WAMP message _after_ GOODBYE reply = message.Goodbye() self._transport.send(reply) self._goodbye_sent = True else: # This is the peer's GOODBYE reply to our own earlier GOODBYE pass # We need to first detach the session from the router before # erasing the session ID below .. self._router.detach(self) # At this point, we've either sent GOODBYE already earlier, # or we have just responded with GOODBYE. In any case, we MUST NOT # send any WAMP message from now on: # clear out session ID, so that anything that might be triggered # in the onLeave below is prohibited from sending WAMP stuff. # E.g. the client might have been subscribed to meta events like # wamp.session.on_leave - and we must not send that client's own # leave to itself! self._session_id = None self._pending_session_id = None # fire callback and close the transport self.onLeave(types.CloseDetails(msg.reason, msg.message)) # don't close the transport, as WAMP allows to reattach a session # to the same or a different realm without closing the transport # self._transport.close() else: self._router.process(self, msg)
def _render_request(self, request): """ Receives an HTTP/POST|PUT request, and then calls the Publisher/Caller processor. """ # read HTTP/POST|PUT body body = request.content.read() args = {native_string(x): y[0] for x, y in request.args.items()} headers = request.requestHeaders # check content type + charset encoding # content_type_header = headers.getRawHeaders(b"content-type", []) if len(content_type_header) > 0: content_type_elements = [ x.strip().lower() for x in content_type_header[0].split(b";") ] else: content_type_elements = [] if self.decode_as_json: # if the client sent a content type, it MUST be one of _ALLOWED_CONTENT_TYPES # (but we allow missing content type .. will catch later during JSON # parsing anyway) if len(content_type_elements) > 0 and \ content_type_elements[0] not in _ALLOWED_CONTENT_TYPES: return self._deny_request( request, 400, accepted=list(_ALLOWED_CONTENT_TYPES), given=content_type_elements[0], log_category="AR452") encoding_parts = {} if len(content_type_elements) > 1: try: for item in content_type_elements: if b"=" not in item: # Don't bother looking at things "like application/json" continue # Parsing things like: # charset=utf-8 _ = native_string(item).split("=") assert len(_) == 2 # We don't want duplicates key = _[0].strip().lower() assert key not in encoding_parts encoding_parts[key] = _[1].strip().lower() except: return self._deny_request(request, 400, log_category="AR450") charset_encoding = encoding_parts.get("charset", "utf-8") if charset_encoding not in ["utf-8", 'utf8']: return self._deny_request(request, 400, log_category="AR450") # enforce "post_body_limit" # body_length = len(body) content_length_header = headers.getRawHeaders(b"content-length", []) if len(content_length_header) == 1: content_length = int(content_length_header[0]) elif len(content_length_header) > 1: return self._deny_request(request, 400, log_category="AR463") else: content_length = body_length if body_length != content_length: # Prevent the body length from being different to the given # Content-Length. This is so that clients can't lie and bypass # length restrictions by giving an incorrect header with a large # body. return self._deny_request(request, 400, bodylen=body_length, conlen=content_length, log_category="AR465") if self._post_body_limit and content_length > self._post_body_limit: return self._deny_request(request, 413, length=content_length, accepted=self._post_body_limit) # # parse/check HTTP/POST|PUT query parameters # # key # if 'key' in args: key_str = args["key"] else: if self._secret: return self._deny_request(request, 400, reason=u"'key' field missing", log_category="AR461") # timestamp # if 'timestamp' in args: timestamp_str = args["timestamp"] try: ts = datetime.datetime.strptime(native_string(timestamp_str), "%Y-%m-%dT%H:%M:%S.%fZ") delta = abs((ts - datetime.datetime.utcnow()).total_seconds()) if self._timestamp_delta_limit and delta > self._timestamp_delta_limit: return self._deny_request(request, 400, log_category="AR464") except ValueError: return self._deny_request( request, 400, reason= u"invalid timestamp '{0}' (must be UTC/ISO-8601, e.g. '2011-10-14T16:59:51.123Z')" .format(native_string(timestamp_str)), log_category="AR462") else: if self._secret: return self._deny_request( request, 400, reason= u"signed request required, but mandatory 'timestamp' field missing", log_category="AR461") # seq # if 'seq' in args: seq_str = args["seq"] try: # FIXME: check sequence seq = int(seq_str) # noqa except: return self._deny_request( request, 400, reason=u"invalid sequence number '{0}' (must be an integer)" .format(native_string(seq_str)), log_category="AR462") else: if self._secret: return self._deny_request(request, 400, reason=u"'seq' field missing", log_category="AR461") # nonce # if 'nonce' in args: nonce_str = args["nonce"] try: # FIXME: check nonce nonce = int(nonce_str) # noqa except: return self._deny_request( request, 400, reason=u"invalid nonce '{0}' (must be an integer)".format( native_string(nonce_str)), log_category="AR462") else: if self._secret: return self._deny_request(request, 400, reason=u"'nonce' field missing", log_category="AR461") # signature # if 'signature' in args: signature_str = args["signature"] else: if self._secret: return self._deny_request(request, 400, reason=u"'signature' field missing", log_category="AR461") # do more checks if signed requests are required # if self._secret: if key_str != self._key: return self._deny_request( request, 401, reason=u"unknown key '{0}' in signed request".format( native_string(key_str)), log_category="AR460") # Compute signature: HMAC[SHA256]_{secret} (key | timestamp | seq | nonce | body) => signature hm = hmac.new(self._secret, None, hashlib.sha256) hm.update(key_str) hm.update(timestamp_str) hm.update(seq_str) hm.update(nonce_str) hm.update(body) signature_recomputed = base64.urlsafe_b64encode(hm.digest()) if signature_str != signature_recomputed: return self._deny_request(request, 401, log_category="AR459") else: self.log.debug("REST request signature valid.", log_category="AR203") # user_agent = headers.get("user-agent", "unknown") client_ip = request.getClientIP() is_secure = request.isSecure() # enforce client IP address # if self._require_ip: ip = ip_address(client_ip) allowed = False for net in self._require_ip: if ip in net: allowed = True break if not allowed: return self._deny_request(request, 400, log_category="AR466") # enforce TLS # if self._require_tls: if not is_secure: return self._deny_request( request, 400, reason=u"request denied because not using TLS") # authenticate request # # TODO: also support HTTP Basic AUTH for ticket def on_auth_ok(value): if value is True: # treat like original behavior and just accept the request_id pass elif isinstance(value, types.Accept): self._session._authid = value.authid self._session._authrole = value.authrole # realm? else: # FIXME: not returning deny request... probably not ideal request.write( self._deny_request(request, 401, reason=u"not authorized", log_category="AR401")) request.finish() return _validator.reset() validation_result = _validator.validate(body) # validate() returns a 4-tuple, of which item 0 is whether it # is valid if not validation_result[0]: request.write( self._deny_request(request, 400, log_category="AR451")) request.finish() return event = body.decode('utf8') if self.decode_as_json: try: event = json.loads(event) except Exception as e: request.write( self._deny_request(request, 400, exc=e, log_category="AR453")) request.finish() return if not isinstance(event, dict): request.write( self._deny_request(request, 400, log_category="AR454")) request.finish() return d = maybeDeferred(self._process, request, event) def finish(value): if isinstance(value, bytes): request.write(value) request.finish() d.addCallback(finish) def on_auth_error(err): # XXX: is it ideal to write to the request? request.write( self._deny_request(request, 401, reason=u"not authorized", log_category="AR401")) request.finish() return authmethod = None authid = None signature = None authorization_header = headers.getRawHeaders(b"authorization", []) if len(authorization_header) == 1: # HTTP Basic Authorization will be processed as ticket authentication authorization = authorization_header[0] auth_scheme, auth_details = authorization.split(b" ", 1) if auth_scheme.lower() == b"basic": try: credentials = binascii.a2b_base64(auth_details + b'===') credentials = credentials.split(b":", 1) if len(credentials) == 2: authmethod = "ticket" authid = credentials[0].decode("utf-8") signature = credentials[1].decode("utf-8") else: return self._deny_request(request, 401, reason=u"not authorized", log_category="AR401") except binascii.Error: # authentication failed return self._deny_request(request, 401, reason=u"not authorized", log_category="AR401") elif 'authmethod' in args and args['authmethod'].decode( "utf-8") == 'ticket': if "ticket" not in args or "authid" not in args: # AR401 - fail if the ticket or authid are not in the args on_auth_ok(False) else: authmethod = "ticket" authid = args['authid'].decode("utf-8") signature = args['ticket'].decode("utf-8") if authmethod and authid and signature: hdetails = types.HelloDetails(authid=authid, authmethods=[authmethod]) # wire up some variables for the authenticators to work, this is hackish # a custom header based authentication scheme can be implemented # without adding alternate authenticators by forwarding all headers. self._session._transport._transport_info = { "http_headers_received": { native_string(x).lower(): native_string(y[0]) for x, y in request.requestHeaders.getAllRawHeaders() } } self._session._pending_session_id = None self._session._router_factory = self._session._transport._routerFactory if authmethod == "ticket": self._pending_auth = PendingAuthTicket( self._session, self._auth_config['ticket']) self._pending_auth.hello(self._session._realm, hdetails) auth_d = maybeDeferred(self._pending_auth.authenticate, signature) auth_d.addCallbacks(on_auth_ok, on_auth_error) else: # don't return the value or it will be written to the request on_auth_ok(True) return server.NOT_DONE_YET
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'))
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 authmethods = msg.authmethods or ['anonymous'] 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) # allow "Personality" classes to add authmethods extra_auth_methods = dict() # if self._router_factory._worker: # personality = self._router_factory._worker.personality # extra_auth_methods = personality.EXTRA_AUTH_METHODS 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 PendingAuthKlass = AUTHMETHOD_MAP[authmethod] # pending_session_id, transport_info, realm_container, config self._pending_auth = PendingAuthKlass( self._pending_session_id, self.transport._transport_info, # FIXME * no_such_role self._controller, auth_config[authmethod], ) try: 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=PendingAuthKlass, hello_result=hello_result) if isinstance(hello_result, types.Accept): try: 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): 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) elif isinstance(hello_result, types.Challenge): self.transport.send(message.Challenge(hello_result.method, extra=hello_result.extra)) elif isinstance(hello_result, types.Deny): self.transport.send(message.Abort(hello_result.reason, message=hello_result.message)) else: # should not arrive here: logic error 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'))