Example #1
0
        def transport_check(_):
            self.log.debug('Entering re-connect loop')

            if not self._can_reconnect():
                err_msg = u"Component failed: Exhausted all transport connect attempts"
                self.log.info(err_msg)
                try:
                    raise RuntimeError(err_msg)
                except RuntimeError as e:
                    txaio.reject(self._done_f, e)
                    return

            while True:

                transport = next(transport_gen)

                if transport.can_reconnect():
                    transport_candidate[0] = transport
                    break

            delay = transport.next_delay()
            self.log.debug(
                'trying transport {transport_idx} using connect delay {transport_delay}',
                transport_idx=transport.idx,
                transport_delay=delay,
            )

            self._delay_f = txaio.sleep(delay)
            txaio.add_callbacks(self._delay_f, attempt_connect, error)
Example #2
0
def test_immediate_failure(framework):
    exception = RuntimeError("it failed")
    try:
        raise exception
    except:
        f0 = txaio.create_future_error()
        fail = txaio.create_failure()

    errors = []
    results = []
    f1 = txaio.create_future_error(fail)

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

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

    txaio.add_callbacks(f0, cb, errback)
    txaio.add_callbacks(f1, cb, errback)

    run_once()
    run_once()
    run_once()

    assert len(results) == 0
    assert len(errors) == 2
    assert isinstance(errors[0], txaio.IFailedFuture)
    assert isinstance(errors[1], txaio.IFailedFuture)
    assert errors[0].value == exception
    assert errors[1].value == exception
    # should be distinct FailedPromise instances
    assert id(errors[0]) != id(errors[1])
Example #3
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 #4
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 #5
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 #6
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 #7
0
    def introspectRemoteObject(self, busName, objectPath,
                               replaceKnownInterfaces=False):
        """
        Calls org.freedesktop.DBus.Introspectable.Introspect

        @type busName: C{string}
        @param busName: Name of the bus containing the object

        @type objectPath: C{string}
        @param objectPath: Object Path to introspect
        
        @type replaceKnownInterfaces: C{bool}
        @param replaceKnownInterfaces: If True (defaults to False), the content of
                                       the introspected XML will override any
                                       pre-existing definitions of the contained
                                       interfaces.

        @rtype: L{twisted.internet.defer.Deferred}
        @returns: a Deferred to a list of L{interface.DBusInterface} instances
                  created from the content of the introspected XML
                  description of the object's interface.
        """
        d = self.callRemote(objectPath, 'Introspect',
                            interface   = 'org.freedesktop.DBus.Introspectable',
                            destination = busName)

        def ok(xml_str):
            return introspection.getInterfacesFromXML(xml_str, replaceKnownInterfaces)

        def err(e):
            raise error.IntrospectionFailed('Introspection Failed: ' + e.getErrorMessage())

        txaio.add_callbacks(d, ok, err)

        return d
Example #8
0
def test_errback_reject_no_args():
    """
    txaio.reject() with no args
    """

    f = txaio.create_future()
    exception = RuntimeError("it failed")
    errors = []

    def err(f):
        errors.append(f)
    txaio.add_callbacks(f, None, err)
    try:
        raise exception
    except:
        txaio.reject(f)

    run_once()

    assert len(errors) == 1
    assert isinstance(errors[0], txaio.IFailedFuture)
    assert exception == errors[0].value
    tb = txaio.failure_format_traceback(errors[0])

    assert 'RuntimeError' in tb
    assert 'it failed' in tb
    assert txaio.failure_message(errors[0]) == 'RuntimeError: it failed'
    assert 'it failed' in str(errors[0])
Example #9
0
def test_errback(framework):
    f = txaio.create_future()
    exception = RuntimeError("it failed")
    errors = []

    def err(f):
        errors.append(f)
    txaio.add_callbacks(f, None, err)
    try:
        raise exception
    except:
        fail = txaio.create_failure()
    txaio.reject(f, fail)

    run_once()

    assert len(errors) == 1
    assert isinstance(errors[0], txaio.IFailedFuture)
    assert exception == errors[0].value
    assert txaio.failure_traceback(errors[0]) is not None

    tb = txaio.failure_format_traceback(errors[0])

    assert 'RuntimeError' in tb
    assert 'it failed' in tb
    assert txaio.failure_message(errors[0]) == 'RuntimeError: it failed'
    assert 'it failed' in str(errors[0])
Example #10
0
def test_create_error():
    f = txaio.create_future(error=RuntimeError("test"))
    if txaio.using_twisted:
        assert f.called
    else:
        assert f.done()
    # cancel the error; we expected it
    txaio.add_callbacks(f, None, lambda _: None)
        def on_connect(req):
            f = txaio.create_future()

            def cb(x):
                f = foo(42)
                f.add_callbacks(f, lambda v: values.append(v), None)
                return f
            txaio.add_callbacks(f, cb, None)
            return f
                def actual_connect(_):
                    f = self._connect_once(loop, transport)

                    def session_done(x):
                        txaio.resolve(done_f, None)

                    def connect_error(fail):
                        if isinstance(fail.value, asyncio.CancelledError):
                            reconnect[0] = False
                            txaio.reject(done_f, fail)
                            return

                        self.log.debug(u'component failed: {error}', error=txaio.failure_message(fail))
                        self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(fail))
                        # If this is a "fatal error" that will never work,
                        # we bail out now
                        if isinstance(fail.value, ApplicationError):
                            if fail.value.error in [u'wamp.error.no_such_realm']:
                                reconnect[0] = False
                                self.log.error(u"Fatal error, not reconnecting")
                                txaio.reject(done_f, fail)
                                return

                            self.log.error(u"{msg}", msg=fail.value.error_message())
                            return one_reconnect_loop(None)

                        elif isinstance(fail.value, OSError):
                            # failed to connect entirely, like nobody
                            # listening etc.
                            self.log.info(u"Connection failed: {msg}", msg=txaio.failure_message(fail))
                            return one_reconnect_loop(None)

                        elif _is_ssl_error(fail.value):
                            # Quoting pyOpenSSL docs: "Whenever
                            # [SSL.Error] is raised directly, it has a
                            # list of error messages from the OpenSSL
                            # error queue, where each item is a tuple
                            # (lib, function, reason). Here lib, function
                            # and reason are all strings, describing where
                            # and what the problem is. See err(3) for more
                            # information."
                            self.log.error(u"TLS failure: {reason}", reason=fail.value.args[1])
                            self.log.error(u"Marking this transport as failed")
                            transport.failed()
                        else:
                            self.log.error(
                                u'Connection failed: {error}',
                                error=txaio.failure_message(fail),
                            )
                            # some types of errors should probably have
                            # stacktraces logged immediately at error
                            # level, e.g. SyntaxError?
                            self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(fail))
                            return one_reconnect_loop(None)

                    txaio.add_callbacks(f, session_done, connect_error)
Example #13
0
    def addMatch(self, callback, mtype=None, sender=None, interface=None,
                 member=None, path=None, path_namespace=None, destination=None,
                 arg=None, arg_path=None, arg0namespace=None):
        """
        Creates a message matching rule, associates it with the specified
        callback function, and sends the match rule to the DBus daemon.
        The arguments to this function are exactly follow the DBus specification.
        Refer to the \"Message Bus Message Routing\" section of the DBus
        specification for details.

        @rtype: C{int}
        @returns: a L{Deferred} to an integer id that may be used to unregister the match rule
        """

        l = list()

        def add( k,v ):
            if v is not None:
                l.append( "%s='%s'" % (k,v) )

        add('type',           mtype)
        add('sender',         sender)
        add('interface',      interface)
        add('member',         member)
        add('path',           path)
        add('path_namespace', path_namespace)
        add('destination',    destination)

        if arg:
            for idx, v in arg:
                add('arg%d' % (idx,), v)

        if arg_path:
            for idx, v in arg_path:
                add('arg%dpath' % (idx,), v)

        add('arg0namespace', arg0namespace)

        rule = ','.join(l)

        d = self.callRemote('/org/freedesktop/DBus', 'AddMatch',
                            interface   = 'org.freedesktop.DBus',
                            destination = 'org.freedesktop.DBus',
                            body        = [rule],
                            signature   = 's')

        def ok(_):
            rule_id = self.router.addMatch(callback, mtype, sender, interface,
                                           member, path, path_namespace, destination,
                                           arg, arg_path, arg0namespace)
            self.match_rules[rule_id] = rule
            return rule_id

        txaio.add_callbacks(d, ok, None)

        return d
Example #14
0
            def onJoin(self, details):
                d2 = self.subscribe(lambda: None, u'com.example.topic1')

                def ok(_):
                    txaio.resolve(d, None)

                def error(err):
                    txaio.reject(d, err)

                txaio.add_callbacks(d2, ok, error)
    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 #16
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 #17
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 #18
0
def test_immediate_result(framework):
    f = txaio.create_future_success("it worked")
    results = []

    def cb(f):
        results.append(f)
    txaio.add_callbacks(f, cb, None)

    run_once()

    assert len(results) == 1
    assert results[0] == "it worked"
Example #19
0
def test_callback(framework):
    f = txaio.create_future()
    results = []

    def cb(f):
        results.append(f)
    txaio.add_callbacks(f, cb, None)
    txaio.resolve(f, "it worked")

    run_once()

    assert len(results) == 1
    assert results[0] == "it worked"
                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 #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 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 #22
