Once upon a time, we used to have mainframes where everything was done in one place and the stuff user's could work with was delivered in ASCII to a green-screen terminal, and the only thing going down the wire was what the user was allowed to see.

Then came Client-Server architecture, and all of a sudden, client applications were loading all sorts of data to satisfy local business logic over what was often an unencrypted albeit local connection. Not a good time in the security world.

The prevalent model is now a combination of these methods with some encryption thrown in, there's a degree of processing happening in client-side applications which requires some server-side data, but in mitigation the connection between the client and server is encrypted. This doesn't however stop the intended USER from seeing data transferred to the client that was only intended for processing rather than viewing. You often need to use data to generate a page that you don't actually want the user to have access to.

Whereas we're used to things like PHP generating a page on the server then throwing it down to the client pre-processed, more and more AJAX type code is loading actual data on-the-fly to provide real-time in-page updates. The question is, how much attention is being paid to 'what' is being loaded in real-time?

For Example ...

Let's say as part of an application, you have some faces on the screen that display in a certain colour based on the mood of the server. If the mood of the server changes, it's very easy to broadcast the new mood to all clients and activate it with some code like this;

var mood = null;
function setMood(new_mood) {
    if(new_mood != mood) {
        $('.face').removeClass('mood-'+mood);
        $('.face').addClass('mood-'+new_mood);
        mood = new_mood;
    };
};

All is well, you can update all clients with one simple broadcast and no page reloads. Sounds good. But, what if only half of your clients are actually authorized to know the mood of the server? Would it surprise you to find that on occasion you see something like this;

var mood = null;
function setMood(new_mood) {
    if(!authorized) return;
    if(new_mood != mood) {
...

Obviously this is wrong as the client is getting sent information they're not authorised to have, it's just not being displayed on the screen. Trouble is that there are a number of potential performance and complexity implications to addressing the issue.

  • The server needs to track who is allowed to see what and maintain a broadcast 'channel' for each view, such that when you send out the mood, instead of broadcasting, it does (n) point-to-point transmissions of the data.
  • For large numbers of subscribers, maintaining a large number of such channels may not be practical, in which case you may want to broadcast an anonymous mood updated message, then allow each client that's authorized re-request the current mood. Downside being a large number of update requests against the server, and all at the same time, which is effectively a traffic storm generator in-waiting.

Sounds like a rock and a hard-place, so what's the solution?

Service level subscriptions ...

Ultimately a broadcast service will always resolve to an iterative point-to-point transmission of some kind, the real question is how much work it poses for the programmer to get it working and maintain it, and how much loading it puts on the server. The answer for the end-user programmer usually presents itself as 'something someone else has thought about a lot and designed a competent solution to'.

The first thing we need to do is make sure we're using a suitable framework, in my case it is Crossbar.io, so this will be a crossbar example. The solution has a number of parts .. first we need a section in our config.json that tells the system we have a new role and how we control who can use that role. It will look like this;

{
   "controller": {},
   "workers": [{
         "type": "router",
         "realms": [{
               "name": "myrealm",
               "roles": [{
                  "name":"client", 
                  "authorizer":"system.security.authorize"
               }]
          ...

Specifically we are defining a role called client which will be allocated when a users is authenticated with crossbar. Typically in this case will will associate it with all clients. Next we will need to define the authorizer, so a client who logs in with the client role will have the ability to try to subscribe to any channel, but will be limited by the system.security.authorize code - which will look something like this;

@wamp.register(u'system.security.authorize')
    def security_authorize(self,session, uri, action):
        authid = session.get('authid',None)
        if not authid: return False    # not a valid user!
        record = db.acl.find_one({'authid':authid,'channel':uri})
        return bool(record)

So the database needs to contain a record with entries (user,channel) for everyone allowed to subscribe to a given channel. So for example we could create entries for selected users with channel ('app.update.mood'), then tell all users to try to subscribe to this channel. Those with entries in the database would succeed, everyone else would fail. If we then subsequently broadcast mood changes to this channel, those messages will only be sent to subscribed clients and of course on successful subscription, the client will add the first snippet on the page as the message handler against that channel.

So on the server we send with;

def sendAll(self,mood,payload=None):
    message = { 'mood':mood }
    if payload: message = dict(message,**payload)
    yield self.server.publish(u'app.update.mood',message)

And on the client we catch with;

function subscribe() {
    function success(data) { debug('subscribe-OK',data); }
    function failure(data) { debug('subscribe-FAIL',data)}
    session.subscribe('app.update.mood',function(data) {
        var msg = data[0];
        setMood(msg.mood);
    }).then(success,failure);
};

If you're using a framework that does not support selective Registration / Subscription with custom authorisers, I would be very interested to hear how you solve this particular class of problem?