Пример #1
0
    def hello(self, realm, details):

        # remember the realm the client requested to join (if any)
        self._realm = realm

        # remember the authid the client wants to identify as (if any)
        self._authid = details.authid

        # WAMP-Ticket "static"
        if self._config[u"type"] == u"static":

            self._authprovider = u"static"
            self._authid = util.generate_serial_number()

            # FIXME: if cookie tracking is enabled, set authid to cookie value
            # self._authid = self._transport._cbtid

            principal = self._config

            error = self._assign_principal(principal)
            if error:
                return error

            return self._accept()

        # WAMP-Ticket "dynamic"
        elif self._config[u"type"] == u"dynamic":

            self._authprovider = u"dynamic"
            self._authid = util.generate_serial_number()

            error = self._init_dynamic_authenticator()
            if error:
                return error

            self._session_details[u"authmethod"] = self._authmethod  # from AUTHMETHOD, via base
            self._session_details[u"authextra"] = details.authextra
            d = self._authenticator_session.call(self._authenticator, self._realm, self._authid, self._session_details)

            def on_authenticate_ok(principal):
                error = self._assign_principal(principal)
                if error:
                    return error

                return self._accept()

            def on_authenticate_error(err):
                return self._marshal_dynamic_authenticator_error(err)

            d.addCallbacks(on_authenticate_ok, on_authenticate_error)

            return d

        else:
            # should not arrive here, as config errors should be caught earlier
            return types.Deny(
                message=u'invalid authentication configuration (authentication type "{}" is unknown)'.format(
                    self._config["type"]
                )
            )
Пример #2
0
    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']))
Пример #3
0
def fill_session(session):
    session.session = util.id()
    session.joined_at = time_ns() - 723 * 10**9
    session.left_at = time_ns()
    session.realm = 'realm-{}'.format(uuid.uuid4())
    session.authid = util.generate_serial_number()
    session.authrole = random.choice(['admin', 'user*', 'guest', 'anon*'])
Пример #4
0
    def hello(self, realm, details):

        # remember the realm the client requested to join (if any)
        self._realm = realm

        # remember the authid the client wants to identify as (if any)
        self._authid = details.authid or util.generate_serial_number()

        self._session_details[u'authmethod'] = u'anonymous'
        self._session_details[u'authextra'] = details.authextra

        # WAMP-anonymous "static"
        if self._config[u'type'] == u'static':

            self._authprovider = u'static'

            # FIXME: if cookie tracking is enabled, set authid to cookie value
            # self._authid = self._transport._cbtid

            principal = {
                u'authid': self._authid,
                u'role': details.authrole or self._config.get(u'role', u'anonymous'),
                u'extra': details.authextra
            }

            error = self._assign_principal(principal)
            if error:
                return error

            return self._accept()

        # WAMP-Ticket "dynamic"
        elif self._config[u'type'] == u'dynamic':

            self._authprovider = u'dynamic'

            error = self._init_dynamic_authenticator()
            if error:
                return error

            d = self._authenticator_session.call(self._authenticator, self._realm, self._authid, self._session_details)

            def on_authenticate_ok(principal):
                error = self._assign_principal(principal)
                if error:
                    return error

                return self._accept()

            def on_authenticate_error(err):
                return self._marshal_dynamic_authenticator_error(err)

            d.addCallbacks(on_authenticate_ok, on_authenticate_error)

            return d

        else:
            # should not arrive here, as config errors should be caught earlier
            return types.Deny(message=u'invalid authentication configuration (authentication type "{}" is unknown)'.format(self._config['type']))