0
 def notify_connect_error(fail):
     chain_f = txaio.create_future()
     # hmm, if connectfailure took a _Transport instead of
     # (or in addition to?) self it could .failed() the
     # transport and we could do away with the is_fatal
     # listener?
     handler_f = self.fire('connectfailure', self, fail.value)
     txaio.add_callbacks(
         handler_f,
         lambda _: txaio.reject(chain_f, fail),
         lambda _: txaio.reject(chain_f, fail)
     )
     return chain_f
Example #23
0
            def onJoin(self, details):
                # noinspection PyUnusedLocal
                def on_event(*arg, **kwargs):
                    pass

                d2 = self.subscribe(on_event, u'com.example.topic1')

                def ok(_):
                    txaio.resolve(d, None)

                def error(err):
                    txaio.reject(d, err)

                txaio.add_callbacks(d2, ok, error)
        def sign_challenge(self, session, challenge):
            """
            Sign WAMP-cryptosign challenge.

            :param challenge: The WAMP-cryptosign challenge object for which a signature should be computed.
            :type challenge: instance of autobahn.wamp.types.Challenge

            :returns: A Deferred/Future that resolves to the computed signature.
            :rtype: str
            """
            if not isinstance(challenge, Challenge):
                raise Exception("challenge must be instance of autobahn.wamp.types.Challenge, not {}".format(type(challenge)))

            if u'challenge' not in challenge.extra:
                raise Exception("missing challenge value in challenge.extra")

            # the challenge sent by the router (a 32 bytes random value)
            challenge_hex = challenge.extra[u'challenge']

            # the challenge for WAMP-cryptosign is a 32 bytes random value in Hex encoding (that is, a unicode string)
            challenge_raw = binascii.a2b_hex(challenge_hex)

            # if the transport has a channel ID, the message to be signed by the client actually
            # is the XOR of the challenge and the channel ID
            channel_id_raw = session._transport.get_channel_id()
            if channel_id_raw:
                data = util.xor(challenge_raw, channel_id_raw)
            else:
                data = challenge_raw

            # a raw byte string is signed, and the signature is also a raw byte string
            d1 = self.sign(data)

            # asyncio lacks callback chaining (and we cannot use co-routines, since we want
            # to support older Pythons), hence we need d2
            d2 = txaio.create_future()

            def process(signature_raw):
                # convert the raw signature into a hex encode value (unicode string)
                signature_hex = binascii.b2a_hex(signature_raw).decode('ascii')

                # we return the concatenation of the signature and the message signed (96 bytes)
                data_hex = binascii.b2a_hex(data).decode('ascii')

                sig = signature_hex + data_hex
                txaio.resolve(d2, sig)

            txaio.add_callbacks(d1, process, None)

            return d2
Example #25
0
    def _connect_transport(self, reactor, transport, session_factory, done):
        """
        Create and connect a WAMP-over-XXX transport.

        :param done: is a Deferred/Future from the parent which we
            should signal upon error if it is not done yet (XXX maybe an
            "on_error" callable instead?)
        """
        transport_factory = _create_transport_factory(reactor, transport, session_factory)
        if transport.proxy:
            transport_endpoint = _create_transport_endpoint(
                reactor,
                {
                    "type": "tcp",
                    "host": transport.proxy["host"],
                    "port": transport.proxy["port"],
                }
            )
        else:
            transport_endpoint = _create_transport_endpoint(reactor, transport.endpoint)
        d = transport_endpoint.connect(transport_factory)

        def on_connect_success(proto):

            # if e.g. an SSL handshake fails, we will have
            # successfully connected (i.e. get here) but need to
            # 'listen' for the "connectionLost" from the underlying
            # protocol in case of handshake failure .. so we wrap
            # it. Also, we don't increment transport.success_count
            # here on purpose (because we might not succeed).
            orig = proto.connectionLost

            @wraps(orig)
            def lost(fail):
                rtn = orig(fail)
                if not txaio.is_called(done):
                    txaio.reject(done, fail)
                return rtn
            proto.connectionLost = lost

        def on_connect_failure(err):
            transport.connect_failures += 1
            # failed to establish a connection in the first place
            txaio.reject(done, err)

        txaio.add_callbacks(d, on_connect_success, None)
        txaio.add_callbacks(d, None, on_connect_failure)
        return d
Example #26
0
def _run(reactor, components):
    if isinstance(components, Component):
        components = [components]

    if type(components) != list:
        raise ValueError(
            '"components" must be a list of Component objects - encountered'
            ' {0}'.format(type(components))
        )

    for c in components:
        if not isinstance(c, Component):
            raise ValueError(
                '"components" must be a list of Component objects - encountered'
                'item of type {0}'.format(type(c))
            )

    log = txaio.make_logger()

    def component_success(c, arg):
        log.debug("Component {c} successfully completed: {arg}", c=c, arg=arg)
        return arg

    def component_failure(f):
        log.error("Component error: {msg}", msg=txaio.failure_message(f))
        log.debug("Component error: {tb}", tb=txaio.failure_format_traceback(f))
        return None

    # all components are started in parallel
    dl = []
    for c in components:
        # a component can be of type MAIN or SETUP
        d = c.start(reactor)
        txaio.add_callbacks(d, partial(component_success, c), component_failure)
        dl.append(d)
    d = txaio.gather(dl, consume_exceptions=False)

    def all_done(arg):
        log.debug("All components ended; stopping reactor")
        try:
            reactor.stop()
        except ReactorNotRunning:
            pass

    txaio.add_callbacks(d, all_done, all_done)

    return d
Example #27
0
    def connectionAuthenticated(self):
        """
        Called by L{protocol.BasicDBusProtocol} when the DBus authentication
        has completed successfully.
        """
        self.router        = router.MessageRouter()
        self.match_rules   = dict()
        self.objHandler    = objects.DBusObjectHandler(self)
        self._pendingCalls = dict() # serial_number => (deferred, delayed_timeout_cb | None)
        self._dcCallbacks  = list()

        d = self.callRemote( '/Hello', 'Hello',
                             interface   = 'org.freedesktop.DBus',
                             destination = 'org.freedesktop.DBus' )

        txaio.add_callbacks(d, self._cbGotHello,
                               lambda err: self.doNotifyStartupError(err))
Example #28
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 #29
0
    def delMatch(self, rule_id):
        """
        Removes a message matching rule previously registered with addMatch
        """
        rule = self.match_rules[rule_id]
        
        d = self.callRemote('/org/freedesktop/DBus', 'RemoveMatch',
                            interface   = 'org.freedesktop.DBus',
                            destination = 'org.freedesktop.DBus',
                            body        = [rule],
                            signature   = 's')

        def ok(_):
            del self.match_rules[rule_id]
            self.router.delMatch(rule_id)

        txaio.add_callbacks(d, ok, None)

        return d
