Example #1
0
def test_explicit_reactor_coroutine(framework):
    """
    If we set an event-loop, Futures + Tasks should use it.
    """
    pytest.importorskip('asyncio')
    if txaio.using_twisted:
        pytest.skip()

    from asyncio import coroutine

    @coroutine
    def some_coroutine():
        yield 'nothing'

    with patch.object(txaio.config, 'loop') as fake_loop:
        txaio.as_future(some_coroutine)

        if sys.version_info < (3, 4, 2):
            assert len(fake_loop.method_calls) == 2
            c = fake_loop.method_calls[1]
            assert c[0] == 'call_soon'
        else:
            assert len(fake_loop.method_calls) == 1
            c = fake_loop.method_calls[0]
            assert c[0] == 'create_task'
Example #2
0
    def onClose(self, wasClean):
        """
        Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onClose`
        """
        self._transport = None

        if self._session_id:
            # fire callback and close the transport
            d = txaio.as_future(
                self.onLeave,
                types.CloseDetails(
                    reason=types.CloseDetails.REASON_TRANSPORT_LOST,
                    message=
                    "WAMP transport was lost without closing the session before"
                ))

            def _error(e):
                return self._swallow_error(e, "While firing onLeave")

            txaio.add_callbacks(d, None, _error)

            self._session_id = None

        d = txaio.as_future(self.onDisconnect)

        def _error(e):
            return self._swallow_error(e, "While firing onDisconnect")

        txaio.add_callbacks(d, None, _error)
Example #3
0
def test_explicit_reactor_coroutine(framework):
    """
    If we set an event-loop, Futures + Tasks should use it.
    """
    pytest.importorskip('asyncio')
    if txaio.using_twisted:
        pytest.skip()

    from asyncio import coroutine

    @coroutine
    def some_coroutine():
        yield 'nothing'

    with patch.object(txaio.config, 'loop') as fake_loop:
        txaio.as_future(some_coroutine)

        if sys.version_info < (3, 4, 2):
            assert len(fake_loop.method_calls) == 2
            c = fake_loop.method_calls[1]
            assert c[0] == 'call_soon'
        else:
            assert len(fake_loop.method_calls) == 1
            c = fake_loop.method_calls[0]
            assert c[0] == 'create_task'
Example #4
0
    def onClose(self, wasClean):
        """
        Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onClose`
        """
        self._transport = None

        if self._session_id:
            # fire callback and close the transport
            d = txaio.as_future(
                self.onLeave,
                types.CloseDetails(
                    reason=types.CloseDetails.REASON_TRANSPORT_LOST,
                    message="WAMP transport was lost without closing the session before",
                ),
            )

            def _error(e):
                return self._swallow_error(e, "While firing onLeave")

            txaio.add_callbacks(d, None, _error)

            self._session_id = None

        d = txaio.as_future(self.onDisconnect, wasClean)

        def _error(e):
            return self._swallow_error(e, "While firing onDisconnect")

        txaio.add_callbacks(d, None, _error)
Example #5
0
def test_as_future_immediate_none(framework):
    '''
    Returning None immediately from as_future
    '''
    errors = []
    results = []
    calls = []

    def method(*args, **kw):
        calls.append((args, kw))
        return None

    f = txaio.as_future(method, 1, 2, 3, key='word')

    def cb(x):
        results.append(x)

    def errback(f):
        errors.append(f)

    txaio.add_callbacks(f, cb, errback)

    run_once()

    assert len(results) == 1
    assert len(errors) == 0
    assert results[0] is None
    assert calls[0] == ((1, 2, 3), dict(key='word'))
Example #6
0
def test_as_future_exception(framework):
    '''
    Raises an exception from as_future
    '''
    errors = []
    results = []
    calls = []
    exception = RuntimeError("sadness")

    def method(*args, **kw):
        calls.append((args, kw))
        raise exception
    f = txaio.as_future(method, 1, 2, 3, key='word')

    def cb(x):
        results.append(x)

    def errback(f):
        errors.append(f)

    txaio.add_callbacks(f, cb, errback)

    run_once()

    assert len(results) == 0
    assert len(errors) == 1
    assert errors[0].value == exception
    assert calls[0] == ((1, 2, 3), dict(key='word'))
Example #7
0
def test_as_future_exception(framework):
    '''
    Raises an exception from as_future
    '''
    errors = []
    results = []
    calls = []
    exception = RuntimeError("sadness")

    def method(*args, **kw):
        calls.append((args, kw))
        raise exception

    f = txaio.as_future(method, 1, 2, 3, key='word')

    def cb(x):
        results.append(x)

    def errback(f):
        errors.append(f)

    txaio.add_callbacks(f, cb, errback)

    run_once()

    assert len(results) == 0
    assert len(errors) == 1
    assert errors[0].value == exception
    assert calls[0] == ((1, 2, 3), dict(key='word'))
Example #8
0
def _authenticator_for_name(config, controller=None):
    """
    :returns: a future which fires with an authenticator function
        (possibly freshly created)
    """

    create_fqn = config['create']
    create_function = _authenticators.get(create_fqn, None)

    if create_function is None:
        create_module, create_name = create_fqn.rsplit('.', 1)
        _mod = importlib.import_module(create_module)
        try:
            create_authenticator = getattr(_mod, create_name)
        except AttributeError:
            raise RuntimeError(
                "No function '{}' in module '{}'".format(create_name, create_module)
            )
        create_d = txaio.as_future(create_authenticator, config.get('config', dict()), controller)

        def got_authenticator(authenticator):
            _authenticators[create_fqn] = authenticator
            return authenticator
        create_d.addCallback(got_authenticator)

    else:
        create_d = Deferred()
        create_d.callback(create_function)
    return create_d
Example #9
0
def test_as_future_immediate_none(framework):
    '''
    Returning None immediately from as_future
    '''
    errors = []
    results = []
    calls = []

    def method(*args, **kw):
        calls.append((args, kw))
        return None
    f = txaio.as_future(method, 1, 2, 3, key='word')

    def cb(x):
        results.append(x)

    def errback(f):
        errors.append(f)

    txaio.add_callbacks(f, cb, errback)

    run_once()

    assert len(results) == 1
    assert len(errors) == 0
    assert results[0] is None
    assert calls[0] == ((1, 2, 3), dict(key='word'))
Example #10
0
    def _init_function_authenticator(self):
        self.log.debug('{klass}._init_function_authenticator', klass=self.__class__.__name__)

        # import the module for the function
        create_fqn = self._config['create']
        if '.' not in create_fqn:
            return types.Deny(
                ApplicationError.NO_SUCH_PROCEDURE,
                "'function' authenticator has no module: '{}'".format(create_fqn)
            )

        if self._config.get('expose_controller', None):
            from crossbar.worker.controller import WorkerController
            if not isinstance(self._realm_container, WorkerController):
                excp = Exception(
                    "Internal Error: Our container '{}' is not a WorkerController".format(
                        self._realm_container,
                    )
                )
                self.log.failure('{klass} could not expose controller',
                                 klass=self.__class__.__name__, failure=excp)
                raise excp
            controller = self._realm_container
        else:
            controller = None

        create_d = txaio.as_future(_authenticator_for_name, self._config, controller=controller)

        def got_authenticator(authenticator):
            self._authenticator = authenticator
        create_d.addCallback(got_authenticator)
        return create_d
Example #11
0
def test_as_future_recursive(framework):
    '''
    Returns another Future from as_future
    '''
    errors = []
    results = []
    calls = []
    f1 = txaio.create_future_success(42)

    def method(*args, **kw):
        calls.append((args, kw))
        return f1

    f0 = txaio.as_future(method, 1, 2, 3, key='word')

    def cb(x):
        results.append(x)

    def errback(f):
        errors.append(f)

    txaio.add_callbacks(f0, cb, errback)

    run_once()

    assert len(results) == 1
    assert len(errors) == 0
    assert results[0] == 42
    assert calls[0] == ((1, 2, 3), dict(key='word'))
Example #12
0
def test_as_future_recursive(framework):
    '''
    Returns another Future from as_future
    '''
    errors = []
    results = []
    calls = []
    f1 = txaio.create_future_success(42)

    def method(*args, **kw):
        calls.append((args, kw))
        return f1
    f0 = txaio.as_future(method, 1, 2, 3, key='word')

    def cb(x):
        results.append(x)

    def errback(f):
        errors.append(f)

    txaio.add_callbacks(f0, cb, errback)

    run_once()

    assert len(results) == 1
    assert len(errors) == 0
    assert results[0] == 42
    assert calls[0] == ((1, 2, 3), dict(key='word'))
Example #13
0
            def init(error):
                if error:
                    return error

                # now call (via direct Python function call) the user provided authenticator (Python function)
                auth_d = as_future(self._authenticator, realm, details.authid, self._session_details)
                auth_d.addCallbacks(on_authenticate_ok, on_authenticate_error)
                return auth_d
Example #14
0
 def fire(self, event, *args, **kwargs):
     res = []
     if event in self._listeners:
         for handler in self._listeners[event]:
             value = txaio.as_future(handler, *args, **kwargs)
             res.append(value)
     if self._parent is not None:
         res.append(self._parent.fire(event, *args, **kwargs))
     return txaio.gather(res)
