From 3c452dabf3536a3ee4148e4e6438d4a2393c5a13 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Thu, 19 Jun 2014 23:14:26 -0700 Subject: [PATCH] Add message translation layer than can handle "me" as an ACL. --- api.py | 20 +++++++------- channel.py | 3 ++- lib/models.py | 72 +++++++++++++++++++++++++++++++++++++++++--------- static/test.js | 30 +++++++++++++++++++++ 4 files changed, 102 insertions(+), 23 deletions(-) diff --git a/api.py b/api.py index 2f36a98..2c103ed 100644 --- a/api.py +++ b/api.py @@ -63,12 +63,13 @@ def Pin(google_user, client, client_address, instance_id, args): sender_message_id = args['sender_message_id'] try: - pin = models.Subject.FindOrCreate(subject).Pin( + pin = models.Subject.FindOrCreate(subject, client).Pin( message, models.Client.profile.get_value_for_datastore(client), sender_message_id, client_address, - instance) + instance, + subject) except models.DuplicateMessage as e: logging.warning('Duplicate pin: %s', sender_message_id) return { @@ -93,11 +94,12 @@ def SendMessage(google_user, client, client_address, instance_id, args): sender_message_id = args['sender_message_id'] try: - msg = models.Subject.FindOrCreate(subject).SendMessage( + msg = models.Subject.FindOrCreate(subject, client).SendMessage( message, models.Client.profile.get_value_for_datastore(client), sender_message_id, - client_address) + client_address, + subject) except models.DuplicateMessage as e: logging.warning('Duplicate message: %s', sender_message_id) return { @@ -118,7 +120,7 @@ def SendMessage(google_user, client, client_address, instance_id, args): def Subscribe(google_user, client, client_address, instance_id, args): instance = models.Instance.FromID(instance_id) - subject = models.Subject.FindOrCreate(args['subject']) + subject = models.Subject.FindOrCreate(args['subject'], client) messages = args.get('messages', 0) last_id = args.get('last_id', None) @@ -141,7 +143,7 @@ def Subscribe(google_user, client, client_address, instance_id, args): return { 'result': 'ok', 'events': models.Subscription.FindOrCreate( - subject, client, instance, messages, last_id), + subject, client, instance, args['subject'], messages, last_id), } @@ -151,7 +153,7 @@ def Unpin(google_user, client, client_address, instance_id, args): sender_message_id = args['sender_message_id'] try: - models.Subject.FindOrCreate(subject).Unpin( + models.Subject.FindOrCreate(subject, client).Unpin( models.Client.profile.get_value_for_datastore(client), sender_message_id, instance.key()) @@ -168,8 +170,8 @@ def Unpin(google_user, client, client_address, instance_id, args): def Unsubscribe(google_user, client, client_address, instance_id, args): instance = models.Instance.FromID(instance_id) - subject = models.Subject.FindOrCreate(args['subject']) - models.Subscription.Remove(subject, instance) + subject = models.Subject.FindOrCreate(args['subject'], client) + models.Subscription.Remove(subject, instance, args['subject']) return {} diff --git a/channel.py b/channel.py index b488b0a..ebe36d8 100644 --- a/channel.py +++ b/channel.py @@ -35,7 +35,8 @@ class OnChannelConnect(webapp2.RequestHandler): message = { 'event_type': 'close', } - channel.send_message(instance_id, json.dumps(msg, default=utils.EncodeJSON)) + channel.send_message( + instance_id, json.dumps(message, default=utils.EncodeJSON)) return instance.active = True instance.put() diff --git a/lib/models.py b/lib/models.py index e395964..1f48bb3 100644 --- a/lib/models.py +++ b/lib/models.py @@ -130,10 +130,12 @@ class Subject(db.Model): return hashobj.hexdigest() @classmethod - def FindOrCreate(cls, subject): + def FindOrCreate(cls, subject, client): if 'readable_only_by' in subject: if subject['readable_only_by'] == 'admin': readable_only_by = Profile.ADMIN_KEY + elif subject['readable_only_by'] == 'me': + readable_only_by = Client.profile.get_value_for_datastore(client) else: readable_only_by = db.Key(subject['readable_only_by']) else: @@ -142,6 +144,8 @@ class Subject(db.Model): if 'writable_only_by' in subject: if subject['writable_only_by'] == 'admin': writable_only_by = Profile.ADMIN_KEY + elif subject['writable_only_by'] == 'me': + writable_only_by = Client.profile.get_value_for_datastore(client) else: writable_only_by = db.Key(subject['writable_only_by']) else: @@ -238,14 +242,22 @@ class Subject(db.Model): readable_only_by != reader): raise AccessDenied - def SendMessage(self, message, sender, sender_message_id, sender_address): + def SendMessage( + self, message, sender, sender_message_id, sender_address, request): self.VerifyWritable(sender) - obj, subscriptions = self.PutMessage( - message, sender, sender_message_id, sender_address) + readable_only_by_me = (request.get('readable_only_by') == 'me') + writable_only_by_me = (request.get('writable_only_by') == 'me') + try: + obj, subscriptions = self.PutMessage( + message, sender, sender_message_id, sender_address) + except DuplicateMessage as e: + e.original = self.TranslateEvent( + e.original, readable_only_by_me, writable_only_by_me) + raise e event = obj.ToEvent() for subscription in subscriptions: subscription.SendMessage(event) - return event + return self.TranslateEvent(event, readable_only_by_me, writable_only_by_me) @db.transactional() def PutPin(self, message, sender, sender_message_id, @@ -273,14 +285,22 @@ class Subject(db.Model): return (obj, list(Subscription.all().ancestor(self))) - def Pin(self, message, sender, sender_message_id, sender_address, instance): + def Pin(self, message, sender, sender_message_id, sender_address, instance, + request): self.VerifyWritable(sender) - obj, subscriptions = self.PutPin( - message, sender, sender_message_id, instance, sender_address) + readable_only_by_me = (request.get('readable_only_by') == 'me') + writable_only_by_me = (request.get('writable_only_by') == 'me') + try: + obj, subscriptions = self.PutPin( + message, sender, sender_message_id, instance, sender_address) + except DuplicateMessage as e: + e.original = self.TranslateEvent( + e.original, readable_only_by_me, writable_only_by_me) + raise e event = obj.ToEvent() for subscription in subscriptions: subscription.SendMessage(event) - return event + return self.TranslateEvent(event, readable_only_by_me, writable_only_by_me) @db.transactional() def RemovePin(self, sender, sender_message_id, instance_key): @@ -323,6 +343,19 @@ class Subject(db.Model): ret['writable_only_by'] = str(writable_only_by) return ret + @classmethod + def TranslateEvent(cls, event, readable_only_by_me, writable_only_by_me): + if readable_only_by_me: + event['subject']['readable_only_by'] = 'me' + if writable_only_by_me: + event['subject']['writable_only_by'] = 'me' + return event + + @classmethod + def TranslateEvents(cls, events, readable_only_by_me, writable_only_by_me): + return [cls.TranslateEvent(event, readable_only_by_me, writable_only_by_me) + for event in events] + @db.transactional() def GetEvents(self, messages, last_id): events = [m.ToEvent() for m in self.GetPins()] @@ -337,26 +370,39 @@ class Subscription(db.Model): # parent=Subject instance = db.ReferenceProperty(reference_class=Instance, required=True) + readable_only_by_me = db.BooleanProperty(required=True, default=False) + writable_only_by_me = db.BooleanProperty(required=True, default=False) @classmethod @db.transactional() - def FindOrCreate(cls, subject, client, instance, messages=0, last_id=None): + def FindOrCreate(cls, subject, client, instance, request, + messages=0, last_id=None): + readable_only_by_me = (request.get('readable_only_by') == 'me') + writable_only_by_me = (request.get('writable_only_by') == 'me') subscriptions = ( cls.all(keys_only=True) .ancestor(subject) .filter('instance =', instance) + .filter('readable_only_by_me =', readable_only_by_me) + .filter('writable_only_by_me =', writable_only_by_me) .fetch(1)) if not subscriptions: cls(parent=subject, instance=instance).put() - return subject.GetEvents(messages, last_id) + return subject.TranslateEvents( + subject.GetEvents(messages, last_id), + readable_only_by_me, writable_only_by_me) @classmethod @db.transactional() - def Remove(cls, subject, instance): + def Remove(cls, subject, instance, request): + readable_only_by_me = (request.get('readable_only_by') == 'me') + writable_only_by_me = (request.get('writable_only_by') == 'me') subscriptions = ( cls.all() .ancestor(subject) - .filter('instance =', instance)) + .filter('instance =', instance) + .filter('readable_only_by_me =', readable_only_by_me) + .filter('writable_only_by_me =', writable_only_by_me)) for subscription in subscriptions: subscription.delete() diff --git a/static/test.js b/static/test.js index 314875c..ba493a1 100644 --- a/static/test.js +++ b/static/test.js @@ -470,6 +470,36 @@ asyncTest('sendMessage ACL', function() { }); }); +asyncTest('"me" ACL', function() { + expect(7); + + var subject = { + 'name': randstring(), + 'readable_only_by': 'me', + 'writable_only_by': 'me' + }; + var message = randstring(); + + var callbacks = { + 'onMessage': function(e) { + equal(e['subject']['name'], subject['name'], 'subject matches'); + equal(e['subject']['readable_only_by'], 'me', 'readable_only_by matches'); + equal(e['subject']['writable_only_by'], 'me', 'writable_only_by matches'); + equal(e['message'], message, 'message matches'); + cosmo.shutdown(); + start(); + } + }; + + var cosmo = new Cosmopolite(callbacks, null, randstring()); + cosmo.sendMessage(subject, message).then(function(msg) { + equal(msg['subject']['name'], subject['name'], 'subject matches'); + equal(msg['subject']['readable_only_by'], 'me', 'readable_only_by matches'); + equal(msg['subject']['writable_only_by'], 'me', 'writable_only_by matches'); + }); + cosmo.subscribe(subject, -1); +}); + asyncTest('pin/unpin', function() { expect(5);