Пример #5
0
    def onHello(self, realm, details):

        try:
            # default authentication method is "WAMP-Anonymous" if client doesn't specify otherwise
            authmethods = details.authmethods or [u'anonymous']

            # if the client had a reassigned realm during authentication, restore it from the cookie
            if hasattr(self._transport,
                       '_authrealm') and self._transport._authrealm:
                realm = self._transport._authrealm

            # perform authentication
            if self._transport._authid is not None and (
                    self._transport._authmethod == u'trusted'
                    or self._transport._authprovider in authmethods):

                # already authenticated .. e.g. via HTTP Cookie or TLS client-certificate

                # check if role still exists on realm
                allow = self._router_factory[realm].has_role(
                    self._transport._authrole)

                if allow:
                    return types.Accept(
                        realm=realm,
                        authid=self._transport._authid,
                        authrole=self._transport._authrole,
                        authmethod=self._transport._authmethod,
                        authprovider=self._transport._authprovider,
                        authextra=None)
                else:
                    return types.Deny(
                        ApplicationError.NO_SUCH_ROLE,
                        message=
                        "session was previously authenticated (via transport), but role '{}' no longer exists on realm '{}'"
                        .format(self._transport._authrole, realm))

            else:
                auth_config = self._transport_config.get(u'auth', None)

                if not auth_config:
                    # if authentication is _not_ configured, allow anyone to join as "anonymous"!

                    # .. but don't if the client isn't ready/willing to go on "anonymous"
                    if u'anonymous' not in authmethods:
                        return types.Deny(
                            ApplicationError.NO_AUTH_METHOD,
                            message=
                            u'cannot authenticate using any of the offered authmethods {}'
                            .format(authmethods))

                    if not realm:
                        return types.Deny(ApplicationError.NO_SUCH_REALM,
                                          message=u'no realm requested')

                    if realm not in self._router_factory:
                        return types.Deny(
                            ApplicationError.NO_SUCH_REALM,
                            message=u'no realm "{}" exists on this router'.
                            format(realm))

                    # we ignore any details.authid the client might have announced, and use
                    # a cookie value or a random value
                    if hasattr(self._transport,
                               "_cbtid") and self._transport._cbtid:
                        # if cookie tracking is enabled, set authid to cookie value
                        authid = self._transport._cbtid
                    else:
                        # if no cookie tracking, generate a random value for authid
                        authid = util.generate_serial_number()

                    return types.Accept(realm=realm,
                                        authid=authid,
                                        authrole=u'anonymous',
                                        authmethod=u'anonymous',
                                        authprovider=u'static',
                                        authextra=None)

                else:
                    # iterate over authentication methods announced by client ..
                    for authmethod in authmethods:

                        # invalid authmethod
                        if authmethod not in AUTHMETHODS:
                            return types.Deny(
                                message=u'invalid authmethod "{}"'.format(
                                    authmethod))

                        # authmethod not configured
                        if authmethod not in auth_config:
                            self.log.debug(
                                "client requested valid, but unconfigured authentication method {authmethod}",
                                authmethod=authmethod)
                            continue

                        # authmethod not available
                        if authmethod not in AUTHMETHOD_MAP:
                            self.log.debug(
                                "client requested valid, but unavailable authentication method {authmethod}",
                                authmethod=authmethod)
                            continue

                        # WAMP-Anonymous, WAMP-Ticket, WAMP-CRA, WAMP-TLS, WAMP-Cryptosign
                        if authmethod in [
                                u'anonymous', u'ticket', u'wampcra', u'tls',
                                u'cryptosign'
                        ]:
                            PendingAuthKlass = AUTHMETHOD_MAP[authmethod]
                            self._pending_auth = PendingAuthKlass(
                                self, auth_config[authmethod])
                            return self._pending_auth.hello(realm, details)

                        # WAMP-Cookie authentication
                        elif authmethod == u'cookie':
                            # the client requested cookie authentication, but there is 1) no cookie set,
                            # or 2) a cookie set, but that cookie wasn't authenticated before using
                            # a different auth method (if it had been, we would never have entered here, since then
                            # auth info would already have been extracted from the transport)
                            # consequently, we skip this auth method and move on to next auth method.
                            continue

                        else:
                            # should not arrive here
                            raise Exception("logic error")

                    # no suitable authmethod found!
                    return types.Deny(
                        ApplicationError.NO_AUTH_METHOD,
                        message=
                        u'cannot authenticate using any of the offered authmethods {}'
                        .format(authmethods))

        except Exception as e:
            self.log.failure('internal error: {log_failure.value}')
            self.log.critical("internal error: {msg}", msg=str(e))
            return types.Deny(message=u'internal error: {}'.format(e))
