Search code examples
pythonwebsockettwistedautobahn

How to limit Autobahn python subscriptions on a per session basis


I am using autobahnpython with twisted (wamp) on server side and autobahnjs in browser. Is there a straight-forward way to allow/restrict subscriptions on a per session basis? For example, a client should not be able to subscribe to topics relavant to other users.

While I am NOT using crossbar.io, I tried using the Python code shown in the 'Example' section at the end of this page http://crossbar.io/docs/Authorization/ where a RPC call is first used to give authorization to a client. Of course, I am using my own authorization logic. Once this authorization is successful, I'd like to give the client privileges to subscribe to topics related only to this client, like 'com.example.user_id'. My issue is that even if auth passes, however, I have not found a way to limit subscription requests in the ApplicationSession class which is where the authorization takes place. How can I prevent a client who authorizes with user_id=user_a from subscribing to 'com.example.user_b'?


Solution

  • You can authorize by creating your own router. To do that, subclass Router() and override (at a minumum) the authorize() method:

    def authorize(self, session, uri, action):
        return True
    

    This method is pretty simple, if you return a True then the session is authorized to do whatever it is attempting. You could make a rule that all subscriptions must start with 'com.example.USER_ID', so, your python code would split the uri, take the third field, and compare it to the current session id, returning True if they match, false otherwise. This is where things get a little weird though. I have code that does a similar thing, here is my authorize() method:

    @inlineCallbacks
    def authorize(self, session, uri, action):
        authid = session._authid
        if authid is None:
            authid = 1
        log.msg("AuthorizeRouter.authorize: {} {} {} {} {}".format(authid,
            session._session_id, uri, IRouter.ACTION_TO_STRING[action], action))
        if authid != 1:
            rv = yield self.check_permission(authid, uri, IRouter.ACTION_TO_STRING[action])
        else:
            rv = yield True
    
        log.msg("AuthorizeRouter.authorize: rv is {}".format(rv))
    
        if not uri.startswith(self.svar['topic_base']):
            self.sessiondb.activity(session._session_id, uri, IRouter.ACTION_TO_STRING[action], rv)
    
        returnValue(rv)
    
        return
    

    Note that I dive into the session to get the _authid, which is bad karma (I think) because I should not be looking at these private variables. I don't know where else to get it, though.

    Also, of note, this goes hand in hand with Authentication. In my implementation, the _authid is the authenticated user id, which is similar to a unix user id (positive unique integer). I am pretty sure this can be anything, like a string, so you should be ok with your 'user_b' as the _auth_id if you wish.

    -g