Example #1
0
def _mqtt_topicfilter_to_wamp(topic):
    """
    Convert a MQTT topic as used in MQTT Subscribe (and hence ptoentially containing
    special characters "+" and "#") to a WAMP URI and a match policy.
    """
    if type(topic) != six.text_type:
        raise TypeError('invalid type "{}" for MQTT topic filter'.format(type(topic)))

    if u'+' in topic:
        # check topic filter containing single-level wildcard character

        # this is a restriction following from WAMP! we cannot have both
        # wildcard and prefix matching combined.
        if u'#' in topic:
            raise TypeError('MQTT topic filter "{}" contains both single-level and multi-level wildcards, and this cannot be mapped to WAMP'.format(topic))

        for c in topic.split(u'/'):
            if c != u'+' and u'+' in c:
                raise TypeError('invalid MQTT filter "{}": single-level wildcard characters must stand on their own in components'.format(topic))

        _match = u'wildcard'
        _topic = topic.replace(u'+', u'')

    elif u'#' in topic:
        # check topic filter containing multi-level wildcard character
        # there can be only one occurence, and it must be at the end
        if topic.find('#') != len(topic) - 1:
            raise TypeError('invalid MQTT topic filter "{}": # multi-level wildcard character must only appear as last character'.format(topic))
        if len(topic) > 1:
            if topic[-2] != u'/':
                raise TypeError('invalid MQTT topic filter "{}": # multi-level wildcard character must either appear solely, or be preceded by a / topic separator character'.format(topic))
        _match = u'prefix'
        _topic = topic[:-1]

    else:
        _match = u'exact'
        _topic = topic[:]

    # MQTT spec 4.7.1.1: "The use of the topic level separator is significant when either
    # of the two wildcard characters is encountered in the Topic Filters specified by subscribing Clients."
    #
    # FIXME: However, we still cannot leave the "/" character untouched and uninterpreted
    # when no "+" or "#" was encountered
    if True or _match != u'exact':
        # replace MQTT level separator "/" with WAMP level separator "."
        _topic = u'.'.join(_topic.split(u'/'))

    if (_match == u'exact' and not _URI_PAT_LOOSE_NON_EMPTY.match(_topic)) or \
       (_match == u'prefix' and not _URI_PAT_LOOSE_LAST_EMPTY.match(_topic)) or \
       (_match == u'wildcard' and not _URI_PAT_LOOSE_EMPTY.match(_topic)):
            raise TypeError('invalid WAMP URI "{}" (match="{}") after conversion from MQTT topic filter "{}"'.format(_topic, _match, topic))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        txaio.add_callbacks(d, on_authorize_success, on_authorize_error)
Example #4
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)
Example #5
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 #6
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)
            elif register.match == u"prefix":
                uri_is_valid = _URI_PAT_STRICT_LAST_EMPTY.match(
                    register.procedure)
            elif register.match == u"exact":
                uri_is_valid = _URI_PAT_STRICT_NON_EMPTY.match(
                    register.procedure)
            else:
                # should not arrive here
                raise Exception("logic error")
        else:
            if register.match == u"wildcard":
                uri_is_valid = _URI_PAT_LOOSE_EMPTY.match(register.procedure)
            elif register.match == u"prefix":
                uri_is_valid = _URI_PAT_LOOSE_LAST_EMPTY.match(
                    register.procedure)
            elif register.match == u"exact":
                uri_is_valid = _URI_PAT_LOOSE_NON_EMPTY.match(
                    register.procedure)
            else:
                # should not arrive here
                raise Exception("logic error")

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

        # disallow registration of procedures starting with "wamp." 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 = register.procedure.startswith(u"wamp.")
            if is_restricted:
                reply = message.Error(
                    message.Register.MESSAGE_TYPE, register.request,
                    ApplicationError.INVALID_URI, [
                        u"register for restricted procedure URI '{0}')".format(
                            register.procedure)
                    ])
                self._router.send(session, reply)
                return

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

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

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

        # authorize REGISTER action
        #
        d = self._router.authorize(session,
                                   register.procedure,
                                   u'register',
                                   options=register.marshal_options())

        def on_authorize_success(authorization):
            if not authorization[u'allow']:
                # 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_callee_extra = RegistrationCalleeExtra(
                    register.concurrency)
                registration, was_already_registered, is_first_callee = self._registration_map.add_observer(
                    session, register.procedure, register.match,
                    registration_extra, registration_callee_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 = {
                                u'id': registration.id,
                                u'created': registration.created,
                                u'uri': registration.uri,
                                u'match': registration.match,
                                u'invoke': registration.extra.invoke,
                            }
                            service_session.publish(
                                u'wamp.registration.on_create',
                                session._session_id, registration_details)
                        if not was_already_registered:
                            service_session.publish(
                                u'wamp.registration.on_register',
                                session._session_id, registration.id)

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

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

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

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

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

        # authorize action
        #
        d = self._as_future(self._router.authorize, session, subscribe.topic, IRouter.ACTION_SUBSCRIBE)

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

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

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

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

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

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

        def on_authorize_error(err):
            # the call to authorize the action _itself_ failed (note this is different from the
            # call to authorize succeed, but the authorization being denied)
            #
            reply = message.Error(message.Subscribe.MESSAGE_TYPE, subscribe.request, ApplicationError.AUTHORIZATION_FAILED, ["failed to authorize session for subscribing to topic URI '{0}': {1}".format(subscribe.topic, err.value)])
            session._transport.send(reply)

        self._add_future_callbacks(d, on_authorize_success, on_authorize_error)
Example #8
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

        # 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)
Example #9
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

                    exclude_authid = None
                    if subscribe.forward_for:
                        exclude_authid = [ff['authid'] for ff in subscribe.forward_for]

                    def _publish():
                        service_session = self._router._realm.session

                        if exclude_authid or self._router.is_traced:
                            options = types.PublishOptions(
                                correlation_id=subscribe.correlation_id,
                                correlation_is_anchor=False,
                                correlation_is_last=False,
                                exclude_authid=exclude_authid,
                            )
                        else:
                            options = None

                        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:
                            if options:
                                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)
Example #10
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)