Example #15
0
 def fire(self, event, *args, **kwargs):
     res = []
     if event in self._listeners:
         for handler in self._listeners[event]:
             value = txaio.as_future(handler, *args, **kwargs)
             res.append(value)
     if self._parent is not None:
         res.append(self._parent.fire(event, *args, **kwargs))
     return txaio.gather(res)
Example #16
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'

            init_d = as_future(self._init_dynamic_authenticator)

            def init(result):
                if result:
                    return result

                self._session_details[
                    'authmethod'] = self._authmethod  # from AUTHMETHOD, via base
                self._session_details['authextra'] = details.authextra

                return types.Challenge(self._authmethod)

            init_d.addBoth(init)
            return init_d

        else:
            # should not arrive here, as config errors should be caught earlier
            return types.Deny(
                message=
                'invalid authentication configuration (authentication type "{}" is unknown)'
                .format(self._config['type']))
Example #17
0
 def component_start(comp):
     # the future from start() errbacks if we fail, or callbacks
     # when the component is considered "done" (so maybe never)
     d = txaio.as_future(comp.start, reactor)
     txaio.add_callbacks(
         d,
         partial(component_success, comp),
         partial(component_failure, comp),
     )
     return d
Example #18
0
def test_gather_two(framework):
    '''
    Wait for two Futures.
    '''

    errors = []
    results = []
    calls = []

    def foo():
        def codependant(*args, **kw):
            calls.append((args, kw))
            return 42

        return txaio.as_future(codependant)

    def method(*args, **kw):
        calls.append((args, kw))
        return "OHAI"

    f0 = txaio.as_future(method, 1, 2, 3, key='word')
    f1 = txaio.as_future(foo)

    f2 = txaio.gather([f0, f1])

    def done(arg):
        results.append(arg)

    def error(fail):
        errors.append(fail)
        # fail.printTraceback()

    txaio.add_callbacks(f2, done, error)

    for f in [f0, f1, f2]:
        await (f)

    assert len(results) == 1
    assert len(errors) == 0
    assert results[0] == ['OHAI', 42] or results[0] == [42, 'OHAI']
    assert len(calls) == 2
    assert calls[0] == ((1, 2, 3), dict(key='word'))
    assert calls[1] == (tuple(), dict())
    def onOpen(self, transport):
        """
        Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onOpen`
        """
        self._transport = transport
        d = txaio.as_future(self.onConnect)

        def _error(e):
            return self._swallow_error(e, "While firing onConnect")
        txaio.add_callbacks(d, None, _error)
Example #20
0
    def onOpen(self, transport):
        """
        Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onOpen`
        """
        self._transport = transport
        d = txaio.as_future(self.onConnect)

        def _error(e):
            return self._swallow_error(e, "While firing onConnect")
        txaio.add_callbacks(d, None, _error)
Example #21
0
                    def on_join(session, details):
                        self.log.debug("session on_join: {details}", details=details)
                        d = txaio.as_future(self._entry, reactor, session)

                        def setup_success(_):
                            self.log.debug("setup_success")

                        def setup_error(err):
                            self.log.debug("setup_error", err)

                        txaio.add_callbacks(d, setup_success, setup_error)
Example #22
0
def test_gather_two():
    '''
    Wait for two Futures.
    '''

    errors = []
    results = []
    calls = []

    def foo():
        def codependant(*args, **kw):
            calls.append((args, kw))
            return 42
        return txaio.as_future(codependant)

    def method(*args, **kw):
        calls.append((args, kw))
        return "OHAI"
    f0 = txaio.as_future(method, 1, 2, 3, key='word')
    f1 = txaio.as_future(foo)

    f2 = txaio.gather([f0, f1])

    def done(arg):
        results.append(arg)

    def error(fail):
        errors.append(fail)
        # fail.printTraceback()
    txaio.add_callbacks(f2, done, error)

    await(f0)
    await(f1)
    await(f2)

    assert len(results) == 1
    assert len(errors) == 0
    assert results[0] == ['OHAI', 42] or results[0] == [42, 'OHAI']
    assert len(calls) == 2
    assert calls[0] == ((1, 2, 3), dict(key='word'))
    assert calls[1] == (tuple(), dict())
Example #23
0
    def authorize(self, session, uri, action, options):
        """
        Authorizes a session for an action on an URI.

        Implements :func:`autobahn.wamp.interfaces.IRouter.authorize`
        """
        assert(type(uri) == str)
        assert(action in [u'call', u'register', u'publish', u'subscribe'])

        # the role under which the session that wishes to perform the given action on
        # the given URI was authenticated under
        role = session._authrole

        if role in self._roles:
            # the authorizer procedure of the role which we will call ..
            authorize = self._roles[role].authorize
            d = txaio.as_future(authorize, session, uri, action, options)
        else:
            # normally, the role should exist on the router (and hence we should not arrive
            # here), but the role might have been dynamically removed - and anyway, safety first!
            d = txaio.create_future_success(False)

        # XXX would be nicer for dynamic-authorizer authors if we
        # sanity-checked the return-value ('authorization') here
        # (i.e. is it a dict? does it have 'allow' in it? does it have
        # disallowed keys in it?)

        def got_authorization(authorization):
            # backward compatibility
            if isinstance(authorization, bool):
                authorization = {
                    u'allow': authorization,
                    u'cache': False
                }
                if action in [u'call', u'publish']:
                    authorization[u'disclose'] = False

            auto_disclose_trusted = False
            if auto_disclose_trusted and role == u'trusted' and action in [u'call', u'publish']:
                authorization[u'disclose'] = True

            self.log.debug("Authorized action '{action}' for URI '{uri}' by session {session_id} with authid '{authid}' and authrole '{authrole}' -> authorization: {authorization}",
                           session_id=session._session_id,
                           uri=uri,
                           action=action,
                           authid=session._authid,
                           authrole=session._authrole,
                           authorization=authorization)

            return authorization

        d.addCallback(got_authorization)
        return d
Example #24
0
    def authorize(self, session, uri, action, options):
        """
        Authorizes a session for an action on an URI.

        Implements :func:`autobahn.wamp.interfaces.IRouter.authorize`
        """
        assert (type(uri) == str)
        assert (action in [u'call', u'register', u'publish', u'subscribe'])

        # the role under which the session that wishes to perform the given action on
        # the given URI was authenticated under
        role = session._authrole

        if role in self._roles:
            # the authorizer procedure of the role which we will call ..
            authorize = self._roles[role].authorize
            d = txaio.as_future(authorize, session, uri, action, options)
        else:
            # normally, the role should exist on the router (and hence we should not arrive
            # here), but the role might have been dynamically removed - and anyway, safety first!
            d = txaio.create_future_success(False)

        # XXX would be nicer for dynamic-authorizer authors if we
        # sanity-checked the return-value ('authorization') here
        # (i.e. is it a dict? does it have 'allow' in it? does it have
        # disallowed keys in it?)

        def got_authorization(authorization):
            # backward compatibility
            if isinstance(authorization, bool):
                authorization = {u'allow': authorization, u'cache': False}
                if action in [u'call', u'publish']:
                    authorization[u'disclose'] = False

            auto_disclose_trusted = False
            if auto_disclose_trusted and role == u'trusted' and action in [
                    u'call', u'publish'
            ]:
                authorization[u'disclose'] = True

            self.log.debug(
                "Authorized action '{action}' for URI '{uri}' by session {session_id} with authid '{authid}' and authrole '{authrole}' -> authorization: {authorization}",
                session_id=session._session_id,
                uri=uri,
                action=action,
                authid=session._authid,
                authrole=session._authrole,
                authorization=authorization)

            return authorization

        d.addCallback(got_authorization)
        return d
                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
                    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
Example #26
0
                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
                    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
Example #27
0
                    def on_join(session, details):
                        self.log.debug("session on_join: {details}", details=details)
                        d = txaio.as_future(self._entry, reactor, session)

                        def main_success(_):
                            self.log.debug("main_success")
                            txaio.resolve(done, None)

                        def main_error(err):
                            self.log.debug("main_error", err)
                            txaio.reject(done, err)

                        txaio.add_callbacks(d, main_success, main_error)
Example #28
0
            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
Example #29
0
 def stop(self):
     self._stopping = True
     if self._session and self._session.is_attached():
         return self._session.leave()
     elif self._delay_f:
         # This cancel request will actually call the "error" callback of
         # the _delay_f future. Nothing to worry about.
         return txaio.as_future(txaio.cancel, self._delay_f)
     # if (for some reason -- should we log warning here to figure
     # out if this can evern happen?) we've not fired _done_f, we
     # do that now (causing our "main" to exit, and thus react() to
     # quit)
     if not txaio.is_called(self._done_f):
         txaio.resolve(self._done_f, None)
     return txaio.create_future_success(None)