Пример #6
0
    def onHello(self, realm, details):

        try:
            # default authentication method is "WAMP-Anonymous" if client doesn't specify otherwise
            authmethods = details.authmethods or [u'anonymous']

            # if the client had a reassigned realm during authentication, restore it from the cookie
            if hasattr(self._transport, '_authrealm') and self._transport._authrealm:
                realm = self._transport._authrealm

            # perform authentication
            if self._transport._authid is not None and (self._transport._authmethod == u'trusted' or self._transport._authprovider in authmethods):

                # already authenticated .. e.g. via HTTP Cookie or TLS client-certificate

                # check if role still exists on realm
                allow = self._router_factory[realm].has_role(self._transport._authrole)

                if allow:
                    return types.Accept(realm=realm,
                                        authid=self._transport._authid,
                                        authrole=self._transport._authrole,
                                        authmethod=self._transport._authmethod,
                                        authprovider=self._transport._authprovider,
                                        authextra=None)
                else:
                    return types.Deny(ApplicationError.NO_SUCH_ROLE, message="session was previously authenticated (via transport), but role '{}' no longer exists on realm '{}'".format(self._transport._authrole, realm))

            else:
                auth_config = self._transport_config.get(u'auth', None)

                if not auth_config:
                    # if authentication is _not_ configured, allow anyone to join as "anonymous"!

                    # .. but don't if the client isn't ready/willing to go on "anonymous"
                    if u'anonymous' not in authmethods:
                        return types.Deny(ApplicationError.NO_AUTH_METHOD, message=u'cannot authenticate using any of the offered authmethods {}'.format(authmethods))

                    if not realm:
                        return types.Deny(ApplicationError.NO_SUCH_REALM, message=u'no realm requested')

                    if realm not in self._router_factory:
                        return types.Deny(ApplicationError.NO_SUCH_REALM, message=u'no realm "{}" exists on this router'.format(realm))

                    # we ignore any details.authid the client might have announced, and use
                    # a cookie value or a random value
                    if self._transport._cbtid:
                        # if cookie tracking is enabled, set authid to cookie value
                        authid = self._transport._cbtid
                    else:
                        # if no cookie tracking, generate a random value for authid
                        authid = util.generate_serial_number()

                    return types.Accept(realm=realm,
                                        authid=authid,
                                        authrole=u'anonymous',
                                        authmethod=u'anonymous',
                                        authprovider=u'static',
                                        authextra=None)

                else:
                    # iterate over authentication methods announced by client ..
                    for authmethod in authmethods:

                        # invalid authmethod
                        if authmethod not in AUTHMETHODS:
                            return types.Deny(message=u'invalid authmethod "{}"'.format(authmethod))

                        # authmethod not configured
                        if authmethod not in auth_config:
                            self.log.debug("client requested valid, but unconfigured authentication method {authmethod}", authmethod=authmethod)
                            continue

                        # authmethod not available
                        if authmethod not in AUTHMETHOD_MAP:
                            self.log.debug("client requested valid, but unavailable authentication method {authmethod}", authmethod=authmethod)
                            continue

                        # WAMP-Anonymous, WAMP-Ticket, WAMP-CRA, WAMP-TLS, WAMP-Cryptosign
                        if authmethod in [u'anonymous', u'ticket', u'wampcra', u'tls', u'cryptosign']:
                            PendingAuthKlass = AUTHMETHOD_MAP[authmethod]
                            self._pending_auth = PendingAuthKlass(self, auth_config[authmethod])
                            return self._pending_auth.hello(realm, details)

                        # WAMP-Cookie authentication
                        elif authmethod == u'cookie':
                            # the client requested cookie authentication, but there is 1) no cookie set,
                            # or 2) a cookie set, but that cookie wasn't authenticated before using
                            # a different auth method (if it had been, we would never have entered here, since then
                            # auth info would already have been extracted from the transport)
                            # consequently, we skip this auth method and move on to next auth method.
                            continue

                        else:
                            # should not arrive here
                            raise Exception("logic error")

                    # no suitable authmethod found!
                    return types.Deny(ApplicationError.NO_AUTH_METHOD, message=u'cannot authenticate using any of the offered authmethods {}'.format(authmethods))

        except Exception as e:
            self.log.critical("Internal error: {msg}", msg=str(e))
            return types.Deny(message=u'internal error: {}'.format(e))
