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
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." 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 REGISTER action # d = self._router.authorize(session, register.procedure, u'register') 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, 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 = { 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(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)
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)
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)
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)
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("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)
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)
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)