Example #30
0
                    def on_join(session, details):
                        self.log.debug("session on_join: {details}", details=details)
                        self.log.info(
                            'Successfully connected to transport #{transport_idx}: url={url}',
                            transport_idx=transport.idx,
                            url=transport.config['url'],
                        )
                        d = txaio.as_future(self._entry, reactor, session)

                        def setup_success(_):
                            self.log.debug("setup_success")

                        def setup_error(err):
                            self.log.debug("setup_error: {err}", err=err)
                            txaio.reject(done, err)

                        txaio.add_callbacks(d, setup_success, setup_error)
Example #31
0
                    def on_join(session, details):
                        self.log.debug("session on_join: {details}",
                                       details=details)
                        self.log.info(
                            'Successfully connected to transport #{transport_idx}: url={url}',
                            transport_idx=transport.idx,
                            url=transport.config['url'],
                        )
                        d = txaio.as_future(self._entry, reactor, session)

                        def setup_success(_):
                            self.log.debug("setup_success")

                        def setup_error(err):
                            self.log.debug("setup_error: {err}", err=err)
                            txaio.reject(done, err)

                        txaio.add_callbacks(d, setup_success, setup_error)
Example #32
0
    def authorize(self, session, uri, action):
        """
        Authorizes a session for an action on an URI.

        Implements :func:`autobahn.wamp.interfaces.IRouter.authorize`
        """
        assert(type(uri) == six.text_type)
        assert(action in [u'call', u'register', u'publish', u'subscribe'])

        # the role under which the session that wishes to perform the given action on
        # the given URI was authenticated under
        role = session._authrole

        if role in self._roles:
            # the authorizer procedure of the role which we will call ..
            authorize = self._roles[role].authorize
            d = txaio.as_future(authorize, session, uri, action)
        else:
            # normally, the role should exist on the router (and hence we should not arrive
            # here), but the role might have been dynamically removed - and anyway, safety first!
            d = txaio.create_future_success(False)

        def got_authorization(authorization):
            # backward compatibility
            if type(authorization) == bool:
                authorization = {
                    u'allow': authorization,
                    u'cache': False
                }
                if action in [u'call', u'publish']:
                    authorization[u'disclose'] = False

            self.log.debug("Authorized action '{action}' for URI '{uri}' by session {session_id} with authid '{authid}' and authrole '{authrole}' -> authorization: {authorization}",
                           session_id=session._session_id,
                           uri=uri,
                           action=action,
                           authid=session._authid,
                           authrole=session._authrole,
                           authorization=authorization)

            return authorization

        d.addCallback(got_authorization)
        return d
Example #33
0
    def authorize(self, session, uri, action):
        """
        Authorizes a session for an action on an URI.

        Implements :func:`autobahn.wamp.interfaces.IRouter.authorize`
        """
        assert (type(uri) == six.text_type)
        assert (action in [u'call', u'register', u'publish', u'subscribe'])

        # the role under which the session that wishes to perform the given action on
        # the given URI was authenticated under
        role = session._authrole

        if role in self._roles:
            # the authorizer procedure of the role which we will call ..
            authorize = self._roles[role].authorize
            d = txaio.as_future(authorize, session, uri, action)
        else:
            # normally, the role should exist on the router (and hence we should not arrive
            # here), but the role might have been dynamically removed - and anyway, safety first!
            d = txaio.create_future_success(False)

        def got_authorization(authorization):
            # backward compatibility
            if type(authorization) == bool:
                authorization = {u'allow': authorization, u'cache': False}
                if action in [u'call', u'publish']:
                    authorization[u'disclose'] = False

            self.log.info(
                "Authorized action '{action}' for URI '{uri}' by session {session_id} with authid '{authid}' and authrole '{authrole}' -> authorization: {authorization}",
                session_id=session._session_id,
                uri=uri,
                action=action,
                authid=session._authid,
                authrole=session._authrole,
                authorization=authorization)

            return authorization

        d.addCallback(got_authorization)
        return d
Example #34
0
                    def on_join(session, details):
                        self.log.debug("session on_join: {details}", details=details)
                        transport.connect_sucesses += 1
                        self.log.info(
                            'Successfully connected to transport #{transport_idx}: url={url}',
                            transport_idx=transport.idx,
                            url=transport.config['url'],
                        )
                        d = txaio.as_future(self._entry, reactor, session)

                        def main_success(_):
                            self.log.debug("main_success")
                            session.leave()

                        def main_error(err):
                            self.log.debug("main_error: {err}", err=err)
                            txaio.reject(done, err)
                            # I guess .leave() here too...?

                        txaio.add_callbacks(d, main_success, main_error)
Example #35
0
                    def on_join(session, details):
                        self.log.debug("session on_join: {details}",
                                       details=details)
                        transport.connect_sucesses += 1
                        self.log.info(
                            'Successfully connected to transport #{transport_idx}: url={url}',
                            transport_idx=transport.idx,
                            url=transport.config['url'],
                        )
                        d = txaio.as_future(self._entry, reactor, session)

                        def main_success(_):
                            self.log.debug("main_success")
                            session.leave()

                        def main_error(err):
                            self.log.debug("main_error: {err}", err=err)
                            txaio.reject(done, err)
                            # I guess .leave() here too...?

                        txaio.add_callbacks(d, main_success, main_error)
Example #36
0
    def fire(self, event, *args, **kwargs):
        """
        Fire a particular event.

        :param event: the event to fire. All other args and kwargs are
            passed on to the handler(s) for the event.

        :return: a Deferred/Future gathering all async results from
            all handlers and/or parent handlers.
        """
        # print("firing '{}' from '{}'".format(event, hash(self)))
        if self._listeners is None:
            return txaio.create_future(result=[])

        self._check_event(event)
        res = []
        for handler in self._listeners.get(event, set()):
            future = txaio.as_future(handler, *args, **kwargs)
            res.append(future)
        if self._parent is not None:
            res.append(self._parent.fire(event, *args, **kwargs))
        return txaio.gather(res, consume_exceptions=False)
Example #37
0
    def fire(self, event, *args, **kwargs):
        """
        Fire a particular event.

        :param event: the event to fire. All other args and kwargs are
            passed on to the handler(s) for the event.

        :return: a Deferred/Future gathering all async results from
            all handlers and/or parent handlers.
        """
        # print("firing '{}' from '{}'".format(event, hash(self)))
        if self._listeners is None:
            return txaio.create_future(result=[])

        self._check_event(event)
        res = []
        for handler in self._listeners.get(event, set()):
            future = txaio.as_future(handler, *args, **kwargs)
            res.append(future)
        if self._parent is not None:
            res.append(self._parent.fire(event, *args, **kwargs))
        return txaio.gather(res, consume_exceptions=False)
Example #38
0
                def on_join(session, details):
                    transport.connect_sucesses += 1
                    self.log.debug("session on_join: {details}", details=details)
                    d = txaio.as_future(self._entry, reactor, session)

                    def main_success(_):
                        self.log.debug("main_success")

                        def leave():
                            try:
                                session.leave()
                            except SessionNotReady:
                                # someone may have already called
                                # leave()
                                pass
                        txaio.call_later(0, leave)

                    def main_error(err):
                        self.log.debug("main_error: {err}", err=err)
                        txaio.reject(done, err)
                        session.disconnect()
                    txaio.add_callbacks(d, main_success, main_error)
Example #39
0
def test_as_future_coroutine(framework):
    '''
    call a coroutine (asyncio)
    '''
    pytest.importorskip('asyncio')
    # can import asyncio on python3.4, but might still be using
    # twisted
    if not txaio.using_asyncio:
        return

    errors = []
    results = []
    calls = []

    from asyncio import coroutine

    @coroutine
    def method(*args, **kw):
        calls.append((args, kw))
        return 42

    f = txaio.as_future(method, 1, 2, 3, key='word')

    def cb(x):
        results.append(x)

    def errback(f):
        errors.append(f)

    txaio.add_callbacks(f, cb, errback)

    run_once()
    run_once()

    assert len(results) == 1
    assert len(errors) == 0
    assert results[0] == 42
    assert calls[0] == ((1, 2, 3), dict(key='word'))
Example #40
0
def test_as_future_coroutine(framework):
    '''
    call a coroutine (asyncio)
    '''
    pytest.importorskip('asyncio')
    # can import asyncio on python3.4, but might still be using
    # twisted
    if not txaio.using_asyncio:
        return

    errors = []
    results = []
    calls = []

    from asyncio import coroutine

    @coroutine
    def method(*args, **kw):
        calls.append((args, kw))
        return 42
    f = txaio.as_future(method, 1, 2, 3, key='word')

    def cb(x):
        results.append(x)

    def errback(f):
        errors.append(f)

    txaio.add_callbacks(f, cb, errback)

    run_once()
    run_once()

    assert len(results) == 1
    assert len(errors) == 0
    assert results[0] == 42
    assert calls[0] == ((1, 2, 3), dict(key='word'))