Пример #7
0
    def hello(self, realm: str, details: types.SessionDetails):
        self.log.info(
            '{func}(realm={realm}, details.realm={authrealm}, details.authid={authid}, details.authrole={authrole}) [config={config}]',
            func=hltype(self.hello),
            realm=hlid(realm),
            authrealm=hlid(details.realm),
            authid=hlid(details.authid),
            authrole=hlid(details.authrole),
            config=self._config)

        # remember the realm the client requested to join (if any)
        self._realm = realm

        self._authid = self._config.get('authid', util.generate_serial_number())

        self._session_details['authmethod'] = 'anonymous'
        self._session_details['authextra'] = details.authextra

        # WAMP-anonymous "static"
        if self._config['type'] == 'static':

            self._authprovider = 'static'

            # FIXME: if cookie tracking is enabled, set authid to cookie value
            # self._authid = self._transport._cbtid

            principal = {
                'realm': realm,
                'authid': self._authid,
                'role': self._config.get('role', 'anonymous'),
                'extra': details.authextra
            }

            error = self._assign_principal(principal)
            if error:
                return error

            return self._accept()

        # WAMP-Ticket "dynamic"
        elif self._config['type'] == 'dynamic':

            self._authprovider = 'dynamic'

            init_d = as_future(self._init_dynamic_authenticator)

            def init(result):
                if result:
                    return result

                d = self._authenticator_session.call(self._authenticator, self._realm, self._authid,
                                                     self._session_details)

                def on_authenticate_ok(principal):
                    error = self._assign_principal(principal)
                    if error:
                        return error

                    return self._accept()

                def on_authenticate_error(err):
                    return self._marshal_dynamic_authenticator_error(err)

                d.addCallbacks(on_authenticate_ok, on_authenticate_error)

                return d

            init_d.addBoth(init)
            return init_d

        else:
            # should not arrive here, as config errors should be caught earlier
            return types.Deny(message='invalid authentication configuration (authentication type "{}" is unknown)'.
                              format(self._config['type']))