Example #30
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 #31
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 for normal subscriptions, may be empty for
        # wildcard subscriptions and must be non-empty for all but the last component for
        # prefix subscriptions
        #
        if self._option_uri_strict:
            if subscribe.match == u"wildcard":
                uri_is_valid = _URI_PAT_STRICT_EMPTY.match(subscribe.topic)
            elif subscribe.match == u"prefix":
                uri_is_valid = _URI_PAT_STRICT_LAST_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)
            elif subscribe.match == u"prefix":
                uri_is_valid = _URI_PAT_LOOSE_LAST_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)])
            self._router.send(session, reply)
            return

        # authorize SUBSCRIBE action
        #
        d = self._router.authorize(session, subscribe.topic, u'subscribe')

        def on_authorize_success(authorization):
            if not authorization[u'allow']:
                # 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 = {
                                u'id': subscription.id,
                                u'created': subscription.created,
                                u'uri': subscription.uri,
                                u'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
            #
            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)
            """
            # 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)]
            )
            self._router.send(session, reply)

        txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
Example #32
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))
        def sign_challenge(self,
                           session,
                           challenge,
                           channel_id_type='tls-unique'):
            """
            Sign WAMP-cryptosign challenge.

            :param session: The authenticating WAMP session.
            :type session: :class:`autobahn.wamp.protocol.ApplicationSession`

            :param challenge: The WAMP-cryptosign challenge object for which a signature should be computed.
            :type challenge: instance of autobahn.wamp.types.Challenge

            :returns: A Deferred/Future that resolves to the computed signature.
            :rtype: str
            """
            if not isinstance(challenge, Challenge):
                raise Exception(
                    "challenge must be instance of autobahn.wamp.types.Challenge, not {}"
                    .format(type(challenge)))

            if 'challenge' not in challenge.extra:
                raise Exception("missing challenge value in challenge.extra")

            # the challenge sent by the router (a 32 bytes random value)
            challenge_hex = challenge.extra['challenge']

            if type(challenge_hex) != str:
                raise Exception(
                    "invalid type {} for challenge (expected a hex string)".
                    format(type(challenge_hex)))

            if len(challenge_hex) != 64:
                raise Exception(
                    "unexpected challenge (hex) length: was {}, but expected 64"
                    .format(len(challenge_hex)))

            # the challenge for WAMP-cryptosign is a 32 bytes random value in Hex encoding (that is, a unicode string)
            challenge_raw = binascii.a2b_hex(challenge_hex)

            if channel_id_type == 'tls-unique':
                # get the TLS channel ID of the underlying TLS connection
                channel_id_raw = session._transport.get_channel_id()
                assert len(
                    channel_id_raw
                ) == 32, 'unexpected TLS transport channel ID length (was {}, but expected 32)'.format(
                    len(channel_id_raw))

                # with TLS channel binding of type "tls-unique", the message to be signed by the client actually
                # is the XOR of the challenge and the TLS channel ID
                data = util.xor(challenge_raw, channel_id_raw)
            elif channel_id_type is None:
                # when no channel binding was requested, the message to be signed by the client is the challenge only
                data = challenge_raw
            else:
                assert False, 'invalid channel_id_type "{}"'.format(
                    channel_id_type)

            # a raw byte string is signed, and the signature is also a raw byte string
            d1 = self.sign(data)

            # asyncio lacks callback chaining (and we cannot use co-routines, since we want
            # to support older Pythons), hence we need d2
            d2 = txaio.create_future()

            def process(signature_raw):
                # convert the raw signature into a hex encode value (unicode string)
                signature_hex = binascii.b2a_hex(signature_raw).decode('ascii')

                # we return the concatenation of the signature and the message signed (96 bytes)
                data_hex = binascii.b2a_hex(data).decode('ascii')

                sig = signature_hex + data_hex
                txaio.resolve(d2, sig)

            txaio.add_callbacks(d1, process, None)

            return d2
Example #34
0
    def processPublish(self, session, publish):
        """
        Implements :func:`crossbar.router.interfaces.IBroker.processPublish`
        """
        if self._router.is_traced:
            if not publish.correlation_id:
                publish.correlation_id = self._router.new_correlation_id()
                publish.correlation_is_anchor = True
            if not publish.correlation_uri:
                publish.correlation_uri = publish.topic

        # 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:
                if self._router.is_traced:
                    publish.correlation_is_last = False
                    self._router._factory._worker._maybe_trace_rx_msg(session, publish)

                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)])
                reply.correlation_id = publish.correlation_id
                reply.correlation_uri = publish.topic
                reply.correlation_is_anchor = False
                reply.correlation_is_last = True
                self._router.send(session, reply)

            else:
                if self._router.is_traced:
                    publish.correlation_is_last = True
                    self._router._factory._worker._maybe_trace_rx_msg(session, publish)
            return

        # disallow publication to topics starting with "wamp." other than for
        # trusted sessions (that are sessions built into Crossbar.io routing core)
        #
        if session._authrole is not None and session._authrole != u"trusted":
            is_restricted = publish.topic.startswith(u"wamp.")
            if is_restricted:
                if publish.acknowledge:
                    if self._router.is_traced:
                        publish.correlation_is_last = False
                        self._router._factory._worker._maybe_trace_rx_msg(session, publish)

                    reply = message.Error(message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.INVALID_URI, [u"publish with restricted topic URI '{0}'".format(publish.topic)])
                    reply.correlation_id = publish.correlation_id
                    reply.correlation_uri = publish.topic
                    reply.correlation_is_anchor = False
                    reply.correlation_is_last = True
                    self._router.send(session, reply)

                else:
                    if self._router.is_traced:
                        publish.correlation_is_last = True
                        self._router._factory._worker._maybe_trace_rx_msg(session, publish)

                return

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

        # check if the event is being persisted by checking if we ourself are among the observers
        # on _any_ matching subscription
        # we've been previously added to observer lists on subscriptions ultimately from
        # node configuration and during the broker starts up.
        store_event = False
        if self._event_store:
            for subscription in subscriptions:
                if self._event_store in subscription.observers:
                    store_event = True
                    break
        if store_event:
            self.log.debug('Persisting event on topic "{topic}"', topic=publish.topic)

        # check if the event is to be retained by inspecting the 'retain' flag
        retain_event = False
        if publish.retain:
            retain_event = True

        # go on if (otherwise there isn't anything to do anyway):
        #
        #   - there are any active subscriptions OR
        #   - the publish is to be acknowledged OR
        #   - the event is to be persisted OR
        #   - the event is to be retained
        #
        if not (subscriptions or publish.acknowledge or store_event or retain_event):

            # the received PUBLISH message is the only one received/sent
            # for this WAMP action, so mark it as "last" (there is another code path below!)
            if self._router.is_traced:
                if publish.correlation_is_last is None:
                    publish.correlation_is_last = True
                self._router._factory._worker._maybe_trace_rx_msg(session, publish)

        else:

            # validate payload
            #
            if publish.payload is None:
                try:
                    self._router.validate('event', publish.topic, publish.args, publish.kwargs)
                except Exception as e:
                    if publish.acknowledge:
                        if self._router.is_traced:
                            publish.correlation_is_last = False
                            self._router._factory._worker._maybe_trace_rx_msg(session, publish)

                        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)])
                        reply.correlation_id = publish.correlation_id
                        reply.correlation_uri = publish.topic
                        reply.correlation_is_anchor = False
                        reply.correlation_is_last = True
                        self._router.send(session, reply)
                    else:
                        if self._router.is_traced:
                            publish.correlation_is_last = True
                            self._router._factory._worker._maybe_trace_rx_msg(session, publish)

                    return

            # authorize PUBLISH action
            #
            d = self._router.authorize(session, publish.topic, u'publish', options=publish.marshal_options())

            def on_authorize_success(authorization):

                # the call to authorize the action _itself_ succeeded. now go on depending on whether
                # the action was actually authorized or not ..
                #
                if not authorization[u'allow']:

                    if publish.acknowledge:
                        if self._router.is_traced:
                            publish.correlation_is_last = False
                            self._router._factory._worker._maybe_trace_rx_msg(session, publish)

                        reply = message.Error(message.Publish.MESSAGE_TYPE, publish.request, ApplicationError.NOT_AUTHORIZED, [u"session not authorized to publish to topic '{0}'".format(publish.topic)])
                        reply.correlation_id = publish.correlation_id
                        reply.correlation_uri = publish.topic
                        reply.correlation_is_anchor = False
                        reply.correlation_is_last = True
                        self._router.send(session, reply)

                    else:
                        if self._router.is_traced:
                            publish.correlation_is_last = True
                            self._router._factory._worker._maybe_trace_rx_msg(session, publish)

                else:

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

                    # publisher disclosure
                    #
                    if authorization[u'disclose']:
                        disclose = True
                    elif (publish.topic.startswith(u"wamp.") or publish.topic.startswith(u"crossbar.")):
                        disclose = True
                    else:
                        disclose = False

                    if disclose:
                        publisher = session._session_id
                        publisher_authid = session._authid
                        publisher_authrole = session._authrole
                    else:
                        publisher = None
                        publisher_authid = None
                        publisher_authrole = None

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

                    # persist event (this is done only once, regardless of the number of subscriptions
                    # the event matches on)
                    #
                    if store_event:
                        self._event_store.store_event(session, publication, publish)

                    # retain event on the topic
                    #
                    if retain_event:
                        retained_event = RetainedEvent(publish, publisher, publisher_authid, publisher_authrole)

                        observation = self._subscription_map.get_observation(publish.topic)

                        if not observation:
                            # No observation, lets make a new one
                            observation = self._subscription_map.create_observation(publish.topic, extra=SubscriptionExtra())
                        else:
                            # this can happen if event-history is
                            # enabled on the topic: the event-store
                            # creates an observation before any client
                            # could possible hit the code above
                            if observation.extra is None:
                                observation.extra = SubscriptionExtra()
                            elif not isinstance(observation.extra, SubscriptionExtra):
                                raise Exception(
                                    "incorrect 'extra' for '{}'".format(publish.topic)
                                )

                        if observation.extra.retained_events:
                            if not publish.eligible and not publish.exclude:
                                observation.extra.retained_events = [retained_event]
                            else:
                                observation.extra.retained_events.append(retained_event)
                        else:
                            observation.extra.retained_events = [retained_event]

                    subscription_to_receivers = {}
                    total_receivers_cnt = 0

                    # iterate over all subscriptions and determine actual receivers of the event
                    # under the respective subscription. also persist events (independent of whether
                    # there is any actual receiver right now on the subscription)
                    #
                    for subscription in subscriptions:

                        # initial list of receivers are all subscribers on a subscription ..
                        #
                        receivers = subscription.observers
                        receivers = self._filter_publish_receivers(receivers, publish)

                        # if receivers is non-empty, dispatch event ..
                        #
                        receivers_cnt = len(receivers) - (1 if self in receivers else 0)
                        if receivers_cnt:

                            total_receivers_cnt += receivers_cnt
                            subscription_to_receivers[subscription] = receivers

                    # send publish acknowledge before dispatching
                    #
                    if publish.acknowledge:
                        if self._router.is_traced:
                            publish.correlation_is_last = False
                            self._router._factory._worker._maybe_trace_rx_msg(session, publish)

                        reply = message.Published(publish.request, publication)
                        reply.correlation_id = publish.correlation_id
                        reply.correlation_uri = publish.topic
                        reply.correlation_is_anchor = False
                        reply.correlation_is_last = total_receivers_cnt == 0
                        self._router.send(session, reply)
                    else:
                        if self._router.is_traced and publish.correlation_is_last is None:
                            if total_receivers_cnt == 0:
                                publish.correlation_is_last = True
                            else:
                                publish.correlation_is_last = False

                    # now actually dispatch the events!
                    # for chunked dispatching, this will be filled with deferreds for each chunk
                    # processed. when the complete list of deferreds is done, that means the
                    # event has been sent out to all applicable receivers
                    all_dl = []

                    if total_receivers_cnt:

                        # list of receivers that should have received the event, but we could not
                        # send the event, since the receiver has disappeared in the meantime
                        vanished_receivers = []

                        for subscription, receivers in subscription_to_receivers.items():

                            storing_event = store_event and self._event_store in subscription.observers

                            self.log.debug('dispatching for subscription={subscription}, storing_event={storing_event}',
                                           subscription=subscription, storing_event=storing_event)

                            # 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

                            if publish.payload:
                                msg = message.Event(subscription.id,
                                                    publication,
                                                    payload=publish.payload,
                                                    publisher=publisher,
                                                    publisher_authid=publisher_authid,
                                                    publisher_authrole=publisher_authrole,
                                                    topic=topic,
                                                    enc_algo=publish.enc_algo,
                                                    enc_key=publish.enc_key,
                                                    enc_serializer=publish.enc_serializer,
                                                    forward_for=publish.forward_for)
                            else:
                                msg = message.Event(subscription.id,
                                                    publication,
                                                    args=publish.args,
                                                    kwargs=publish.kwargs,
                                                    publisher=publisher,
                                                    publisher_authid=publisher_authid,
                                                    publisher_authrole=publisher_authrole,
                                                    topic=topic,
                                                    forward_for=publish.forward_for)

                            # if the publish message had a correlation ID, this will also be the
                            # correlation ID of the event message sent out
                            msg.correlation_id = publish.correlation_id
                            msg.correlation_uri = publish.topic
                            msg.correlation_is_anchor = False
                            msg.correlation_is_last = False

                            chunk_size = self._options.event_dispatching_chunk_size

                            if chunk_size and len(receivers) > chunk_size:
                                self.log.debug('chunked dispatching to {receivers_size} with chunk_size={chunk_size}',
                                               receivers_size=len(receivers), chunk_size=chunk_size)
                            else:
                                self.log.debug('unchunked dispatching to {receivers_size} receivers',
                                               receivers_size=len(receivers))

                            # note that we're using one code-path for both chunked and unchunked
                            # dispatches; the *first* chunk is always done "synchronously" (before
                            # the first call-later) so "un-chunked mode" really just means we know
                            # we'll be done right now and NOT do a call_later...

                            # a Deferred that fires when all chunks are done
                            all_d = txaio.create_future()
                            all_dl.append(all_d)

                            # all the event messages are the same except for the last one, which
                            # needs to have the "is_last" flag set if we're doing a trace
                            if self._router.is_traced:
                                last_msg = copy.deepcopy(msg)
                                last_msg.correlation_id = msg.correlation_id
                                last_msg.correlation_uri = msg.correlation_uri
                                last_msg.correlation_is_anchor = False
                                last_msg.correlation_is_last = True

                            def _notify_some(receivers):

                                # we do a first pass over the proposed chunk of receivers
                                # because not all of them will have a transport, and if this
                                # will be the last chunk of receivers we need to figure out
                                # which event is last...
                                receivers_this_chunk = []
                                for receiver in receivers[:chunk_size]:
                                    if receiver._session_id and receiver._transport:
                                        receivers_this_chunk.append(receiver)
                                    else:
                                        vanished_receivers.append(receiver)

                                receivers = receivers[chunk_size:]

                                # XXX note there's still going to be some edge-cases here .. if
                                # we are NOT the last chunk, but all the next chunk's receivers
                                # (could be only 1 in that chunk!) vanish before we run our next
                                # batch, then a "last" event will never go out ...

                                # we now actually do the deliveries, but now we know which
                                # receiver is the last one
                                if receivers or not self._router.is_traced:

                                    # NOT the last chunk (or we're not traced so don't care)
                                    for receiver in receivers_this_chunk:

                                        # send out WAMP msg to peer
                                        self._router.send(receiver, msg)
                                        if self._event_store or storing_event:
                                            self._event_store.store_event_history(publication, subscription.id, receiver)
                                else:
                                    # last chunk, so last receiver gets the different message
                                    for receiver in receivers_this_chunk[:-1]:
                                        self._router.send(receiver, msg)
                                        if self._event_store or storing_event:
                                            self._event_store.store_event_history(publication, subscription.id, receiver)

                                    # FIXME: I don't get the following comment and code path. when, how? and what to
                                    # do about event store? => storing_event
                                    #
                                    # we might have zero valid receivers
                                    if receivers_this_chunk:
                                        self._router.send(receivers_this_chunk[-1], last_msg)
                                        # FIXME: => storing_event

                                if receivers:
                                    # still more to do ..
                                    return txaio.call_later(0, _notify_some, receivers)
                                else:
                                    # all done! resolve all_d, which represents all receivers
                                    # to a single subscription matching the event
                                    txaio.resolve(all_d, None)

                            _notify_some([
                                recv for recv in receivers
                                if (me_also or recv != session) and recv != self._event_store
                            ])

                    return txaio.gather(all_dl)

            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("Authorization failed", failure=err)
                if publish.acknowledge:

                    if self._router.is_traced:
                        publish.correlation_is_last = False
                        self._router._factory._worker._maybe_trace_rx_msg(session, publish)

                    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)]
                    )
                    reply.correlation_id = publish.correlation_id
                    reply.correlation_uri = publish.topic
                    reply.correlation_is_anchor = False
                    self._router.send(session, reply)
                else:
                    if self._router.is_traced:
                        publish.correlation_is_last = True
                        self._router._factory._worker._maybe_trace_rx_msg(session, publish)

            txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
Example #35
0
    def onMessage(self, msg):
        """
        Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage`
        """
        if self._session_id is None:

            # the first message must be WELCOME, ABORT or CHALLENGE ..
            if isinstance(msg, message.Welcome):
                self._session_id = msg.session

                details = SessionDetails(self._realm, self._session_id,
                                         msg.authid, msg.authrole,
                                         msg.authmethod)
                d = txaio.as_future(self.onJoin, details)

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

                txaio.add_callbacks(d, None, _error)

            elif isinstance(msg, message.Abort):

                # fire callback and close the transport
                details = types.CloseDetails(msg.reason, msg.message)
                d = txaio.as_future(self.onLeave, details)

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

                txaio.add_callbacks(d, None, _error)

            elif isinstance(msg, message.Challenge):

                challenge = types.Challenge(msg.method, msg.extra)
                d = txaio.as_future(self.onChallenge, challenge)

                def success(signature):
                    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)

                        try:
                            handler.fn(*invoke_args, **invoke_kwargs)
                        except Exception as e:
                            msg = 'While firing {0} subscribed under {1}.'.format(
                                handler.fn, msg.subscription)
                            try:
                                self.onUserError(e, msg)
                            except:
                                pass

                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__))
    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:
                session = self.session_factory(cfg)
                for auth_name, auth_config in self._authentication.items():
                    session.add_authenticator(auth_name, **auth_config)

            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 self._entry:
                        txaio.resolve(done, None)

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

                    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 was_clean:
                            # eg the session has left the realm, and the transport was properly
                            # shut down. successfully finish the connection
                            txaio.resolve(done, None)
                        else:
                            txaio.reject(
                                done,
                                RuntimeError('transport closed uncleanly'))

                session.on('disconnect', on_disconnect)

                # return the fresh session object
                return session

        transport.connect_attempts += 1
        d = self._connect_transport(reactor, transport, create_session)

        def on_connect_sucess(proto):
            # if e.g. an SSL handshake fails, we will have
            # successfully connected (i.e. get here) but need to
            # 'listen' for the "connectionLost" from the underlying
            # protocol in case of handshake failure .. so we wrap
            # it. Also, we don't increment transport.success_count
            # here on purpose (because we might not succeed).
            orig = proto.connectionLost

            @wraps(orig)
            def lost(fail):
                rtn = orig(fail)
                if not txaio.is_called(done):
                    txaio.reject(done, fail)
                return rtn

            proto.connectionLost = lost

        def on_connect_failure(err):
            transport.connect_failures += 1
            # failed to establish a connection in the first place
            done.errback(err)

        txaio.add_callbacks(d, on_connect_sucess, None)
        txaio.add_callbacks(d, None, on_connect_failure)

        return done
Example #37
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:
                session = self.session_factory(cfg)
            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

                # the only difference bewteen MAIN and SETUP-type
                # entry-points is that we want to shut down the
                # component when a MAIN-type entrypoint's Deferred is
                # done.
                if self._entry_type == Component.TYPE_MAIN:

                    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.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)

                    session.on('join', on_join)

                elif self._entry_type == Component.TYPE_SETUP:

                    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.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)

                    session.on('join', on_join)

                else:
                    assert (False), 'logic error'

                # listen on leave events
                def on_leave(session, details):
                    self.log.debug("session on_leave: {details}",
                                   details=details)
                    # this could be a "leave" that's expected e.g. our
                    # main() exited, or it could be an error
                    if not txaio.is_called(done):
                        if details.reason.startswith('wamp.error.'):
                            txaio.reject(
                                done,
                                ApplicationError(details.reason,
                                                 details.message))
                        else:
                            txaio.resolve(done, None)

                session.on('leave', on_leave)

                # listen on disconnect events
                def on_disconnect(session, was_clean):
                    self.log.debug("session on_disconnect: {was_clean}",
                                   was_clean=was_clean)

                    if was_clean:
                        # eg the session has left the realm, and the transport was properly
                        # shut down. successfully finish the connection
                        txaio.resolve(done, None)
                    else:
                        txaio.reject(
                            done, RuntimeError('transport closed uncleanly'))

                session.on('disconnect', on_disconnect)

                # return the fresh session object
                return session

        transport.connect_attempts += 1
        d = self._connect_transport(reactor, transport, create_session)

        def on_connect_sucess(proto):
            # if e.g. an SSL handshake fails, we will have
            # successfully connected (here) but need to listen for the
            # "connectionLost" from the underlying protocol in case of
            # handshake failure .. so we wrap it. Also, we don't
            # increment transport.success_count here.
            orig = proto.connectionLost

            @wraps(orig)
            def lost(fail):
                rtn = orig(fail)
                if not txaio.is_called(done):
                    txaio.reject(done, fail)
                return rtn

            proto.connectionLost = lost

        def on_connect_failure(err):
            transport.connect_failures += 1
            # failed to establish a connection in the first place
            done.errback(err)

        txaio.add_callbacks(d, on_connect_sucess, on_connect_failure)

        return done
def _run(reactor, components, done_callback):
    """
    Internal helper. Use "run" method from autobahn.twisted.wamp or
    autobahn.asyncio.wamp

    This is the generic parts of the run() method so that there's very
    little code in the twisted/asyncio specific run() methods.

    This is called by react() (or run_until_complete() so any errors
    coming out of this should be handled properly. Logging will
    already be started.
    """
    # let user pass a single component to run, too
    # XXX probably want IComponent? only demand it, here and below?
    if isinstance(components, Component):
        components = [components]

    if type(components) != list:
        raise ValueError(
            '"components" must be a list of Component objects - encountered'
            ' {0}'.format(type(components)))

    for c in components:
        if not isinstance(c, Component):
            raise ValueError(
                '"components" must be a list of Component objects - encountered'
                'item of type {0}'.format(type(c)))

    # validation complete; proceed with startup
    log = txaio.make_logger()

    def component_success(comp, arg):
        log.debug("Component '{c}' successfully completed: {arg}",
                  c=comp,
                  arg=arg)
        return arg

    def component_failure(comp, f):
        log.error("Component '{c}' error: {msg}",
                  c=comp,
                  msg=txaio.failure_message(f))
        log.debug("Component error: {tb}",
                  tb=txaio.failure_format_traceback(f))
        # double-check: is a component-failure still fatal to the
        # startup process (because we passed consume_exception=False
        # to gather() below?)
        return None

    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

    # note that these are started in parallel -- maybe we want to add
    # a "connected" signal to components so we could start them in the
    # order they're given to run() as "a" solution to dependencies.
    dl = []
    for comp in components:
        d = component_start(comp)
        dl.append(d)
    done_d = txaio.gather(dl, consume_exceptions=False)

    def all_done(arg):
        log.debug("All components ended; stopping reactor")
        done_callback(reactor, arg)

    txaio.add_callbacks(done_d, all_done, all_done)
    return done_d
Example #39
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)

            # fire onOpen callback and handle any exception escaping from there
            d = txaio.as_future(self._session.onJoin, details)
            txaio.add_callbacks(d, None, lambda fail: self._swallow_error(fail, "While firing onJoin"))

        # 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):
            # fire onClose callback and handle any exception escaping from there
            d = txaio.as_future(self._session.onClose, None)
            txaio.add_callbacks(d, None, lambda fail: self._swallow_error(fail, "While firing onClose"))

        else:
            # should not arrive here
            #
            raise Exception("RouterApplicationSession.send: unhandled message {0}".format(msg))
Example #40
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 #41
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: answer with GOODBYE reply.
                    # Note: We MUST NOT send any WAMP message _after_ GOODBYE
                    reply = message.Goodbye()
                    self._transport.send(reply)
                    self._goodbye_sent = True
                else:
                    # This is the peer's GOODBYE reply to our own earlier GOODBYE
                    pass

                # We need to first detach the session from the router before
                # erasing the session ID below ..
                self._router.detach(self)

                # 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 #42
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)
                ])
            session._transport.send(reply)
            return

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

        if registration:

            # validate payload
            #
            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)
                    ])
                session._transport.send(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)
                        ])
                    session._transport.send(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

                    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)
                    callee._transport.send(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)
                    ])
                session._transport.send(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)
                ])
            session._transport.send(reply)
Example #43
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, [
                        "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, [
                            "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, [
                            "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,
                                IRouter.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, [
                                "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)
                #
                if publish.acknowledge:
                    reply = message.Error(
                        message.Publish.MESSAGE_TYPE, publish.request,
                        ApplicationError.AUTHORIZATION_FAILED, [
                            "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 #44
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)
                ])
            session._transport.send(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)
                    ])
                session._transport.send(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)
                    ])
                session._transport.send(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)
                    ])
                session._transport.send(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
            #
            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)
            """
            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)
                ])
            session._transport.send(reply)

        txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
Example #45
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)
                    ])
                self._router.send(session, reply)
            return

        # disallow publication to topics starting with "wamp." other than for
        # trusted sessions (that are sessions built into Crossbar.io routing core)
        #
        if session._authrole is not None and session._authrole != u"trusted":
            is_restricted = publish.topic.startswith(u"wamp.")
            if is_restricted:
                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)
                        ])
                    self._router.send(session, reply)
                return

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

        # check if the event is being persisted by checking if we ourself are among the observers
        # on _any_ matching subscription
        # we've been previously added to observer lists on subscriptions ultimately from
        # node configuration and during the broker starts up.
        store_event = False
        if self._event_store:
            for subscription in subscriptions:
                if self._event_store in subscription.observers:
                    store_event = True
                    break
        if store_event:
            self.log.debug('Persisting event on topic "{topic}"',
                           topic=publish.topic)

        # check if the event is to be retained by inspecting the 'retain' flag
        retain_event = False
        if publish.retain:
            retain_event = True

        # go on if (otherwise there isn't anything to do anyway):
        #
        #   - there are any active subscriptions OR
        #   - the publish is to be acknowledged OR
        #   - the event is to be persisted OR
        #   - the event is to be retained
        #
        if subscriptions or publish.acknowledge or store_event or retain_event:

            # If it's a MQTT publish, we need to adjust the arguments.
            if getattr(publish, "_mqtt_publish", False):
                from crossbar.adapter.mqtt.wamp import mqtt_payload_transform
                tfd = mqtt_payload_transform(self._router._mqtt_payload_format,
                                             publish.payload)

                if not tfd:
                    # If we have no message to give, drop it entirely
                    return
                else:
                    publish.payload = None
                    publish.args, publish.kwargs = tfd

            # validate payload
            #
            if publish.payload is None:
                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)
                            ])
                        self._router.send(session, reply)
                    return

            # authorize PUBLISH action
            #
            d = self._router.authorize(session, publish.topic, u'publish')

            def on_authorize_success(authorization):

                # the call to authorize the action _itself_ succeeded. now go on depending on whether
                # the action was actually authorized or not ..
                #
                if not authorization[u'allow']:

                    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)
                            ])
                        self._router.send(session, reply)

                else:

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

                    # persist event (this is done only once, regardless of the number of subscriptions
                    # the event matches on)
                    #
                    if store_event:
                        self._event_store.store_event(session._session_id,
                                                      publication,
                                                      publish.topic,
                                                      publish.args,
                                                      publish.kwargs)

                    # retain event on the topic
                    #
                    if retain_event:
                        observation = self._subscription_map.get_observation(
                            publish.topic)

                        if not observation:
                            # No observation, lets make a new one
                            observation = self._subscription_map.create_observation(
                                publish.topic, extra=SubscriptionExtra())

                        if observation.extra.retained_events:
                            if not publish.eligible and not publish.exclude:
                                observation.extra.retained_events = [publish]
                            else:
                                observation.extra.retained_events.append(
                                    publish)
                        else:
                            observation.extra.retained_events = [publish]

                    # send publish acknowledge immediately when requested
                    #
                    if publish.acknowledge:
                        reply = message.Published(publish.request, publication)
                        self._router.send(session, reply)

                    # publisher disclosure
                    #
                    if authorization[u'disclose']:
                        disclose = True
                    elif (publish.topic.startswith(u"wamp.")
                          or publish.topic.startswith(u"crossbar.")):
                        disclose = True
                    else:
                        disclose = False

                    if disclose:
                        publisher = session._session_id
                        publisher_authid = session._authid
                        publisher_authrole = session._authrole
                    else:
                        publisher = None
                        publisher_authid = None
                        publisher_authrole = 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:

                        # persist event history, but check if it is persisted on the individual subscription!
                        #
                        if store_event and self._event_store in subscription.observers:
                            self._event_store.store_event_history(
                                publication, subscription.id)

                        # 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 ..
                        #
                        receivers_cnt = len(receivers) - (1 if self
                                                          in receivers else 0)
                        if receivers_cnt:

                            # 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

                            if publish.payload:
                                msg = message.Event(
                                    subscription.id,
                                    publication,
                                    payload=publish.payload,
                                    publisher=publisher,
                                    publisher_authid=publisher_authid,
                                    publisher_authrole=publisher_authrole,
                                    topic=topic,
                                    enc_algo=publish.enc_algo,
                                    enc_key=publish.enc_key,
                                    enc_serializer=publish.enc_serializer)
                            else:
                                msg = message.Event(
                                    subscription.id,
                                    publication,
                                    args=publish.args,
                                    kwargs=publish.kwargs,
                                    publisher=publisher,
                                    publisher_authid=publisher_authid,
                                    publisher_authrole=publisher_authrole,
                                    topic=topic)
                            for receiver in receivers:
                                if (me_also or receiver != session
                                    ) and receiver != self._event_store:
                                    # the receiving subscriber session
                                    # might have no transport, or no
                                    # longer be joined
                                    if receiver._session_id and receiver._transport:
                                        self._router.send(receiver, 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("Authorization failed", 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)
                        ])
                    self._router.send(session, reply)

            txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
Example #46
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)
                    ])
                self._router.send(session, reply)
            return

        # disallow publication to topics starting with "wamp." other than for
        # trusted sessions (that are sessions built into Crossbar.io routing core)
        #
        if session._authrole is not None and session._authrole != u"trusted":
            is_restricted = publish.topic.startswith(u"wamp.")
            if is_restricted:
                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)
                        ])
                    self._router.send(session, reply)
                return

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

        # check if the event is being persisted by checking if we ourself are among the observers
        # on _any_ matching subscription
        # we've been previously added to observer lists on subscriptions ultimately from
        # node configuration and during the broker starts up.
        store_event = False
        if self._event_store:
            for subscription in subscriptions:
                if self._event_store in subscription.observers:
                    store_event = True
                    break
        if store_event:
            self.log.debug('Persisting event on topic "{topic}"',
                           topic=publish.topic)

        # check if the event is to be retained by inspecting the 'retain' flag
        retain_event = False
        if publish.retain:
            retain_event = True

        # go on if (otherwise there isn't anything to do anyway):
        #
        #   - there are any active subscriptions OR
        #   - the publish is to be acknowledged OR
        #   - the event is to be persisted OR
        #   - the event is to be retained
        #
        if subscriptions or publish.acknowledge or store_event or retain_event:

            # validate payload
            #
            if publish.payload is None:
                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)
                            ])
                        self._router.send(session, reply)
                    return

            # authorize PUBLISH action
            #
            d = self._router.authorize(session,
                                       publish.topic,
                                       u'publish',
                                       options=publish.marshal_options())

            def on_authorize_success(authorization):

                # the call to authorize the action _itself_ succeeded. now go on depending on whether
                # the action was actually authorized or not ..
                #
                if not authorization[u'allow']:

                    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)
                            ])
                        self._router.send(session, reply)

                else:

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

                    # send publish acknowledge immediately when requested
                    #
                    if publish.acknowledge:
                        reply = message.Published(publish.request, publication)
                        self._router.send(session, reply)

                    # publisher disclosure
                    #
                    if authorization[u'disclose']:
                        disclose = True
                    elif (publish.topic.startswith(u"wamp.")
                          or publish.topic.startswith(u"crossbar.")):
                        disclose = True
                    else:
                        disclose = False

                    if disclose:
                        publisher = session._session_id
                        publisher_authid = session._authid
                        publisher_authrole = session._authrole
                    else:
                        publisher = None
                        publisher_authid = None
                        publisher_authrole = None

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

                    # persist event (this is done only once, regardless of the number of subscriptions
                    # the event matches on)
                    #
                    if store_event:
                        self._event_store.store_event(session._session_id,
                                                      publication,
                                                      publish.topic,
                                                      publish.args,
                                                      publish.kwargs)

                    # retain event on the topic
                    #
                    if retain_event:
                        retained_event = RetainedEvent(publish, publisher,
                                                       publisher_authid,
                                                       publisher_authrole)

                        observation = self._subscription_map.get_observation(
                            publish.topic)

                        if not observation:
                            # No observation, lets make a new one
                            observation = self._subscription_map.create_observation(
                                publish.topic, extra=SubscriptionExtra())
                        else:
                            # this can happen if event-history is
                            # enabled on the topic: the event-store
                            # creates an observation before any client
                            # could possible hit the code above
                            if observation.extra is None:
                                observation.extra = SubscriptionExtra()
                            elif not isinstance(observation.extra,
                                                SubscriptionExtra):
                                raise Exception(
                                    "incorrect 'extra' for '{}'".format(
                                        publish.topic))

                        if observation.extra.retained_events:
                            if not publish.eligible and not publish.exclude:
                                observation.extra.retained_events = [
                                    retained_event
                                ]
                            else:
                                observation.extra.retained_events.append(
                                    retained_event)
                        else:
                            observation.extra.retained_events = [
                                retained_event
                            ]

                    all_dl = []

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

                        self.log.debug(
                            'dispatching for subscription={subscription}',
                            subscription=subscription)

                        # persist event history, but check if it is persisted on the individual subscription!
                        #
                        if store_event and self._event_store in subscription.observers:
                            self._event_store.store_event_history(
                                publication, subscription.id)

                        # initial list of receivers are all subscribers on a subscription ..
                        #
                        receivers = subscription.observers
                        receivers = self._filter_publish_receivers(
                            receivers, publish)

                        # if receivers is non-empty, dispatch event ..
                        #
                        receivers_cnt = len(receivers) - (1 if self
                                                          in receivers else 0)
                        if receivers_cnt:

                            # 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

                            if publish.payload:
                                msg = message.Event(
                                    subscription.id,
                                    publication,
                                    payload=publish.payload,
                                    publisher=publisher,
                                    publisher_authid=publisher_authid,
                                    publisher_authrole=publisher_authrole,
                                    topic=topic,
                                    enc_algo=publish.enc_algo,
                                    enc_key=publish.enc_key,
                                    enc_serializer=publish.enc_serializer)
                            else:
                                msg = message.Event(
                                    subscription.id,
                                    publication,
                                    args=publish.args,
                                    kwargs=publish.kwargs,
                                    publisher=publisher,
                                    publisher_authid=publisher_authid,
                                    publisher_authrole=publisher_authrole,
                                    topic=topic)

                            chunk_size = self._options.event_dispatching_chunk_size

                            if chunk_size:
                                self.log.debug(
                                    'chunked dispatching to {receivers_size} with chunk_size={chunk_size}',
                                    receivers_size=len(receivers),
                                    chunk_size=chunk_size)

                                # a Deferred that fires when all chunks are done
                                all_d = txaio.create_future()
                                all_dl.append(all_d)

                                def _notify_some(receivers):
                                    for receiver in receivers[:chunk_size]:
                                        if (
                                                me_also or receiver != session
                                        ) and receiver != self._event_store:
                                            # the receiving subscriber session
                                            # might have no transport, or no
                                            # longer be joined
                                            if receiver._session_id and receiver._transport:
                                                self._router.send(
                                                    receiver, msg)
                                    receivers = receivers[chunk_size:]
                                    if len(receivers) > 0:
                                        # still more to do ..
                                        return txaio.call_later(
                                            0, _notify_some, receivers)
                                    else:
                                        # all done! resolve all_d, which represents all receivers
                                        # to a single subscription matching the event
                                        txaio.resolve(all_d, None)

                                _notify_some(list(receivers))
                            else:
                                self.log.debug(
                                    'unchunked dispatching to {receivers_size} receivers',
                                    receivers_size=len(receivers))

                                for receiver in receivers:
                                    if (me_also or receiver != session
                                        ) and receiver != self._event_store:
                                        # the receiving subscriber session
                                        # might have no transport, or no
                                        # longer be joined
                                        if receiver._session_id and receiver._transport:
                                            self._router.send(receiver, msg)

                    return txaio.gather(all_dl)

            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("Authorization failed", 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)
                        ])
                    self._router.send(session, reply)

            txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
    def _connect_once(self, reactor, transport):

        self.log.debug(
            '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 [
                                "wamp.close.normal",
                                "wamp.close.goodbye_and_out"
                        ]:
                            txaio.resolve(done, None)
                        else:
                            f = txaio.create_failure(
                                ApplicationError(details.reason,
                                                 details.message))
                            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("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
Example #48
0
    def _connect_once(self, reactor, transport_config):

        self.log.info(
            'connecting once using transport type "{transport_type}" '
            'over endpoint type "{endpoint_type}"',
            transport_type=transport_config['type'],
            endpoint_type=transport_config['endpoint']['type'],
        )

        done = txaio.create_future()

        # factory for ISession objects
        def create_session():
            cfg = ComponentConfig(self._realm, self._extra)
            try:
                session = self.session(cfg)
            except Exception:
                # couldn't instantiate session calls, which is fatal.
                # let the reconnection logic deal with that
                raise
            else:
                # hook up the listener to the parent so we can bubble
                # up events happning on the session onto the connection
                session._parent = self

                if self._entry_type == Component.TYPE_MAIN:

                    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}", err=err)
                            txaio.reject(done, err)

                        txaio.add_callbacks(d, main_success, main_error)

                    session.on('join', on_join)

                elif self._entry_type == Component.TYPE_SETUP:

                    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}", err=err)

                        txaio.add_callbacks(d, setup_success, setup_error)

                    session.on('join', on_join)

                else:
                    assert (False), 'logic error'

                # listen on leave events
                def on_leave(session, details):
                    self.log.debug("session on_leave: {details}",
                                   details=details)

                session.on('leave', on_leave)

                # listen on disconnect events
                def on_disconnect(session, was_clean):
                    self.log.debug("session on_disconnect: {was_clean}",
                                   was_clean=was_clean)

                    if was_clean:
                        # eg the session has left the realm, and the transport was properly
                        # shut down. successfully finish the connection
                        txaio.resolve(done, None)
                    else:
                        txaio.reject(
                            done, RuntimeError('transport closed uncleanly'))

                session.on('disconnect', on_disconnect)

                # return the fresh session object
                return session

        d = self._connect_transport(reactor, transport_config, create_session)

        def on_connect_sucess(proto):
            # FIXME: leave / cleanup proto when reactor stops?
            pass

        def on_connect_failure(err):
            # failed to establish a connection in the first place
            done.errback(err)

        txaio.add_callbacks(d, on_connect_sucess, on_connect_failure)

        return done
 def connect_error(fail):
     notify_f = notify_connect_error(fail)
     txaio.add_callbacks(notify_f, None, handle_connect_error)
Example #50
0
    def processSubscribe(self, session, subscribe):
        """
        Implements :func:`crossbar.router.interfaces.IBroker.processSubscribe`
        """
        if self._router.is_traced:
            if not subscribe.correlation_id:
                subscribe.correlation_id = self._router.new_correlation_id()
                subscribe.correlation_is_anchor = True
                subscribe.correlation_is_last = False
            if not subscribe.correlation_uri:
                subscribe.correlation_uri = subscribe.topic
            self._router._factory._worker._maybe_trace_rx_msg(session, subscribe)

        # check topic URI: for SUBSCRIBE, must be valid URI (either strict or loose), and all
        # URI components must be non-empty for normal subscriptions, may be empty for
        # wildcard subscriptions and must be non-empty for all but the last component for
        # prefix subscriptions
        #
        if self._option_uri_strict:
            if subscribe.match == u"wildcard":
                uri_is_valid = _URI_PAT_STRICT_EMPTY.match(subscribe.topic)
            elif subscribe.match == u"prefix":
                uri_is_valid = _URI_PAT_STRICT_LAST_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)
            elif subscribe.match == u"prefix":
                uri_is_valid = _URI_PAT_LOOSE_LAST_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)])
            reply.correlation_id = subscribe.correlation_id
            reply.correlation_uri = subscribe.topic
            reply.correlation_is_anchor = False
            reply.correlation_is_last = True
            self._router.send(session, reply)
            return

        # authorize SUBSCRIBE action
        #
        d = self._router.authorize(session, subscribe.topic, u'subscribe', options=subscribe.marshal_options())

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

            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, extra=SubscriptionExtra())

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

                # publish WAMP meta events, if we have a service session, but
                # not for the meta API itself!
                #
                if self._router._realm and \
                   self._router._realm.session and \
                   not subscription.uri.startswith(u'wamp.') and \
                   (is_first_subscriber or not was_already_subscribed):

                    has_follow_up_messages = True

                    def _publish():
                        service_session = self._router._realm.session
                        options = types.PublishOptions(
                            correlation_id=subscribe.correlation_id,
                            correlation_is_anchor=False,
                            correlation_is_last=False,
                        )
                        if is_first_subscriber:
                            subscription_details = {
                                u'id': subscription.id,
                                u'created': subscription.created,
                                u'uri': subscription.uri,
                                u'match': subscription.match,
                            }
                            service_session.publish(
                                u'wamp.subscription.on_create',
                                session._session_id,
                                subscription_details,
                                options=options,
                            )
                        if not was_already_subscribed:
                            options.correlation_is_last = True
                            service_session.publish(
                                u'wamp.subscription.on_subscribe',
                                session._session_id,
                                subscription.id,
                                options=options,
                            )
                    # we postpone actual sending of meta events until we return to this client session
                    self._reactor.callLater(0, _publish)

                else:
                    has_follow_up_messages = False

                # check for retained events
                #
                def _get_retained_event():

                    if subscription.extra.retained_events:
                        retained_events = list(subscription.extra.retained_events)
                        retained_events.reverse()

                        for retained_event in retained_events:
                            authorized = False

                            if not retained_event.publish.exclude and not retained_event.publish.eligible:
                                authorized = True
                            elif session._session_id in retained_event.publish.eligible and session._session_id not in retained_event.publish.exclude:
                                authorized = True

                            if authorized:
                                publication = util.id()

                                if retained_event.publish.payload:
                                    msg = message.Event(subscription.id,
                                                        publication,
                                                        payload=retained_event.publish.payload,
                                                        enc_algo=retained_event.publish.enc_algo,
                                                        enc_key=retained_event.publish.enc_key,
                                                        enc_serializer=retained_event.publish.enc_serializer,
                                                        publisher=retained_event.publisher,
                                                        publisher_authid=retained_event.publisher_authid,
                                                        publisher_authrole=retained_event.publisher_authrole,
                                                        retained=True)
                                else:
                                    msg = message.Event(subscription.id,
                                                        publication,
                                                        args=retained_event.publish.args,
                                                        kwargs=retained_event.publish.kwargs,
                                                        publisher=retained_event.publisher,
                                                        publisher_authid=retained_event.publisher_authid,
                                                        publisher_authrole=retained_event.publisher_authrole,
                                                        retained=True)

                                msg.correlation_id = subscribe.correlation_id
                                msg.correlation_uri = subscribe.topic
                                msg.correlation_is_anchor = False
                                msg.correlation_is_last = False

                                return [msg]
                    return []

                # acknowledge subscribe with subscription ID
                #
                replies = [message.Subscribed(subscribe.request, subscription.id)]
                replies[0].correlation_id = subscribe.correlation_id
                replies[0].correlation_uri = subscribe.topic
                replies[0].correlation_is_anchor = False
                replies[0].correlation_is_last = False
                if subscribe.get_retained:
                    replies.extend(_get_retained_event())

                replies[-1].correlation_is_last = not has_follow_up_messages

            # send out reply to subscribe requestor
            #
            [self._router.send(session, reply) for reply in replies]

        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("Authorization of 'subscribe' for '{uri}' failed",
                             uri=subscribe.topic, 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)]
            )
            reply.correlation_id = subscribe.correlation_id
            reply.correlation_uri = subscribe.topic
            reply.correlation_is_anchor = False
            reply.correlation_is_last = True
            self._router.send(session, reply)

        txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
        def attempt_connect(_):
            self._delay_f = None

            def handle_connect_error(fail):
                # FIXME - make txaio friendly
                # Can connect_f ever be in a cancelled state?
                # if txaio.using_asyncio and isinstance(fail.value, asyncio.CancelledError):
                #     unrecoverable_error = True

                self.log.debug('component failed: {error}',
                               error=txaio.failure_message(fail))
                self.log.debug('{tb}', tb=txaio.failure_format_traceback(fail))
                # If this is a "fatal error" that will never work,
                # we bail out now
                if isinstance(fail.value, ApplicationError):
                    self.log.error("{msg}", msg=fail.value.error_message())

                elif isinstance(fail.value, OSError):
                    # failed to connect entirely, like nobody
                    # listening etc.
                    self.log.info("Connection failed: {msg}",
                                  msg=txaio.failure_message(fail))

                elif self._is_ssl_error(fail.value):
                    # Quoting pyOpenSSL docs: "Whenever
                    # [SSL.Error] is raised directly, it has a
                    # list of error messages from the OpenSSL
                    # error queue, where each item is a tuple
                    # (lib, function, reason). Here lib, function
                    # and reason are all strings, describing where
                    # and what the problem is. See err(3) for more
                    # information."
                    # (and 'args' is a 1-tuple containing the above
                    # 3-tuple...)
                    ssl_lib, ssl_func, ssl_reason = fail.value.args[0][0]
                    self.log.error("TLS failure: {reason}", reason=ssl_reason)
                else:
                    self.log.error(
                        'Connection failed: {error}',
                        error=txaio.failure_message(fail),
                    )

                if self._is_fatal is None:
                    is_fatal = False
                else:
                    is_fatal = self._is_fatal(fail.value)
                if is_fatal:
                    self.log.info("Error was fatal; failing transport")
                    transport_candidate[0].failed()

                txaio.call_later(0, transport_check, None)
                return

            def notify_connect_error(fail):
                chain_f = txaio.create_future()
                # hmm, if connectfailure took a _Transport instead of
                # (or in addition to?) self it could .failed() the
                # transport and we could do away with the is_fatal
                # listener?
                handler_f = self.fire('connectfailure', self, fail.value)
                txaio.add_callbacks(handler_f,
                                    lambda _: txaio.reject(chain_f, fail),
                                    lambda _: txaio.reject(chain_f, fail))
                return chain_f

            def connect_error(fail):
                notify_f = notify_connect_error(fail)
                txaio.add_callbacks(notify_f, None, handle_connect_error)

            def session_done(x):
                txaio.resolve(self._done_f, None)

            connect_f = txaio.as_future(
                self._connect_once,
                loop,
                transport_candidate[0],
            )
            txaio.add_callbacks(connect_f, session_done, connect_error)
Example #52
0
def cb(value):
    print("Callback:", value)
    return value  # should always return input arg


def eb(fail):
    # fail will implement txaio.IFailedPromise
    print("Errback:", fail)
    # fail.printTraceback()
    return fail  # should always return input arg


f0 = txaio.create_future()
f1 = txaio.create_future()
txaio.add_callbacks(f0, cb, eb)
txaio.add_callbacks(f1, cb, eb)

# ...

txaio.reject(f0, RuntimeError("it failed"))
# or can just "txaio.reject(f0)" if inside an except: block
txaio.resolve(f1, "The answer is: 42")

if txaio.using_asyncio:
    # for twisted, we don't need to enter the event-loop for this
    # simple example (since all results are already available), but
    # you'd simply use reactor.run()/.stop() or task.react() as normal
    import asyncio
    asyncio.get_event_loop().run_until_complete(f1)
    def _start(self, loop=None):
        """
        This starts the Component, which means it will start connecting
        (and re-connecting) to its configured transports. A Component
        runs until it is "done", which means one of:

        - There was a "main" function defined, and it completed successfully;
        - Something called ``.leave()`` on our session, and we left successfully;
        - ``.stop()`` was called, and completed successfully;
        - none of our transports were able to connect successfully (failure);

        :returns: a Future/Deferred which will resolve (to ``None``) when we are
            "done" or with an error if something went wrong.
        """

        # we can only be "start()ed" once before we stop .. but that
        # doesn't have to be an error we can give back another future
        # that fires when our "real" _done_f is completed.
        if self._done_f is not None:
            d = txaio.create_future()

            def _cb(arg):
                txaio.resolve(d, arg)

            txaio.add_callbacks(self._done_f, _cb, _cb)
            return d

        # this future will be returned, and thus has the semantics
        # specified in the docstring.
        self._done_f = txaio.create_future()

        def _reset(arg):
            """
            if the _done_f future is resolved (good or bad), we want to set it
            to None in our class
            """
            self._done_f = None
            return arg

        txaio.add_callbacks(self._done_f, _reset, _reset)

        # Create a generator of transports that .can_reconnect()
        transport_gen = itertools.cycle(self._transports)

        # this is a 1-element list so we can set it from closures in
        # this function
        transport_candidate = [0]

        def error(fail):
            self._delay_f = None
            if self._stopping:
                # might be better to add framework-specific checks in
                # subclasses to see if this is CancelledError (for
                # Twisted) and whatever asyncio does .. but tracking
                # if we're in the shutdown path is fine too
                txaio.resolve(self._done_f, None)
            else:
                self.log.info("Internal error {msg}",
                              msg=txaio.failure_message(fail))
                self.log.debug("{tb}", tb=txaio.failure_format_traceback(fail))
                txaio.reject(self._done_f, fail)

        def attempt_connect(_):
            self._delay_f = None

            def handle_connect_error(fail):
                # FIXME - make txaio friendly
                # Can connect_f ever be in a cancelled state?
                # if txaio.using_asyncio and isinstance(fail.value, asyncio.CancelledError):
                #     unrecoverable_error = True

                self.log.debug('component failed: {error}',
                               error=txaio.failure_message(fail))
                self.log.debug('{tb}', tb=txaio.failure_format_traceback(fail))
                # If this is a "fatal error" that will never work,
                # we bail out now
                if isinstance(fail.value, ApplicationError):
                    self.log.error("{msg}", msg=fail.value.error_message())

                elif isinstance(fail.value, OSError):
                    # failed to connect entirely, like nobody
                    # listening etc.
                    self.log.info("Connection failed: {msg}",
                                  msg=txaio.failure_message(fail))

                elif self._is_ssl_error(fail.value):
                    # Quoting pyOpenSSL docs: "Whenever
                    # [SSL.Error] is raised directly, it has a
                    # list of error messages from the OpenSSL
                    # error queue, where each item is a tuple
                    # (lib, function, reason). Here lib, function
                    # and reason are all strings, describing where
                    # and what the problem is. See err(3) for more
                    # information."
                    # (and 'args' is a 1-tuple containing the above
                    # 3-tuple...)
                    ssl_lib, ssl_func, ssl_reason = fail.value.args[0][0]
                    self.log.error("TLS failure: {reason}", reason=ssl_reason)
                else:
                    self.log.error(
                        'Connection failed: {error}',
                        error=txaio.failure_message(fail),
                    )

                if self._is_fatal is None:
                    is_fatal = False
                else:
                    is_fatal = self._is_fatal(fail.value)
                if is_fatal:
                    self.log.info("Error was fatal; failing transport")
                    transport_candidate[0].failed()

                txaio.call_later(0, transport_check, None)
                return

            def notify_connect_error(fail):
                chain_f = txaio.create_future()
                # hmm, if connectfailure took a _Transport instead of
                # (or in addition to?) self it could .failed() the
                # transport and we could do away with the is_fatal
                # listener?
                handler_f = self.fire('connectfailure', self, fail.value)
                txaio.add_callbacks(handler_f,
                                    lambda _: txaio.reject(chain_f, fail),
                                    lambda _: txaio.reject(chain_f, fail))
                return chain_f

            def connect_error(fail):
                notify_f = notify_connect_error(fail)
                txaio.add_callbacks(notify_f, None, handle_connect_error)

            def session_done(x):
                txaio.resolve(self._done_f, None)

            connect_f = txaio.as_future(
                self._connect_once,
                loop,
                transport_candidate[0],
            )
            txaio.add_callbacks(connect_f, session_done, connect_error)

        def transport_check(_):
            self.log.debug('Entering re-connect loop')

            if not self._can_reconnect():
                err_msg = "Component failed: Exhausted all transport connect attempts"
                self.log.info(err_msg)
                try:
                    raise RuntimeError(err_msg)
                except RuntimeError as e:
                    txaio.reject(self._done_f, e)
                    return

            while True:

                transport = next(transport_gen)

                if transport.can_reconnect():
                    transport_candidate[0] = transport
                    break

            delay = transport.next_delay()
            self.log.debug(
                'trying transport {transport_idx} using connect delay {transport_delay}',
                transport_idx=transport.idx,
                transport_delay=delay,
            )

            self._delay_f = txaio.sleep(delay)
            txaio.add_callbacks(self._delay_f, attempt_connect, error)

        # issue our first event, then start the reconnect loop
        start_f = self.fire('start', loop, self)
        txaio.add_callbacks(start_f, transport_check, error)
        return self._done_f
Example #54
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
                self._authextra = authextra or {}

                self._authextra[
                    u'x_cb_node_id'] = self._router_factory._node_id
                self._authextra[u'x_cb_peer'] = str(self._transport.peer)
                self._authextra[u'x_cb_pid'] = os.getpid()

                roles = self._router.attach(self)

                msg = message.Welcome(self._session_id,
                                      roles,
                                      realm=realm,
                                      authid=authid,
                                      authrole=authrole,
                                      authmethod=authmethod,
                                      authprovider=authprovider,
                                      authextra=self._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
                    # it is possible this session has disconnected
                    # while onHello was taking place
                    if self._transport is None:
                        self.log.info(
                            "Client session disconnected during authentication",
                        )
                        return

                    if isinstance(res, types.Accept):
                        custom = {
                            u'x_cb_node_id': self._router_factory._node_id
                        }
                        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
                    # it is possible this session has disconnected
                    # while authentication was taking place
                    if self._transport is None:
                        self.log.info(
                            "Client session disconnected during authentication",
                        )
                        return

                    if isinstance(res, types.Accept):
                        custom = {
                            u'x_cb_node_id': self._router_factory._node_id
                        }
                        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 #55
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 for normal subscriptions, may be empty for
        # wildcard subscriptions and must be non-empty for all but the last component for
        # prefix subscriptions
        #
        if self._option_uri_strict:
            if subscribe.match == u"wildcard":
                uri_is_valid = _URI_PAT_STRICT_EMPTY.match(subscribe.topic)
            elif subscribe.match == u"prefix":
                uri_is_valid = _URI_PAT_STRICT_LAST_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)
            elif subscribe.match == u"prefix":
                uri_is_valid = _URI_PAT_LOOSE_LAST_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)
                ])
            self._router.send(session, reply)
            return

        # authorize SUBSCRIBE action
        #
        d = self._router.authorize(session, subscribe.topic, u'subscribe')

        def on_authorize_success(authorization):
            if not authorization[u'allow']:
                # error reply since session is not authorized to subscribe
                #
                replies = [
                    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,
                    extra=SubscriptionExtra())

                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 = {
                                u'id': subscription.id,
                                u'created': subscription.created,
                                u'uri': subscription.uri,
                                u'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)

                # check for retained events
                #
                def _get_retained_event():

                    if subscription.extra.retained_events:
                        retained_events = list(
                            subscription.extra.retained_events)
                        retained_events.reverse()

                        for publish in retained_events:
                            authorised = False

                            if not publish.exclude and not publish.eligible:
                                authorised = True
                            elif session._session_id in publish.eligible and session._session_id not in publish.exclude:
                                authorised = True

                            if authorised:
                                publication = util.id()

                                if publish.payload:
                                    msg = message.Event(
                                        subscription.id,
                                        publication,
                                        payload=publish.payload,
                                        retained=True,
                                        enc_algo=publish.enc_algo,
                                        enc_key=publish.enc_key,
                                        enc_serializer=publish.enc_serializer)
                                else:
                                    msg = message.Event(subscription.id,
                                                        publication,
                                                        args=publish.args,
                                                        kwargs=publish.kwargs,
                                                        retained=True)

                                return [msg]
                    return []

                # acknowledge subscribe with subscription ID
                #
                replies = [
                    message.Subscribed(subscribe.request, subscription.id)
                ]
                if subscribe.get_retained:
                    replies.extend(_get_retained_event())

            # send out reply to subscribe requestor
            #
            [self._router.send(session, reply) for reply in replies]

        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("Authorization failed", 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)
                ])
            self._router.send(session, reply)

        txaio.add_callbacks(d, on_authorize_success, on_authorize_error)