Example #41
0
    def processCall(self, session, call):
        """
        Implements :func:`crossbar.router.interfaces.IDealer.processCall`
        """
        # check procedure URI: for CALL, must be valid URI (either strict or loose), and
        # all URI components must be non-empty
        if self._option_uri_strict:
            uri_is_valid = _URI_PAT_STRICT_NON_EMPTY.match(call.procedure)
        else:
            uri_is_valid = _URI_PAT_LOOSE_NON_EMPTY.match(call.procedure)

        if not uri_is_valid:
            reply = message.Error(message.Call.MESSAGE_TYPE, call.request, ApplicationError.INVALID_URI, [u"call with invalid procedure URI '{0}' (URI strict checking {1})".format(call.procedure, self._option_uri_strict)])
            self._router.send(session, reply)
            return

        # get registrations active on the procedure called
        #
        registration = self._registration_map.best_matching_observation(call.procedure)

        if registration:

            # validate payload (skip in "payload_transparency" mode)
            #
            if call.payload is None:
                try:
                    self._router.validate('call', call.procedure, call.args, call.kwargs)
                except Exception as e:
                    reply = message.Error(message.Call.MESSAGE_TYPE, call.request, ApplicationError.INVALID_ARGUMENT, [u"call of procedure '{0}' with invalid application payload: {1}".format(call.procedure, e)])
                    self._router.send(session, reply)
                    return

            # authorize CALL action
            #
            d = txaio.as_future(self._router.authorize, session, call.procedure, RouterAction.ACTION_CALL)

            def on_authorize_success(authorized):

                # the call to authorize the action _itself_ succeeded. now go on depending on whether
                # the action was actually authorized or not ..
                #
                if not authorized:
                    reply = message.Error(message.Call.MESSAGE_TYPE, call.request, ApplicationError.NOT_AUTHORIZED, [u"session is not authorized to call procedure '{0}'".format(call.procedure)])
                    self._router.send(session, reply)

                else:

                    # determine callee according to invocation policy
                    #
                    if registration.extra.invoke == message.Register.INVOKE_SINGLE:
                        callee = registration.observers[0]

                    elif registration.extra.invoke == message.Register.INVOKE_FIRST:
                        callee = registration.observers[0]

                    elif registration.extra.invoke == message.Register.INVOKE_LAST:
                        callee = registration.observers[len(registration.observers) - 1]

                    elif registration.extra.invoke == message.Register.INVOKE_ROUNDROBIN:
                        callee = registration.observers[registration.extra.roundrobin_current % len(registration.observers)]
                        registration.extra.roundrobin_current += 1

                    elif registration.extra.invoke == message.Register.INVOKE_RANDOM:
                        callee = registration.observers[random.randint(0, len(registration.observers) - 1)]

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

                    # new ID for the invocation
                    #
                    invocation_request_id = self._request_id_gen.next()

                    # caller disclosure
                    #
                    if call.disclose_me:
                        caller = session._session_id
                    else:
                        caller = None

                    # for pattern-based registrations, the INVOCATION must contain
                    # the actual procedure being called
                    #
                    if registration.match != message.Register.MATCH_EXACT:
                        procedure = call.procedure
                    else:
                        procedure = None

                    if call.payload:
                        invocation = message.Invocation(invocation_request_id,
                                                        registration.id,
                                                        payload=call.payload,
                                                        timeout=call.timeout,
                                                        receive_progress=call.receive_progress,
                                                        caller=caller,
                                                        procedure=procedure,
                                                        enc_algo=call.enc_algo,
                                                        enc_key=call.enc_key,
                                                        enc_serializer=call.enc_serializer)
                    else:
                        invocation = message.Invocation(invocation_request_id,
                                                        registration.id,
                                                        args=call.args,
                                                        kwargs=call.kwargs,
                                                        timeout=call.timeout,
                                                        receive_progress=call.receive_progress,
                                                        caller=caller,
                                                        procedure=procedure)

                    self._invocations[invocation_request_id] = InvocationRequest(invocation_request_id, session, call)
                    self._router.send(callee, invocation)

            def on_authorize_error(err):
                """
                the call to authorize the action _itself_ failed (note this is
                different from the call to authorize succeed, but the
                authorization being denied)
                """
                self.log.failure(err)
                reply = message.Error(
                    message.Call.MESSAGE_TYPE,
                    call.request,
                    ApplicationError.AUTHORIZATION_FAILED,
                    [u"failed to authorize session for calling procedure '{0}': {1}".format(call.procedure, err.value)]
                )
                self._router.send(session, reply)

            txaio.add_callbacks(d, on_authorize_success, on_authorize_error)

        else:
            reply = message.Error(message.Call.MESSAGE_TYPE, call.request, ApplicationError.NO_SUCH_PROCEDURE, [u"no callee registered for procedure <{0}>".format(call.procedure)])
            self._router.send(session, reply)
Example #42
0
    def onMessage(self, msg):
        """
        Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage`
        """
        if self._session_id is None:

            if not self._pending_session_id:
                self._pending_session_id = util.id()

            def welcome(realm,
                        authid=None,
                        authrole=None,
                        authmethod=None,
                        authprovider=None,
                        authextra=None,
                        custom=None):
                self._realm = realm
                self._session_id = self._pending_session_id
                self._pending_session_id = None
                self._goodbye_sent = False

                self._router = self._router_factory.get(realm)
                if not self._router:
                    # should not arrive here
                    raise Exception(
                        "logic error (no realm at a stage were we should have one)"
                    )

                self._authid = authid
                self._authrole = authrole
                self._authmethod = authmethod
                self._authprovider = authprovider

                roles = self._router.attach(self)

                msg = message.Welcome(self._session_id,
                                      roles,
                                      realm=realm,
                                      authid=authid,
                                      authrole=authrole,
                                      authmethod=authmethod,
                                      authprovider=authprovider,
                                      authextra=authextra,
                                      custom=custom)
                self._transport.send(msg)

                self.onJoin(
                    SessionDetails(self._realm, self._session_id, self._authid,
                                   self._authrole, self._authmethod,
                                   self._authprovider, self._authextra))

            # the first message MUST be HELLO
            if isinstance(msg, message.Hello):

                self._session_roles = msg.roles

                details = types.HelloDetails(
                    realm=msg.realm,
                    authmethods=msg.authmethods,
                    authid=msg.authid,
                    authrole=msg.authrole,
                    authextra=msg.authextra,
                    session_roles=msg.roles,
                    pending_session=self._pending_session_id)

                d = txaio.as_future(self.onHello, msg.realm, details)

                def success(res):
                    msg = None
                    if isinstance(res, types.Accept):
                        custom = {
                            # FIXME:
                            # u'x_cb_node_id': self._router_factory._node_id
                            u'x_cb_node_id': None
                        }
                        welcome(res.realm, res.authid, res.authrole,
                                res.authmethod, res.authprovider,
                                res.authextra, custom)

                    elif isinstance(res, types.Challenge):
                        msg = message.Challenge(res.method, res.extra)

                    elif isinstance(res, types.Deny):
                        msg = message.Abort(res.reason, res.message)

                    else:
                        pass

                    if msg:
                        self._transport.send(msg)

                txaio.add_callbacks(d, success, self._swallow_error_and_abort)

            elif isinstance(msg, message.Authenticate):

                d = txaio.as_future(self.onAuthenticate, msg.signature, {})

                def success(res):
                    msg = None
                    if isinstance(res, types.Accept):
                        custom = {
                            # FIXME:
                            # u'x_cb_node_id': self._router_factory._node_id
                            u'x_cb_node_id': None
                        }
                        welcome(res.realm, res.authid, res.authrole,
                                res.authmethod, res.authprovider,
                                res.authextra, custom)

                    elif isinstance(res, types.Deny):
                        msg = message.Abort(res.reason, res.message)

                    else:
                        pass

                    if msg:
                        self._transport.send(msg)

                txaio.add_callbacks(d, success, self._swallow_error_and_abort)

            elif isinstance(msg, message.Abort):

                # fire callback and close the transport
                self.onLeave(types.CloseDetails(msg.reason, msg.message))

                self._session_id = None
                self._pending_session_id = None

                # self._transport.close()

            else:
                # raise ProtocolError(u"PReceived {0} message while session is not joined".format(msg.__class__))
                # self.log.warn('Protocol state error - received {message} while session is not joined')
                # swallow all noise like still getting PUBLISH messages from log event forwarding - maybe FIXME
                pass

        else:

            if isinstance(msg, message.Hello):
                raise ProtocolError(
                    u"HELLO message received, while session is already established"
                )

            elif isinstance(msg, message.Goodbye):
                if not self._goodbye_sent:
                    # The peer wants to close: answer with GOODBYE reply.
                    # Note: We MUST NOT send any WAMP message _after_ GOODBYE
                    reply = message.Goodbye()
                    self._transport.send(reply)
                    self._goodbye_sent = True
                else:
                    # This is the peer's GOODBYE reply to our own earlier GOODBYE
                    pass

                # We need to first detach the session from the router before
                # erasing the session ID below ..
                try:
                    self._router.detach(self)
                except Exception:
                    self.log.failure("Internal error")

                # In order to send wamp.session.on_leave properly
                # (i.e. *with* the proper session_id) we save it
                previous_session_id = self._session_id

                # At this point, we've either sent GOODBYE already earlier,
                # or we have just responded with GOODBYE. In any case, we MUST NOT
                # send any WAMP message from now on:
                # clear out session ID, so that anything that might be triggered
                # in the onLeave below is prohibited from sending WAMP stuff.
                # E.g. the client might have been subscribed to meta events like
                # wamp.session.on_leave - and we must not send that client's own
                # leave to itself!
                self._session_id = None
                self._pending_session_id = None

                # publish event, *after* self._session_id is None so
                # that we don't publish to ourselves as well (if this
                # session happens to be subscribed to wamp.session.on_leave)
                if self._service_session:
                    self._service_session.publish(
                        u'wamp.session.on_leave',
                        previous_session_id,
                    )

                # fire callback and close the transport
                self.onLeave(types.CloseDetails(msg.reason, msg.message))

                # don't close the transport, as WAMP allows to reattach a session
                # to the same or a different realm without closing the transport
                # self._transport.close()

            else:
                self._router.process(self, msg)
