From 3d4ed8bafc7a7c216603038a5fe0432a607835c6 Mon Sep 17 00:00:00 2001 From: meejah Date: Fri, 19 May 2017 15:02:08 -0600 Subject: [PATCH 1/2] add dynamic authorizer example --- examples/router/.crossbar/config.json | 30 +++++++ examples/twisted/wamp/rpc/authorize/README.md | 4 + .../twisted/wamp/rpc/authorize/__init__.py | 2 + .../twisted/wamp/rpc/authorize/authorizer.py | 28 +++++++ .../twisted/wamp/rpc/authorize/backend.py | 80 +++++++++++++++++++ 5 files changed, 144 insertions(+) create mode 100644 examples/twisted/wamp/rpc/authorize/README.md create mode 100644 examples/twisted/wamp/rpc/authorize/__init__.py create mode 100644 examples/twisted/wamp/rpc/authorize/authorizer.py create mode 100644 examples/twisted/wamp/rpc/authorize/backend.py diff --git a/examples/router/.crossbar/config.json b/examples/router/.crossbar/config.json index 776c9610..5dad536f 100644 --- a/examples/router/.crossbar/config.json +++ b/examples/router/.crossbar/config.json @@ -3,6 +3,9 @@ "workers": [ { "type": "router", + "options": { + "pythonpath": ["../../twisted/wamp/rpc/authorize/"] + }, "realms": [ { "name": "crossbardemo", @@ -55,6 +58,21 @@ "cache": true } ] + }, + { + "name": "approver", + "permissions": [ + { + "uri": "com.example.authorize", + "allow": { + "register": true + } + } + ] + }, + { + "name": "dynamic_authed", + "authorizer": "com.example.authorize" } ] } @@ -120,6 +138,10 @@ "secret": "p4ssw0rd", "role": "authenticated" }, + "bob": { + "secret": "p4ssw0rd", + "role": "dynamic_authed" + }, "salted": { "secret": "zFXAAAqW5nlonWfP6JLMq4KGLRYZAd8OSXWknEbckCQ=", "role": "authenticated", @@ -175,6 +197,14 @@ } } } + ], + "components": [ + { + "type": "class", + "classname": "authorizer.MyAuthorizer", + "realm": "crossbardemo", + "role": "approver" + } ] } ] diff --git a/examples/twisted/wamp/rpc/authorize/README.md b/examples/twisted/wamp/rpc/authorize/README.md new file mode 100644 index 00000000..992c7790 --- /dev/null +++ b/examples/twisted/wamp/rpc/authorize/README.md @@ -0,0 +1,4 @@ +This shows how to use dynamic authorizers + +Try changing the "if True" in "authorize.py" -- then the registration +should fail. \ No newline at end of file diff --git a/examples/twisted/wamp/rpc/authorize/__init__.py b/examples/twisted/wamp/rpc/authorize/__init__.py new file mode 100644 index 00000000..8f7f12e2 --- /dev/null +++ b/examples/twisted/wamp/rpc/authorize/__init__.py @@ -0,0 +1,2 @@ +# this is a module so we can include it via PYTHONPATH in the example +# router's config. diff --git a/examples/twisted/wamp/rpc/authorize/authorizer.py b/examples/twisted/wamp/rpc/authorize/authorizer.py new file mode 100644 index 00000000..f956ea84 --- /dev/null +++ b/examples/twisted/wamp/rpc/authorize/authorizer.py @@ -0,0 +1,28 @@ +from twisted.internet.defer import inlineCallbacks +from autobahn.twisted.wamp import ApplicationSession + + +class MyAuthorizer(ApplicationSession): + + @inlineCallbacks + def onJoin(self, details): + print("MyAuthorizer.onJoin({})".format(details)) + try: + yield self.register(self.authorize, u'com.example.authorize') + print("MyAuthorizer: authorizer registered") + except Exception as e: + print("MyAuthorizer: failed to register authorizer procedure ({})".format(e)) + raise + + def authorize(self, details, uri, action, options={}): + print("MyAuthorizer.authorize(uri='{}', action='{}')".format(uri, action)) + print("options:") + for k, v in options.items(): + print(" {}: {}".format(k, v)) + if False: + print("I allow everything.") + else: + if options.get(u"match", "") != u"exact": + print("only exact-match subscriptions allowed") + return False + return True diff --git a/examples/twisted/wamp/rpc/authorize/backend.py b/examples/twisted/wamp/rpc/authorize/backend.py new file mode 100644 index 00000000..6b13b00e --- /dev/null +++ b/examples/twisted/wamp/rpc/authorize/backend.py @@ -0,0 +1,80 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Crossbar.io Technologies GmbH +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +############################################################################### + +from os import environ +from twisted.internet import reactor +from twisted.internet.defer import inlineCallbacks + +from autobahn.twisted.wamp import Session, ApplicationRunner +from autobahn.wamp.types import SubscribeOptions, RegisterOptions +from autobahn.twisted.util import sleep + + +class Component(Session): + """ + An application component calling the different backend procedures. + """ + + @inlineCallbacks + def onJoin(self, details): + print("session attached {}".format(details)) + + def foo(*args, **kw): + print("foo(): {} {}".format(args, kw)) + return None + reg = yield self.register( + foo, u'example.foo', + options=RegisterOptions( + invoke=u'roundrobin', + ) + ) + print("registered example.foo: {}".format(reg)) + + def bar(*args, **kw): + print("bar(): {} {}".format(args, kw)) + return None + sub = yield self.subscribe( + bar, u"example.", + options=SubscribeOptions( + match=u"prefix", + ) + ) + print("subscribed: {}".format(sub)) + + +if __name__ == '__main__': + runner = ApplicationRunner( + environ.get("AUTOBAHN_DEMO_ROUTER", u"ws://127.0.0.1:8080/auth_ws"), + u"crossbardemo", + ) + + def make(config): + session = Component(config) + session.add_authenticator( + u"wampcra", authid=u'bob', secret=u'p4ssw0rd' + ) + return session + runner.run(make) From b6cb8027400651362e7fcb9b960de93f2c1669ee Mon Sep 17 00:00:00 2001 From: meejah Date: Fri, 19 May 2017 15:02:18 -0600 Subject: [PATCH 2/2] split out marshal_options() from marshal() --- autobahn/wamp/message.py | 80 +++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/autobahn/wamp/message.py b/autobahn/wamp/message.py index 7bbfaf25..8dea4140 100644 --- a/autobahn/wamp/message.py +++ b/autobahn/wamp/message.py @@ -1744,13 +1744,7 @@ class Publish(Message): return obj - def marshal(self): - """ - Marshal this object into a raw message for subsequent serialization to bytes. - - :returns: The serialized raw message. - :rtype: list - """ + def marshal_options(self): options = {} if self.acknowledge is not None: @@ -1780,6 +1774,19 @@ class Publish(Message): options[u'enc_key'] = self.enc_key if self.enc_serializer is not None: options[u'enc_serializer'] = self.enc_serializer + + return options + + def marshal(self): + """ + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: The serialized raw message. + :rtype: list + """ + options = self.marshal_options() + + if self.payload: return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.payload] else: if self.kwargs: @@ -1962,13 +1969,7 @@ class Subscribe(Message): return obj - def marshal(self): - """ - Marshal this object into a raw message for subsequent serialization to bytes. - - :returns: The serialized raw message. - :rtype: list - """ + def marshal_options(self): options = {} if self.match and self.match != Subscribe.MATCH_EXACT: @@ -1977,7 +1978,16 @@ class Subscribe(Message): if self.get_retained is not None: options[u'get_retained'] = self.get_retained - return [Subscribe.MESSAGE_TYPE, self.request, options, self.topic] + return options + + def marshal(self): + """ + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: The serialized raw message. + :rtype: list + """ + return [Subscribe.MESSAGE_TYPE, self.request, self.marshal_options(), self.topic] def __str__(self): """ @@ -2784,13 +2794,7 @@ class Call(Message): return obj - def marshal(self): - """ - Marshal this object into a raw message for subsequent serialization to bytes. - - :returns: The serialized raw message. - :rtype: list - """ + def marshal_options(self): options = {} if self.timeout is not None: @@ -2806,6 +2810,19 @@ class Call(Message): options[u'enc_key'] = self.enc_key if self.enc_serializer is not None: options[u'enc_serializer'] = self.enc_serializer + + return options + + def marshal(self): + """ + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: The serialized raw message. + :rtype: list + """ + options = self.marshal_options() + + if self.payload: return [Call.MESSAGE_TYPE, self.request, options, self.procedure, self.payload] else: if self.kwargs: @@ -3253,13 +3270,7 @@ class Register(Message): return obj - def marshal(self): - """ - Marshal this object into a raw message for subsequent serialization to bytes. - - :returns: The serialized raw message. - :rtype: list - """ + def marshal_options(self): options = {} if self.match and self.match != Register.MATCH_EXACT: @@ -3271,7 +3282,16 @@ class Register(Message): if self.concurrency: options[u'concurrency'] = self.concurrency - return [Register.MESSAGE_TYPE, self.request, options, self.procedure] + return options + + def marshal(self): + """ + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: The serialized raw message. + :rtype: list + """ + return [Register.MESSAGE_TYPE, self.request, self.marshal_options(), self.procedure] def __str__(self): """