Пример #8
0
    async def _authenticate(self, realm, authid, details, call_details):
        self.log.info(
            '{klass}.authenticate(realm="{realm}", authid="{authid}", details={details})',
            klass=self.__class__.__name__,
            realm=realm,
            authid=authid,
            details=details)

        if 'authmethod' not in details:
            msg = 'missing "authmethod" in authentication details (WAMP HELLO message details)'
            raise ApplicationError(
                self.ERROR_INVALID_AUTH_REQUEST,
                self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg))

        authmethod = details['authmethod']

        if authmethod != 'cryptosign':
            msg = 'authmethod "{}" not permissible'.format(authmethod)
            raise ApplicationError(
                self.ERROR_INVALID_AUTH_REQUEST,
                self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg))

        if 'authextra' not in details:
            msg = 'Must provide authextra for authmethod cryptosign'
            raise ApplicationError(
                self.ERROR_INVALID_AUTH_REQUEST,
                self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg))

        authextra = details['authextra']

        if 'pubkey' not in authextra:
            msg = 'missing public key in authextra for authmethod cryptosign'
            raise ApplicationError(
                self.ERROR_INVALID_AUTH_REQUEST,
                self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg))

        pubkey = authextra['pubkey']
        if isinstance(pubkey, str):
            pubkey = binascii.a2b_hex(without_0x(pubkey))
        assert is_cs_pubkey(pubkey)

        session_id = details['session']
        assert type(session_id) == int

        # FIXME: find a more elegant way to query the db.
        def get_actor(_txn, address):
            _actor = self._schema.actors[_txn, (self._market_oid, address,
                                                ActorType.PROVIDER)]
            if _actor:
                return _actor

            _actor = self._schema.actors[_txn, (self._market_oid, address,
                                                ActorType.CONSUMER)]
            if _actor:
                return _actor

            _actor = self._schema.actors[_txn, (self._market_oid, address,
                                                ActorType.PROVIDER_CONSUMER)]
            if _actor:
                return _actor

        if ('wallet_address' not in authextra or not authextra['wallet_address']) and \
                ('signature' not in authextra or not authextra['signature']):
            with self._db.begin() as txn:
                user_key = self._xbrmm.user_keys[txn, pubkey]
                actor = None
                if user_key:
                    actor = get_actor(txn, bytes(user_key.wallet_address))
                    if actor:
                        authrole = 'user'
                        authid = 'member-{}'.format(
                            binascii.b2a_hex(user_key.wallet_address).decode())
                    else:
                        authrole = 'anonymous'
                        authid = 'anonymous-{}'.format(
                            generate_serial_number())
                else:
                    authrole = 'anonymous'
                    authid = 'anonymous-{}'.format(generate_serial_number())

                self._pubkey_by_session[session_id] = pubkey

                auth = {
                    'pubkey': binascii.b2a_hex(pubkey),
                    'realm': realm,
                    'authid': authid,
                    'role': authrole,
                    'cache': True,
                    'extra': {
                        'actor_type': actor.actor_type if actor else 0
                    }
                }

                self.log.info('{klass}.authenticate(..) => {auth}',
                              klass=self.__class__.__name__,
                              auth=auth)

                return auth

        if ('wallet_address' not in authextra or not authextra['wallet_address']) or \
                ('signature' not in authextra or not authextra['signature']):
            msg = 'Should provide `pubkey`, `wallet_address` and `signature` in authextra ' \
                  'to authenticate new member. To authenticate existing member, only provide ' \
                  '`pubkey`'
            raise ApplicationError(
                self.ERROR_INVALID_AUTH_REQUEST,
                self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg))

        wallet_address = authextra['wallet_address']
        assert is_address(wallet_address)

        signature = authextra['signature']
        assert is_signature(signature)

        try:
            signer_address = recover_eip712_market_member_login(
                wallet_address, pubkey, signature)
        except Exception as e:
            self.log.warn(
                'EIP712 signature recovery failed (wallet_adr={wallet_adr}): {err}',
                wallet_adr=wallet_address,
                err=str(e))
            raise ApplicationError(
                'xbr.error.invalid_signature',
                'EIP712 signature recovery failed ({})'.format(e))

        if signer_address != wallet_address:
            self.log.warn(
                'EIP712 signature invalid: signer_address={signer_address}, wallet_adr={wallet_adr}',
                signer_address=signer_address,
                wallet_adr=wallet_address)
            raise ApplicationError('xbr.error.invalid_signature',
                                   'EIP712 signature invalid')

        with self._db.begin(write=True) as txn:
            account = self._schema.members[txn, wallet_address]
            actor = None
            if account:
                actor = get_actor(txn, wallet_address)
                if actor:
                    user_key = self._xbrmm.user_keys[txn, pubkey]
                    if not user_key:
                        user_key = cfxdb.xbrmm.UserKey()
                        user_key.owner = account.account_oid
                        user_key.pubkey = pubkey
                        user_key.created = np.datetime64(txaio.time_ns(), 'ns')
                        user_key.wallet_address = wallet_address
                        user_key.signature = signature
                        self._xbrmm.user_keys[txn, pubkey] = user_key

                    self._pubkey_by_session[session_id] = pubkey

                    authrole = 'user'
                    # authid = 'member-{}'.format(account.account_oid)
                    # account.account_oid returns a pseudo value because
                    # the "emit" from the xbr contracts does not include
                    # account_oid in it, hence we don't really have that.
                    # To compensate that, we could include wallet address
                    # in authid, so that API calls could validate
                    # if the caller really is the "owner" of a resource.
                    authid = 'member-{}'.format(
                        binascii.b2a_hex(wallet_address).decode())
                else:
                    authrole = 'anonymous'
                    authid = 'anonymous-{}'.format(generate_serial_number())

            else:
                authrole = 'anonymous'
                authid = 'anonymous-{}'.format(generate_serial_number())

        auth = {
            'pubkey': binascii.b2a_hex(pubkey),
            'realm': realm,
            'authid': authid,
            'role': authrole,
            'cache': True,
            'extra': {
                'actor_type': actor.actor_type if actor else 0
            }
        }

        self.log.info('{klass}.authenticate(..) => {auth}',
                      klass=self.__class__.__name__,
                      auth=auth)

        return auth