Example #43
0
    def processRegister(self, session, register):
        """
        Implements :func:`crossbar.router.interfaces.IDealer.processRegister`
        """
        # check topic URI: for SUBSCRIBE, must be valid URI (either strict or loose), and all
        # URI components must be non-empty other than for wildcard subscriptions
        #
        if self._option_uri_strict:
            if register.match == u"wildcard":
                uri_is_valid = _URI_PAT_STRICT_EMPTY.match(register.procedure)
            else:
                uri_is_valid = _URI_PAT_STRICT_NON_EMPTY.match(register.procedure)
        else:
            if register.match == u"wildcard":
                uri_is_valid = _URI_PAT_LOOSE_EMPTY.match(register.procedure)
            else:
                uri_is_valid = _URI_PAT_LOOSE_NON_EMPTY.match(register.procedure)

        if not uri_is_valid:
            reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.INVALID_URI, [u"register for invalid procedure URI '{0}' (URI strict checking {1})".format(register.procedure, self._option_uri_strict)])
            self._router.send(session, reply)
            return

        # disallow registration of procedures starting with "wamp." and  "crossbar." other than for
        # trusted sessions (that are sessions built into Crossbar.io)
        #
        if session._authrole is not None and session._authrole != u"trusted":
            is_restricted = register.procedure.startswith(u"wamp.") or register.procedure.startswith(u"crossbar.")
            if is_restricted:
                reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.INVALID_URI, [u"register for restricted procedure URI '{0}')".format(register.procedure)])
                self._router.send(session, reply)
                return

        # get existing registration for procedure / matching strategy - if any
        #
        registration = self._registration_map.get_observation(register.procedure, register.match)
        if registration:

            # there is an existing registration, and that has an invocation strategy that only allows a single callee
            # on a the given registration
            #
            if registration.extra.invoke == message.Register.INVOKE_SINGLE:
                reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.PROCEDURE_ALREADY_EXISTS, [u"register for already registered procedure '{0}'".format(register.procedure)])
                self._router.send(session, reply)
                return

            # there is an existing registration, and that has an invokation strategy different from the one
            # requested by the new callee
            #
            if registration.extra.invoke != register.invoke:
                reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.PROCEDURE_EXISTS_INVOCATION_POLICY_CONFLICT, [u"register for already registered procedure '{0}' with conflicting invocation policy (has {1} and {2} was requested)".format(register.procedure, registration.extra.invoke, register.invoke)])
                self._router.send(session, reply)
                return

        # authorize action
        #
        d = txaio.as_future(self._router.authorize, session, register.procedure, RouterAction.ACTION_REGISTER)

        def on_authorize_success(authorized):
            if not authorized:
                # error reply since session is not authorized to register
                #
                reply = message.Error(message.Register.MESSAGE_TYPE, register.request, ApplicationError.NOT_AUTHORIZED, [u"session is not authorized to register procedure '{0}'".format(register.procedure)])

            else:
                # ok, session authorized to register. now get the registration
                #
                registration_extra = RegistrationExtra(register.invoke)
                registration, was_already_registered, is_first_callee = self._registration_map.add_observer(session, register.procedure, register.match, registration_extra)

                if not was_already_registered:
                    self._session_to_registrations[session].add(registration)

                # publish WAMP meta events
                #
                if self._router._realm:
                    service_session = self._router._realm.session
                    if service_session and not registration.uri.startswith(u'wamp.'):
                        if is_first_callee:
                            registration_details = {
                                'id': registration.id,
                                'created': registration.created,
                                'uri': registration.uri,
                                'match': registration.match,
                                'invoke': registration.extra.invoke,
                            }
                            service_session.publish(u'wamp.registration.on_create', session._session_id, registration_details)
                        if not was_already_registered:
                            service_session.publish(u'wamp.registration.on_register', session._session_id, registration.id)

                # acknowledge register with registration ID
                #
                reply = message.Registered(register.request, registration.id)

            # send out reply to register requestor
            #
            self._router.send(session, reply)

        def on_authorize_error(err):
            """
            the call to authorize the action _itself_ failed (note this is
            different from the call to authorize succeed, but the
            authorization being denied)
            """
            self.log.failure(err)
            reply = message.Error(
                message.Register.MESSAGE_TYPE,
                register.request,
                ApplicationError.AUTHORIZATION_FAILED,
                [u"failed to authorize session for registering procedure '{0}': {1}".format(register.procedure, err.value)]
            )
            self._router.send(session, reply)

        txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
Example #44
0
    def processSubscribe(self, session, subscribe):
        """
        Implements :func:`crossbar.router.interfaces.IBroker.processSubscribe`
        """
        # check topic URI: for SUBSCRIBE, must be valid URI (either strict or loose), and all
        # URI components must be non-empty other than for wildcard subscriptions
        #
        if self._option_uri_strict:
            if subscribe.match == u"wildcard":
                uri_is_valid = _URI_PAT_STRICT_EMPTY.match(subscribe.topic)
            else:
                uri_is_valid = _URI_PAT_STRICT_NON_EMPTY.match(subscribe.topic)
        else:
            if subscribe.match == u"wildcard":
                uri_is_valid = _URI_PAT_LOOSE_EMPTY.match(subscribe.topic)
            else:
                uri_is_valid = _URI_PAT_LOOSE_NON_EMPTY.match(subscribe.topic)

        if not uri_is_valid:
            reply = message.Error(message.Subscribe.MESSAGE_TYPE, subscribe.request, ApplicationError.INVALID_URI, [u"subscribe for invalid topic URI '{0}'".format(subscribe.topic)])
            session._transport.send(reply)
            return

        # authorize action
        #
        d = txaio.as_future(self._router.authorize, session, subscribe.topic, RouterAction.ACTION_SUBSCRIBE)

        def on_authorize_success(authorized):
            if not authorized:
                # error reply since session is not authorized to subscribe
                #
                reply = message.Error(message.Subscribe.MESSAGE_TYPE, subscribe.request, ApplicationError.NOT_AUTHORIZED, [u"session is not authorized to subscribe to topic '{0}'".format(subscribe.topic)])

            else:
                # ok, session authorized to subscribe. now get the subscription
                #
                subscription, was_already_subscribed, is_first_subscriber = self._subscription_map.add_observer(session, subscribe.topic, subscribe.match)

                if not was_already_subscribed:
                    self._session_to_subscriptions[session].add(subscription)

                # publish WAMP meta events
                #
                if self._router._realm:
                    service_session = self._router._realm.session
                    if service_session and not subscription.uri.startswith(u'wamp.'):
                        if is_first_subscriber:
                            subscription_details = {
                                'id': subscription.id,
                                'created': subscription.created,
                                'uri': subscription.uri,
                                'match': subscription.match,
                            }
                            service_session.publish(u'wamp.subscription.on_create', session._session_id, subscription_details)
                        if not was_already_subscribed:
                            service_session.publish(u'wamp.subscription.on_subscribe', session._session_id, subscription.id)

                # acknowledge subscribe with subscription ID
                #
                reply = message.Subscribed(subscribe.request, subscription.id)

            # send out reply to subscribe requestor
            #
            session._transport.send(reply)

        def on_authorize_error(err):
            """
            the call to authorize the action _itself_ failed (note this is
            different from the call to authorize succeed, but the
            authorization being denied)
            """
            # XXX same as another code-block, can we collapse?
            self.log.failure(err)
            reply = message.Error(
                message.Subscribe.MESSAGE_TYPE,
                subscribe.request,
                ApplicationError.AUTHORIZATION_FAILED,
                [u"failed to authorize session for subscribing to topic URI '{0}': {1}".format(subscribe.topic, err.value)]
            )
            session._transport.send(reply)

        txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
