Пример #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

        # 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']))
Пример #2
0
    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)
Пример #3
0
                                    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)
Пример #5
0
    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)
        )
Пример #6
0
 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
Пример #7
0
                                    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)
Пример #8
0
 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)
Пример #9
0
    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
Пример #10
0
        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)
Пример #11
0
 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(),
         })
Пример #12
0
            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)
Пример #13
0
            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)
Пример #14
0
 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,
     )
Пример #15
0
                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)
Пример #16
0
 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',
     )
Пример #17
0
    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()
Пример #18
0
        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)
Пример #19
0
    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))
Пример #20
0
    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)
Пример #23
0
    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))
Пример #24
0
    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)
Пример #25
0
   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__))
Пример #26
0
    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']))
Пример #27
0
    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__))
Пример #28
0
    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']))
Пример #29
0
    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))
Пример #30
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

        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']))