Пример #9
0
    def start_link(self, link_id, link_config, caller):
        assert type(link_id) == str
        assert isinstance(link_config, RLinkConfig)
        assert isinstance(caller, SessionIdent)

        if link_id in self._links:
            raise ApplicationError('crossbar.error.already_running',
                                   'router link {} already running'.format(link_id))

        # setup local session
        #
        local_extra = {
            'other': None,
            'on_ready': Deferred(),
            'rlink': link_id,
            'forward_events': link_config.forward_local_events,
            'forward_invocations': link_config.forward_local_invocations,
        }
        local_realm = self._realm.config['name']

        local_authid = link_config.authid or util.generate_serial_number()
        local_authrole = 'trusted'
        local_config = ComponentConfig(local_realm, local_extra)
        local_session = RLinkLocalSession(local_config)

        # setup remote session
        #
        remote_extra = {
            'rlink_manager': self,
            'other': None,
            'on_ready': Deferred(),
            'authid': link_config.authid,
            'exclude_authid': link_config.exclude_authid,
            'forward_events': link_config.forward_remote_events,
            'forward_invocations': link_config.forward_remote_invocations,
        }
        remote_realm = link_config.realm
        remote_config = ComponentConfig(remote_realm, remote_extra)
        remote_session = RLinkRemoteSession(remote_config)

        # cross-connect the two sessions
        #
        local_extra['other'] = remote_session
        remote_extra['other'] = local_session

        # the rlink
        #
        rlink = RLink(link_id, link_config)
        self._links[link_id] = rlink
        local_extra['tracker'] = rlink

        # create connecting client endpoint
        #
        connecting_endpoint = create_connecting_endpoint_from_config(
            link_config.transport['endpoint'], self._controller.cbdir, self._controller._reactor, self.log)
        try:
            # connect the local session
            #
            self._realm.controller._router_session_factory.add(
                local_session, self._realm.router, authid=local_authid, authrole=local_authrole, authextra=local_extra)

            yield local_extra['on_ready']

            # connect the remote session
            #
            # remote connection parameters to ApplicationRunner:
            #
            # url: The WebSocket URL of the WAMP router to connect to (e.g. ws://somehost.com:8090/somepath)
            # realm: The WAMP realm to join the application session to.
            # extra: Optional extra configuration to forward to the application component.
            # serializers: List of :class:`autobahn.wamp.interfaces.ISerializer` (or None for default serializers).
            # ssl: None or :class:`twisted.internet.ssl.CertificateOptions`
            # proxy: Explicit proxy server to use; a dict with ``host`` and ``port`` keys
            # headers: Additional headers to send (only applies to WAMP-over-WebSocket).
            # max_retries: Maximum number of reconnection attempts. Unlimited if set to -1.
            # initial_retry_delay: Initial delay for reconnection attempt in seconds (Default: 1.0s).
            # max_retry_delay: Maximum delay for reconnection attempts in seconds (Default: 60s).
            # retry_delay_growth: The growth factor applied to the retry delay between reconnection attempts (Default 1.5).
            # retry_delay_jitter: A 0-argument callable that introduces nose into the delay. (Default random.random)
            #
            remote_runner = ApplicationRunner(
                url=link_config.transport['url'], realm=remote_realm, extra=remote_extra)

            yield remote_runner.run(
                remote_session,
                start_reactor=False,
                auto_reconnect=True,
                endpoint=connecting_endpoint,
                reactor=self._controller._reactor)

            yield remote_extra['on_ready']

        except:
            # make sure to remove the half-initialized link from our map ..
            del self._links[link_id]

            # .. and then re-raise
            raise

        # the router link is established: store final infos
        rlink.started = time_ns()
        rlink.started_by = caller
        rlink.local = local_session
        rlink.remote = remote_session

        return rlink