Example #45
0
    def send(self, msg):
        """
        Implements :func:`autobahn.wamp.interfaces.ITransport.send`
        """
        if isinstance(msg, message.Hello):
            self._router = self._routerFactory.get(msg.realm)

            # fake session ID assignment (normally done in WAMP opening handshake)
            self._session._session_id = util.id()

            # set fixed/trusted authentication information
            self._session._authid = self._trusted_authid
            self._session._authrole = self._trusted_authrole
            self._session._authmethod = None
            # FIXME: the following does blow up
            # self._session._authmethod = u'trusted'
            self._session._authprovider = None
            self._session._authextra = None

            # add app session to router
            self._router.attach(self._session)

            # fake app session open
            details = SessionDetails(
                self._session._realm, self._session._session_id,
                self._session._authid, self._session._authrole,
                self._session._authmethod, self._session._authprovider,
                self._session._authextra)

            # have to fire the 'join' notification ourselves, as we're
            # faking out what the protocol usually does.
            d = self._session.fire('join', self._session, details)
            d.addErrback(
                lambda fail: self._log_error(fail, "While notifying 'join'"))
            # now fire onJoin (since _log_error returns None, we'll be
            # back in the callback chain even on errors from 'join'
            d.addCallback(
                lambda _: txaio.as_future(self._session.onJoin, details))
            d.addErrback(
                lambda fail: self._swallow_error(fail, "While firing onJoin"))
            d.addCallback(lambda _: self._session.fire('ready', self._session))
            d.addErrback(
                lambda fail: self._log_error(fail, "While notifying 'ready'"))

        # app-to-router
        #
        elif isinstance(msg, (message.Publish,
                              message.Subscribe,
                              message.Unsubscribe,
                              message.Call,
                              message.Yield,
                              message.Register,
                              message.Unregister,
                              message.Cancel)) or \
            (isinstance(msg, message.Error) and
             msg.request_type == message.Invocation.MESSAGE_TYPE):

            # deliver message to router
            #
            self._router.process(self._session, msg)

        # router-to-app
        #
        elif isinstance(msg, (message.Event,
                              message.Invocation,
                              message.Result,
                              message.Published,
                              message.Subscribed,
                              message.Unsubscribed,
                              message.Registered,
                              message.Unregistered)) or \
            (isinstance(msg, message.Error) and (msg.request_type in {
                message.Call.MESSAGE_TYPE,
                message.Cancel.MESSAGE_TYPE,
                message.Register.MESSAGE_TYPE,
                message.Unregister.MESSAGE_TYPE,
                message.Publish.MESSAGE_TYPE,
                message.Subscribe.MESSAGE_TYPE,
                message.Unsubscribe.MESSAGE_TYPE})):

            # deliver message to app session
            #
            self._session.onMessage(msg)

        # ignore messages
        #
        elif isinstance(msg, message.Goodbye):
            details = types.CloseDetails(msg.reason, msg.message)
            session = self._session

            @inlineCallbacks
            def do_goodbye():
                try:
                    yield session.onLeave(details)
                except Exception:
                    self._log_error(Failure(), "While firing onLeave")

                if session._transport:
                    session._transport.close()

                try:
                    yield session.fire('leave', session, details)
                except Exception:
                    self._log_error(Failure(), "While notifying 'leave'")

                try:
                    yield session.fire('disconnect', session)
                except Exception:
                    self._log_error(Failure(), "While notifying 'disconnect'")

                if self._router._realm.session:
                    yield self._router._realm.session.publish(
                        u'wamp.session.on_leave',
                        session._session_id,
                    )

            d = do_goodbye()
            d.addErrback(lambda fail: self._log_error(fail, "Internal error"))

        else:
            # should not arrive here
            #
            raise Exception(
                "RouterApplicationSession.send: unhandled message {0}".format(
                    msg))
Example #46
0
    def onMessage(self, msg):
        """
        Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage`
        """
        if self._session_id is None:

            if not self._pending_session_id:
                self._pending_session_id = util.id()

            def welcome(realm, authid=None, authrole=None, authmethod=None, authprovider=None):
                self._session_id = self._pending_session_id
                self._pending_session_id = None
                self._goodbye_sent = False

                self._router = self._router_factory.get(realm)
                if not self._router:
                    raise Exception("no such realm")

                self._authid = authid
                self._authrole = authrole
                self._authmethod = authmethod
                self._authprovider = authprovider

                roles = self._router.attach(self)

                msg = message.Welcome(self._session_id, roles, authid=authid, authrole=authrole, authmethod=authmethod, authprovider=authprovider)
                self._transport.send(msg)

                self.onJoin(SessionDetails(self._realm, self._session_id, self._authid, self._authrole, self._authmethod, self._authprovider))

            # the first message MUST be HELLO
            if isinstance(msg, message.Hello):

                self._realm = msg.realm
                self._session_roles = msg.roles

                details = types.HelloDetails(msg.roles, msg.authmethods, msg.authid, self._pending_session_id)

                d = txaio.as_future(self.onHello, self._realm, details)

                def success(res):
                    msg = None

                    if isinstance(res, types.Accept):
                        welcome(self._realm, res.authid, res.authrole, res.authmethod, res.authprovider)

                    elif isinstance(res, types.Challenge):
                        msg = message.Challenge(res.method, res.extra)

                    elif isinstance(res, types.Deny):
                        msg = message.Abort(res.reason, res.message)

                    else:
                        pass

                    if msg:
                        self._transport.send(msg)

                txaio.add_callbacks(d, success, self._swallow_error_and_abort)

            elif isinstance(msg, message.Authenticate):

                d = txaio.as_future(self.onAuthenticate, msg.signature, {})

                def success(res):
                    msg = None

                    if isinstance(res, types.Accept):
                        welcome(self._realm, res.authid, res.authrole, res.authmethod, res.authprovider)

                    elif isinstance(res, types.Deny):
                        msg = message.Abort(res.reason, res.message)

                    else:
                        pass

                    if msg:
                        self._transport.send(msg)

                txaio.add_callbacks(d, success, self._swallow_error_and_abort)

            elif isinstance(msg, message.Abort):

                # fire callback and close the transport
                self.onLeave(types.CloseDetails(msg.reason, msg.message))

                self._session_id = None
                self._pending_session_id = None

                # self._transport.close()

            else:
                raise ProtocolError("Received {0} message, and session is not yet established".format(msg.__class__))

        else:

            if isinstance(msg, message.Hello):
                raise ProtocolError(u"HELLO message received, while session is already established")

            elif isinstance(msg, message.Goodbye):
                if not self._goodbye_sent:
                    # the peer wants to close: send GOODBYE reply
                    reply = message.Goodbye()
                    self._transport.send(reply)

                # fire callback and close the transport
                self.onLeave(types.CloseDetails(msg.reason, msg.message))

                self._router.detach(self)

                self._session_id = None
                self._pending_session_id = None

                # self._transport.close()

            else:

                self._router.process(self, msg)
Example #47
0
    def processPublish(self, session, publish):
        """
        Implements :func:`crossbar.router.interfaces.IBroker.processPublish`
        """
        # check topic URI: for PUBLISH, must be valid URI (either strict or loose), and
        # all URI components must be non-empty
        if self._option_uri_strict:
            uri_is_valid = _URI_PAT_STRICT_NON_EMPTY.match(publish.topic)
        else:
            uri_is_valid = _URI_PAT_LOOSE_NON_EMPTY.match(publish.topic)

        if not uri_is_valid:
            if publish.acknowledge:
                reply = message.Error(message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.INVALID_URI, [u"publish with invalid topic URI '{0}' (URI strict checking {1})".format(publish.topic, self._option_uri_strict)])
                session._transport.send(reply)
            return

        # disallow publication to topics starting with "wamp." and
        # "crossbar." other than for trusted session (that are sessions
        # built into Crossbar.io)
        if session._authrole is not None and session._authrole != u"trusted":
            if publish.topic.startswith(u"wamp.") or publish.topic.startswith(u"crossbar."):
                if publish.acknowledge:
                    reply = message.Error(message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.INVALID_URI, [u"publish with restricted topic URI '{0}'".format(publish.topic)])
                    session._transport.send(reply)
                return

        # get subscriptions active on the topic published to
        #
        subscriptions = self._subscription_map.match_observations(publish.topic)

        # go on if there are any active subscriptions or the publish is to be acknowledged
        # otherwise there isn't anything to do anyway.
        #
        if subscriptions or publish.acknowledge:

            # validate payload
            #
            try:
                self._router.validate('event', publish.topic, publish.args, publish.kwargs)
            except Exception as e:
                if publish.acknowledge:
                    reply = message.Error(message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.INVALID_ARGUMENT, [u"publish to topic URI '{0}' with invalid application payload: {1}".format(publish.topic, e)])
                    session._transport.send(reply)
                return

            # authorize PUBLISH action
            #
            d = txaio.as_future(self._router.authorize, session, publish.topic, RouterAction.ACTION_PUBLISH)

            def on_authorize_success(authorized):

                # the call to authorize the action _itself_ succeeded. now go on depending on whether
                # the action was actually authorized or not ..
                #
                if not authorized:

                    if publish.acknowledge:
                        reply = message.Error(message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.NOT_AUTHORIZED, [u"session not authorized to publish to topic '{0}'".format(publish.topic)])
                        session._transport.send(reply)

                else:

                    # new ID for the publication
                    #
                    publication = util.id()

                    # send publish acknowledge immediately when requested
                    #
                    if publish.acknowledge:
                        msg = message.Published(publish.request, publication)
                        session._transport.send(msg)

                    # publisher disclosure
                    #
                    if publish.disclose_me:
                        publisher = session._session_id
                    else:
                        publisher = None

                    # skip publisher
                    #
                    if publish.exclude_me is None or publish.exclude_me:
                        me_also = False
                    else:
                        me_also = True

                    # iterate over all subscriptions ..
                    #
                    for subscription in subscriptions:

                        # initial list of receivers are all subscribers on a subscription ..
                        #
                        receivers = subscription.observers

                        # filter by "eligible" receivers
                        #
                        if publish.eligible:

                            # map eligible session IDs to eligible sessions
                            eligible = []
                            for session_id in publish.eligible:
                                if session_id in self._router._session_id_to_session:
                                    eligible.append(self._router._session_id_to_session[session_id])

                            # filter receivers for eligible sessions
                            receivers = set(eligible) & receivers

                        # remove "excluded" receivers
                        #
                        if publish.exclude:

                            # map excluded session IDs to excluded sessions
                            exclude = []
                            for s in publish.exclude:
                                if s in self._router._session_id_to_session:
                                    exclude.append(self._router._session_id_to_session[s])

                            # filter receivers for excluded sessions
                            if exclude:
                                receivers = receivers - set(exclude)

                        # if receivers is non-empty, dispatch event ..
                        #
                        if receivers:

                            # for pattern-based subscriptions, the EVENT must contain
                            # the actual topic being published to
                            #
                            if subscription.match != message.Subscribe.MATCH_EXACT:
                                topic = publish.topic
                            else:
                                topic = None

                            msg = message.Event(subscription.id,
                                                publication,
                                                args=publish.args,
                                                kwargs=publish.kwargs,
                                                publisher=publisher,
                                                topic=topic)
                            for receiver in receivers:
                                if me_also or receiver != session:
                                    # the receiving subscriber session might have been lost in the meantime ..
                                    if receiver._transport:
                                        receiver._transport.send(msg)

            def on_authorize_error(err):
                """
                the call to authorize the action _itself_ failed (note this is
                different from the call to authorize succeed, but the
                authorization being denied)
                """
                self.log.failure(err)
                if publish.acknowledge:
                    reply = message.Error(
                        message.Publish.MESSAGE_TYPE,
                        publish.request,
                        ApplicationError.AUTHORIZATION_FAILED,
                        [u"failed to authorize session for publishing to topic URI '{0}': {1}".format(publish.topic, err.value)]
                    )
                    session._transport.send(reply)

            txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
Example #48
0
    def send(self, msg):
        """
        Implements :func:`autobahn.wamp.interfaces.ITransport.send`
        """
        if isinstance(msg, message.Hello):

            self._router = self._routerFactory.get(msg.realm)

            # fake session ID assignment (normally done in WAMP opening handshake)
            self._session._session_id = util.id()

            # set fixed/trusted authentication information
            self._session._authid = self._trusted_authid
            self._session._authrole = self._trusted_authrole
            self._session._authmethod = None
            # FIXME: the following does blow up
            # self._session._authmethod = u'trusted'
            self._session._authprovider = None

            # add app session to router
            self._router.attach(self._session)

            # fake app session open
            #
            details = SessionDetails(self._session._realm, self._session._session_id,
                                     self._session._authid, self._session._authrole, self._session._authmethod,
                                     self._session._authprovider)

            d = txaio.as_future(self._session.onJoin, details)

            def _log_error_and_close(fail):
                self.log.failure("Internal error: {log_failure.value}", failure=fail)
                self.close()
            txaio.add_callbacks(d, None, _log_error_and_close)

        # app-to-router
        #
        elif isinstance(msg, message.Publish) or \
            isinstance(msg, message.Subscribe) or \
            isinstance(msg, message.Unsubscribe) or \
            isinstance(msg, message.Call) or \
            isinstance(msg, message.Yield) or \
            isinstance(msg, message.Register) or \
            isinstance(msg, message.Unregister) or \
            isinstance(msg, message.Cancel) or \
            (isinstance(msg, message.Error) and
             msg.request_type == message.Invocation.MESSAGE_TYPE):

            # deliver message to router
            #
            self._router.process(self._session, msg)

        # router-to-app
        #
        elif isinstance(msg, message.Event) or \
            isinstance(msg, message.Invocation) or \
            isinstance(msg, message.Result) or \
            isinstance(msg, message.Published) or \
            isinstance(msg, message.Subscribed) or \
            isinstance(msg, message.Unsubscribed) or \
            isinstance(msg, message.Registered) or \
            isinstance(msg, message.Unregistered) or \
            (isinstance(msg, message.Error) and (
                msg.request_type == message.Call.MESSAGE_TYPE or
                msg.request_type == message.Cancel.MESSAGE_TYPE or
                msg.request_type == message.Register.MESSAGE_TYPE or
                msg.request_type == message.Unregister.MESSAGE_TYPE or
                msg.request_type == message.Publish.MESSAGE_TYPE or
                msg.request_type == message.Subscribe.MESSAGE_TYPE or
                msg.request_type == message.Unsubscribe.MESSAGE_TYPE)):

            # deliver message to app session
            #
            self._session.onMessage(msg)

        else:
            # should not arrive here
            #
            raise Exception("RouterApplicationSession.send: unhandled message {0}".format(msg))