Пример #10
0
    async def _authenticate(self, realm, authid, details):
        self.log.info(
            '{klass}.authenticate(realm="{realm}", authid="{authid}", details={details})',
            klass=self.__class__.__name__,
            realm=realm,
            authid=authid,
            details=details)

        if 'authmethod' not in details:
            msg = 'missing "authmethod" in authentication details (WAMP HELLO message details)'
            raise ApplicationError(
                self.ERROR_INVALID_AUTH_REQUEST,
                self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg))

        authmethod = details['authmethod']

        if authmethod not in ['cryptosign']:
            msg = 'authmethod "{}" not permissible'.format(authmethod)
            raise ApplicationError(
                self.ERROR_INVALID_AUTH_REQUEST,
                self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg))

        if 'authextra' not in details or 'pubkey' not in details['authextra']:
            msg = 'missing public key in authextra for authmethod cryptosign'
            raise ApplicationError(
                self.ERROR_INVALID_AUTH_REQUEST,
                self.ERROR_INVALID_AUTH_REQUEST_MSG.format(msg))
        pubkey = details['authextra']['pubkey']
        pubkey_raw = binascii.a2b_hex(pubkey)
        assert type(pubkey_raw) == bytes and len(pubkey_raw) == 32

        session_id = details['session']
        assert type(session_id) == int

        with self._db.begin() as txn:
            # double check (again) for username collision, as the mailgun email submit happens async in above after
            # we initially checked for collision
            user_key = self._schema.user_keys[txn, pubkey_raw]
            if user_key:
                account = self._schema.accounts[txn, user_key.owner]
                authrole = 'member'
                # authid = account.username
                authid = 'member-{}'.format(account.oid)
            else:
                account = None
                authrole = 'anonymous'
                authid = 'anonymous-{}'.format(generate_serial_number())

            self._pubkey_by_session[session_id] = pubkey_raw
            if account:
                self._member_by_session[session_id] = account.oid
                if account.oid not in self._sessions_by_member:
                    self._sessions_by_member[account.oid] = []
                self._sessions_by_member[account.oid].append(session_id)

        auth = {
            'pubkey': pubkey,
            'realm': realm,
            'authid': authid,
            'role': authrole,
            'extra': None,
            'cache': True
        }

        self.log.info('{klass}.authenticate(..) => {auth}',
                      klass=self.__class__.__name__,
                      auth=auth)

        return auth
Пример #11
0
    def hello(self, realm, details):

        # remember the realm the client requested to join (if any)
        self._realm = realm

        self._authid = self._config.get('authid',
                                        util.generate_serial_number())

        self._session_details['authmethod'] = 'anonymous'
        self._session_details['authextra'] = details.authextra

        # WAMP-anonymous "static"
        if self._config['type'] == 'static':

            self._authprovider = 'static'

            # FIXME: if cookie tracking is enabled, set authid to cookie value
            # self._authid = self._transport._cbtid

            principal = {
                'authid': self._authid,
                'role': self._config.get('role', 'anonymous'),
                'extra': details.authextra
            }

            error = self._assign_principal(principal)
            if error:
                return error

            return self._accept()

        # WAMP-Ticket "dynamic"
        elif self._config['type'] == 'dynamic':

            self._authprovider = 'dynamic'

            error = self._init_dynamic_authenticator()
            if error:
                return error

            d = self._authenticator_session.call(self._authenticator,
                                                 self._realm, self._authid,
                                                 self._session_details)

            def on_authenticate_ok(principal):
                error = self._assign_principal(principal)
                if error:
                    return error

                return self._accept()

            def on_authenticate_error(err):
                return self._marshal_dynamic_authenticator_error(err)

            d.addCallbacks(on_authenticate_ok, on_authenticate_error)

            return d

        else:
            # should not arrive here, as config errors should be caught earlier
            return types.Deny(
                message=
                'invalid authentication configuration (authentication type "{}" is unknown)'
                .format(self._config['type']))
Пример #12
0
    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'))