Example #49
0
    def _connect_once(self, reactor, transport):

        self.log.info(
            'connecting once using transport type "{transport_type}" '
            'over endpoint "{endpoint_desc}"',
            transport_type=transport.type,
            endpoint_desc=transport.describe_endpoint(),
        )

        done = txaio.create_future()

        # factory for ISession objects
        def create_session():
            cfg = ComponentConfig(self._realm, self._extra)
            try:
                self._session = session = self.session_factory(cfg)
                for auth_name, auth_config in self._authentication.items():
                    if isinstance(auth_config, IAuthenticator):
                        session.add_authenticator(auth_config)
                    else:
                        authenticator = create_authenticator(auth_name, **auth_config)
                        session.add_authenticator(authenticator)

            except Exception as e:
                # couldn't instantiate session calls, which is fatal.
                # let the reconnection logic deal with that
                f = txaio.create_failure(e)
                txaio.reject(done, f)
                raise
            else:
                # hook up the listener to the parent so we can bubble
                # up events happning on the session onto the
                # connection. This lets you do component.on('join',
                # cb) which will work just as if you called
                # session.on('join', cb) for every session created.
                session._parent = self

                # listen on leave events; if we get errors
                # (e.g. no_such_realm), an on_leave can happen without
                # an on_join before
                def on_leave(session, details):
                    self.log.info(
                        "session leaving '{details.reason}'",
                        details=details,
                    )
                    if not txaio.is_called(done):
                        if details.reason in [u"wamp.close.normal"]:
                            txaio.resolve(done, None)
                        else:
                            f = txaio.create_failure(
                                ApplicationError(details.reason)
                            )
                            txaio.reject(done, f)
                session.on('leave', on_leave)

                # if we were given a "main" procedure, we run through
                # it completely (i.e. until its Deferred fires) and
                # then disconnect this session
                def on_join(session, details):
                    transport.connect_sucesses += 1
                    self.log.debug("session on_join: {details}", details=details)
                    d = txaio.as_future(self._entry, reactor, session)

                    def main_success(_):
                        self.log.debug("main_success")

                        def leave():
                            try:
                                session.leave()
                            except SessionNotReady:
                                # someone may have already called
                                # leave()
                                pass
                        txaio.call_later(0, leave)

                    def main_error(err):
                        self.log.debug("main_error: {err}", err=err)
                        txaio.reject(done, err)
                        session.disconnect()
                    txaio.add_callbacks(d, main_success, main_error)
                if self._entry is not None:
                    session.on('join', on_join)

                # listen on disconnect events. Note that in case we
                # had a "main" procedure, we could have already
                # resolve()'d our "done" future
                def on_disconnect(session, was_clean):
                    self.log.debug(
                        "session on_disconnect: was_clean={was_clean}",
                        was_clean=was_clean,
                    )
                    if not txaio.is_called(done):
                        if not was_clean:
                            self.log.warn(
                                u"Session disconnected uncleanly"
                            )
                        else:
                            # eg the session has left the realm, and the transport was properly
                            # shut down. successfully finish the connection
                            txaio.resolve(done, None)
                session.on('disconnect', on_disconnect)

                # return the fresh session object
                return session

        transport.connect_attempts += 1

        d = txaio.as_future(
            self._connect_transport,
            reactor, transport, create_session, done,
        )

        def on_error(err):
            """
            this may seem redundant after looking at _connect_transport, but
            it will handle a case where something goes wrong in
            _connect_transport itself -- as the only connect our
            caller has is the 'done' future
            """
            transport.connect_failures += 1
            # something bad has happened, and maybe didn't get caught
            # upstream yet
            if not txaio.is_called(done):
                txaio.reject(done, err)
        txaio.add_callbacks(d, None, on_error)

        return done
    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):
                    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
                    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)

                        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, 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 as e:
                                try:
                                    self.onUserError(e, "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:
                            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 = 'Failure while invoking procedure {0} registered under "{1}".'.format(endpoint.fn, registration.procedure)
                            try:
                                self.onUserError(err, errmsg)
                            except:
                                pass
                            formatted_tb = None
                            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)
                                formatted_tb = tb.getvalue().splitlines()

                            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, 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 as e:
                        # XXX can .cancel() return a Deferred/Future?
                        try:
                            self.onUserError(e, "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__))
Example #51
0
    def send(self, msg):
        """
        Implements :func:`autobahn.wamp.interfaces.ITransport.send`
        """
        if isinstance(msg, message.Hello):
            self._router = self._routerFactory.get(msg.realm)

            # fake session ID assignment (normally done in WAMP opening handshake)
            self._session._session_id = util.id()

            # set fixed/trusted authentication information
            self._session._authid = self._trusted_authid
            self._session._authrole = self._trusted_authrole
            self._session._authmethod = None
            # FIXME: the following does blow up
            # self._session._authmethod = u'trusted'
            self._session._authprovider = None
            self._session._authextra = None

            # add app session to router
            self._router.attach(self._session)

            # fake app session open
            details = SessionDetails(self._session._realm,
                                     self._session._session_id,
                                     self._session._authid,
                                     self._session._authrole,
                                     self._session._authmethod,
                                     self._session._authprovider,
                                     self._session._authextra)

            # have to fire the 'join' notification ourselves, as we're
            # faking out what the protocol usually does.
            d = self._session.fire('join', self._session, details)
            d.addErrback(lambda fail: self._log_error(fail, "While notifying 'join'"))
            # now fire onJoin (since _log_error returns None, we'll be
            # back in the callback chain even on errors from 'join'
            d.addCallback(lambda _: txaio.as_future(self._session.onJoin, details))
            d.addErrback(lambda fail: self._swallow_error(fail, "While firing onJoin"))
            d.addCallback(lambda _: self._session.fire('ready', self._session))
            d.addErrback(lambda fail: self._log_error(fail, "While notifying 'ready'"))

        # app-to-router
        #
        elif isinstance(msg, (message.Publish,
                              message.Subscribe,
                              message.Unsubscribe,
                              message.Call,
                              message.Yield,
                              message.Register,
                              message.Unregister,
                              message.Cancel)) or \
            (isinstance(msg, message.Error) and
             msg.request_type == message.Invocation.MESSAGE_TYPE):

            # deliver message to router
            #
            self._router.process(self._session, msg)

        # router-to-app
        #
        elif isinstance(msg, (message.Event,
                              message.Invocation,
                              message.Result,
                              message.Published,
                              message.Subscribed,
                              message.Unsubscribed,
                              message.Registered,
                              message.Unregistered)) or \
            (isinstance(msg, message.Error) and (msg.request_type in {
                message.Call.MESSAGE_TYPE,
                message.Cancel.MESSAGE_TYPE,
                message.Register.MESSAGE_TYPE,
                message.Unregister.MESSAGE_TYPE,
                message.Publish.MESSAGE_TYPE,
                message.Subscribe.MESSAGE_TYPE,
                message.Unsubscribe.MESSAGE_TYPE})):

            # deliver message to app session
            #
            self._session.onMessage(msg)

        # ignore messages
        #
        elif isinstance(msg, message.Goodbye):
            details = types.CloseDetails(msg.reason, msg.message)
            session = self._session

            @inlineCallbacks
            def do_goodbye():
                try:
                    yield session.onLeave(details)
                except Exception:
                    self._log_error(Failure(), "While firing onLeave")

                if session._transport:
                    session._transport.close()

                try:
                    yield session.fire('leave', session, details)
                except Exception:
                    self._log_error(Failure(), "While notifying 'leave'")

                try:
                    yield session.fire('disconnect', session)
                except Exception:
                    self._log_error(Failure(), "While notifying 'disconnect'")

                if self._router._realm.session:
                    yield self._router._realm.session.publish(
                        u'wamp.session.on_leave',
                        session._session_id,
                    )
            d = do_goodbye()
            d.addErrback(lambda fail: self._log_error(fail, "Internal error"))

        else:
            # should not arrive here
            #
            raise Exception("RouterApplicationSession.send: unhandled message {0}".format(msg))
Example #52
0
    def onMessage(self, msg):
        """
        Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage`
        """
        if self._session_id is None:

            if not self._pending_session_id:
                self._pending_session_id = util.id()

            def welcome(realm, authid=None, authrole=None, authmethod=None, authprovider=None, authextra=None, custom=None):
                self._realm = realm
                self._session_id = self._pending_session_id
                self._pending_session_id = None
                self._goodbye_sent = False

                self._router = self._router_factory.get(realm)
                if not self._router:
                    # should not arrive here
                    raise Exception("logic error (no realm at a stage were we should have one)")

                self._authid = authid
                self._authrole = authrole
                self._authmethod = authmethod
                self._authprovider = authprovider

                roles = self._router.attach(self)

                msg = message.Welcome(self._session_id,
                                      roles,
                                      realm=realm,
                                      authid=authid,
                                      authrole=authrole,
                                      authmethod=authmethod,
                                      authprovider=authprovider,
                                      authextra=authextra,
                                      custom=custom)
                self._transport.send(msg)

                self.onJoin(SessionDetails(self._realm, self._session_id, self._authid, self._authrole, self._authmethod, self._authprovider, self._authextra))

            # the first message MUST be HELLO
            if isinstance(msg, message.Hello):

                self._session_roles = msg.roles

                details = types.HelloDetails(realm=msg.realm,
                                             authmethods=msg.authmethods,
                                             authid=msg.authid,
                                             authrole=msg.authrole,
                                             authextra=msg.authextra,
                                             session_roles=msg.roles,
                                             pending_session=self._pending_session_id)

                d = txaio.as_future(self.onHello, msg.realm, details)

                def success(res):
                    msg = None
                    if isinstance(res, types.Accept):
                        custom = {
                            # FIXME:
                            # u'x_cb_node_id': self._router_factory._node_id
                            u'x_cb_node_id': None
                        }
                        welcome(res.realm, res.authid, res.authrole, res.authmethod, res.authprovider, res.authextra, custom)

                    elif isinstance(res, types.Challenge):
                        msg = message.Challenge(res.method, res.extra)

                    elif isinstance(res, types.Deny):
                        msg = message.Abort(res.reason, res.message)

                    else:
                        pass

                    if msg:
                        self._transport.send(msg)

                txaio.add_callbacks(d, success, self._swallow_error_and_abort)

            elif isinstance(msg, message.Authenticate):

                d = txaio.as_future(self.onAuthenticate, msg.signature, {})

                def success(res):
                    msg = None
                    if isinstance(res, types.Accept):
                        custom = {
                            # FIXME:
                            # u'x_cb_node_id': self._router_factory._node_id
                            u'x_cb_node_id': None
                        }
                        welcome(res.realm, res.authid, res.authrole, res.authmethod, res.authprovider, res.authextra, custom)

                    elif isinstance(res, types.Deny):
                        msg = message.Abort(res.reason, res.message)

                    else:
                        pass

                    if msg:
                        self._transport.send(msg)

                txaio.add_callbacks(d, success, self._swallow_error_and_abort)

            elif isinstance(msg, message.Abort):

                # fire callback and close the transport
                self.onLeave(types.CloseDetails(msg.reason, msg.message))

                self._session_id = None
                self._pending_session_id = None

                # self._transport.close()

            else:
                # raise ProtocolError(u"PReceived {0} message while session is not joined".format(msg.__class__))
                # self.log.warn('Protocol state error - received {message} while session is not joined')
                # swallow all noise like still getting PUBLISH messages from log event forwarding - maybe FIXME
                pass

        else:

            if isinstance(msg, message.Hello):
                raise ProtocolError(u"HELLO message received, while session is already established")

            elif isinstance(msg, message.Goodbye):
                if not self._goodbye_sent:
                    # The peer wants to close: answer with GOODBYE reply.
                    # Note: We MUST NOT send any WAMP message _after_ GOODBYE
                    reply = message.Goodbye()
                    self._transport.send(reply)
                    self._goodbye_sent = True
                else:
                    # This is the peer's GOODBYE reply to our own earlier GOODBYE
                    pass

                # We need to first detach the session from the router before
                # erasing the session ID below ..
                try:
                    self._router.detach(self)
                except Exception:
                    self.log.failure("Internal error")

                # In order to send wamp.session.on_leave properly
                # (i.e. *with* the proper session_id) we save it
                previous_session_id = self._session_id

                # At this point, we've either sent GOODBYE already earlier,
                # or we have just responded with GOODBYE. In any case, we MUST NOT
                # send any WAMP message from now on:
                # clear out session ID, so that anything that might be triggered
                # in the onLeave below is prohibited from sending WAMP stuff.
                # E.g. the client might have been subscribed to meta events like
                # wamp.session.on_leave - and we must not send that client's own
                # leave to itself!
                self._session_id = None
                self._pending_session_id = None

                # publish event, *after* self._session_id is None so
                # that we don't publish to ourselves as well (if this
                # session happens to be subscribed to wamp.session.on_leave)
                if self._service_session:
                    self._service_session.publish(
                        u'wamp.session.on_leave',
                        previous_session_id,
                    )

                # fire callback and close the transport
                self.onLeave(types.CloseDetails(msg.reason, msg.message))

                # don't close the transport, as WAMP allows to reattach a session
                # to the same or a different realm without closing the transport
                # self._transport.close()

            else:
                self._router.process(self, msg)