2014-03-25 13:43:11 -07:00
|
|
|
# Copyright 2014, Ian Gulliver
|
|
|
|
|
#
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
#
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
#
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
2014-05-14 23:28:56 +03:00
|
|
|
import logging
|
2014-03-25 13:43:11 -07:00
|
|
|
import webapp2
|
|
|
|
|
|
|
|
|
|
from google.appengine.api import channel
|
|
|
|
|
from google.appengine.ext import db
|
|
|
|
|
|
|
|
|
|
from cosmopolite.lib import auth
|
|
|
|
|
from cosmopolite.lib import models
|
|
|
|
|
from cosmopolite.lib import security
|
|
|
|
|
from cosmopolite.lib import session
|
|
|
|
|
from cosmopolite.lib import utils
|
|
|
|
|
|
|
|
|
|
import config
|
|
|
|
|
|
|
|
|
|
|
2014-06-08 22:04:53 -07:00
|
|
|
def CreateChannel(google_user, client, client_address, instance_id, args):
|
2014-05-23 15:00:28 -07:00
|
|
|
models.Instance.FindOrCreate(instance_id)
|
2014-05-23 11:23:30 -07:00
|
|
|
|
2014-05-06 22:46:07 -07:00
|
|
|
token = channel.create_channel(
|
2014-05-23 14:29:11 -07:00
|
|
|
client_id=instance_id,
|
2014-05-06 22:46:07 -07:00
|
|
|
duration_minutes=config.CHANNEL_DURATION_SECONDS / 60)
|
2014-05-11 19:34:42 +03:00
|
|
|
events = []
|
2014-05-06 22:46:07 -07:00
|
|
|
if google_user:
|
2014-05-09 15:00:48 -07:00
|
|
|
events.append({
|
2014-05-31 23:25:15 -07:00
|
|
|
'event_type': 'login',
|
|
|
|
|
'google_user': google_user.email(),
|
2014-05-06 22:46:07 -07:00
|
|
|
})
|
|
|
|
|
else:
|
2014-05-09 15:00:48 -07:00
|
|
|
events.append({
|
|
|
|
|
'event_type': 'logout',
|
2014-05-06 22:46:07 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'token': token,
|
2014-05-09 15:00:48 -07:00
|
|
|
'events': events,
|
2014-05-06 22:46:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-06-08 22:04:53 -07:00
|
|
|
def Pin(google_user, client, client_address, instance_id, args):
|
2014-05-25 23:40:56 -07:00
|
|
|
instance = models.Instance.FromID(instance_id)
|
2014-05-28 14:44:13 -07:00
|
|
|
if not instance or not instance.active:
|
|
|
|
|
# Probably a race with the channel opening
|
|
|
|
|
return {
|
|
|
|
|
'result': 'retry',
|
|
|
|
|
}
|
2014-05-25 23:40:56 -07:00
|
|
|
|
|
|
|
|
subject = args['subject']
|
|
|
|
|
message = args['message']
|
|
|
|
|
sender_message_id = args['sender_message_id']
|
|
|
|
|
|
|
|
|
|
try:
|
2014-06-12 23:12:00 -07:00
|
|
|
pin = models.Subject.FindOrCreate(subject).Pin(
|
2014-05-31 23:25:15 -07:00
|
|
|
message,
|
|
|
|
|
models.Client.profile.get_value_for_datastore(client),
|
|
|
|
|
sender_message_id,
|
2014-06-08 22:04:53 -07:00
|
|
|
client_address,
|
2014-05-31 23:25:15 -07:00
|
|
|
instance)
|
2014-06-12 23:12:00 -07:00
|
|
|
except models.DuplicateMessage as e:
|
2014-05-27 14:32:15 -07:00
|
|
|
logging.warning('Duplicate pin: %s', sender_message_id)
|
2014-05-25 23:40:56 -07:00
|
|
|
return {
|
|
|
|
|
'result': 'duplicate_message',
|
2014-06-12 23:12:00 -07:00
|
|
|
'message': e.original,
|
2014-05-25 23:40:56 -07:00
|
|
|
}
|
|
|
|
|
except models.AccessDenied:
|
2014-05-27 14:32:15 -07:00
|
|
|
logging.warning('Pin access denied')
|
2014-05-25 23:40:56 -07:00
|
|
|
return {
|
|
|
|
|
'result': 'access_denied',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'result': 'ok',
|
2014-06-12 23:12:00 -07:00
|
|
|
'pin': pin,
|
2014-05-25 23:40:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-06-08 22:04:53 -07:00
|
|
|
def SendMessage(google_user, client, client_address, instance_id, args):
|
2014-05-06 22:46:07 -07:00
|
|
|
subject = args['subject']
|
|
|
|
|
message = args['message']
|
2014-05-16 23:07:38 +03:00
|
|
|
sender_message_id = args['sender_message_id']
|
2014-05-06 22:46:07 -07:00
|
|
|
|
2014-05-16 23:07:38 +03:00
|
|
|
try:
|
2014-06-12 23:12:00 -07:00
|
|
|
msg = models.Subject.FindOrCreate(subject).SendMessage(
|
2014-05-31 23:25:15 -07:00
|
|
|
message,
|
|
|
|
|
models.Client.profile.get_value_for_datastore(client),
|
2014-06-08 22:04:53 -07:00
|
|
|
sender_message_id,
|
|
|
|
|
client_address)
|
2014-06-12 23:12:00 -07:00
|
|
|
except models.DuplicateMessage as e:
|
2014-05-27 14:32:15 -07:00
|
|
|
logging.warning('Duplicate message: %s', sender_message_id)
|
2014-05-19 20:52:57 +03:00
|
|
|
return {
|
|
|
|
|
'result': 'duplicate_message',
|
2014-06-12 23:12:00 -07:00
|
|
|
'message': e.original,
|
2014-05-19 20:52:57 +03:00
|
|
|
}
|
|
|
|
|
except models.AccessDenied:
|
2014-05-27 14:32:15 -07:00
|
|
|
logging.warning('SendMessage access denied')
|
2014-05-19 20:52:57 +03:00
|
|
|
return {
|
|
|
|
|
'result': 'access_denied',
|
|
|
|
|
}
|
2014-05-06 22:46:07 -07:00
|
|
|
|
2014-05-19 20:52:57 +03:00
|
|
|
return {
|
|
|
|
|
'result': 'ok',
|
2014-06-12 23:12:00 -07:00
|
|
|
'message': msg,
|
2014-05-19 20:52:57 +03:00
|
|
|
}
|
2014-05-06 22:46:07 -07:00
|
|
|
|
|
|
|
|
|
2014-06-08 22:04:53 -07:00
|
|
|
def Subscribe(google_user, client, client_address, instance_id, args):
|
2014-05-23 14:29:11 -07:00
|
|
|
instance = models.Instance.FromID(instance_id)
|
2014-06-01 21:14:58 -07:00
|
|
|
subject = models.Subject.FindOrCreate(args['subject'])
|
|
|
|
|
messages = args.get('messages', 0)
|
|
|
|
|
last_id = args.get('last_id', None)
|
|
|
|
|
|
2014-06-02 21:53:51 -07:00
|
|
|
try:
|
|
|
|
|
subject.VerifyReadable(models.Client.profile.get_value_for_datastore(client))
|
|
|
|
|
except models.AccessDenied:
|
|
|
|
|
logging.warning('Subscribe access denied')
|
|
|
|
|
return {
|
|
|
|
|
'result': 'access_denied',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-05-23 14:29:11 -07:00
|
|
|
if not instance or not instance.active:
|
2014-05-23 15:32:31 -07:00
|
|
|
# Probably a race with the channel opening
|
|
|
|
|
return {
|
|
|
|
|
'result': 'retry',
|
2014-06-01 21:14:58 -07:00
|
|
|
'events': subject.GetEvents(messages, last_id),
|
2014-05-23 15:32:31 -07:00
|
|
|
}
|
2014-05-23 14:29:11 -07:00
|
|
|
|
2014-06-02 21:53:51 -07:00
|
|
|
return {
|
|
|
|
|
'result': 'ok',
|
|
|
|
|
'events': models.Subscription.FindOrCreate(
|
|
|
|
|
subject, client, instance, messages, last_id),
|
|
|
|
|
}
|
2014-05-19 20:52:57 +03:00
|
|
|
|
2014-05-06 13:38:40 -07:00
|
|
|
|
2014-06-08 22:04:53 -07:00
|
|
|
def Unpin(google_user, client, client_address, instance_id, args):
|
2014-05-25 23:40:56 -07:00
|
|
|
instance = models.Instance.FromID(instance_id)
|
|
|
|
|
subject = args['subject']
|
|
|
|
|
sender_message_id = args['sender_message_id']
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
models.Subject.FindOrCreate(subject).Unpin(
|
2014-05-31 23:25:15 -07:00
|
|
|
models.Client.profile.get_value_for_datastore(client),
|
|
|
|
|
sender_message_id,
|
|
|
|
|
instance.key())
|
2014-05-25 23:40:56 -07:00
|
|
|
except models.AccessDenied:
|
2014-05-27 14:32:15 -07:00
|
|
|
logging.warning('Pin access denied')
|
2014-05-25 23:40:56 -07:00
|
|
|
return {
|
|
|
|
|
'result': 'access_denied',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'result': 'ok',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-06-08 22:04:53 -07:00
|
|
|
def Unsubscribe(google_user, client, client_address, instance_id, args):
|
2014-05-23 14:29:11 -07:00
|
|
|
instance = models.Instance.FromID(instance_id)
|
2014-05-10 15:47:33 +02:00
|
|
|
subject = models.Subject.FindOrCreate(args['subject'])
|
2014-05-23 10:31:52 -07:00
|
|
|
models.Subscription.Remove(subject, instance)
|
2014-05-10 15:47:33 +02:00
|
|
|
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
2014-05-06 13:38:40 -07:00
|
|
|
class APIWrapper(webapp2.RequestHandler):
|
|
|
|
|
|
|
|
|
|
_COMMANDS = {
|
|
|
|
|
'createChannel': CreateChannel,
|
2014-05-25 23:40:56 -07:00
|
|
|
'pin': Pin,
|
2014-05-06 22:46:07 -07:00
|
|
|
'sendMessage': SendMessage,
|
|
|
|
|
'subscribe': Subscribe,
|
2014-05-25 23:40:56 -07:00
|
|
|
'unpin': Unpin,
|
2014-05-10 15:47:33 +02:00
|
|
|
'unsubscribe': Unsubscribe,
|
2014-05-06 13:38:40 -07:00
|
|
|
}
|
|
|
|
|
|
2014-03-25 13:43:11 -07:00
|
|
|
@utils.chaos_monkey
|
2014-05-06 13:38:40 -07:00
|
|
|
@utils.expects_json
|
2014-03-25 13:43:11 -07:00
|
|
|
@utils.returns_json
|
|
|
|
|
@utils.local_namespace
|
|
|
|
|
@security.google_user_xsrf_protection
|
|
|
|
|
@security.weak_security_checks
|
|
|
|
|
@session.session_required
|
|
|
|
|
def post(self):
|
2014-06-17 21:14:59 -07:00
|
|
|
profile_str = str(
|
|
|
|
|
models.Client.profile.get_value_for_datastore(self.client))
|
2014-05-06 13:47:57 -07:00
|
|
|
ret = {
|
|
|
|
|
'status': 'ok',
|
2014-06-17 21:14:59 -07:00
|
|
|
'profile': profile_str,
|
2014-05-06 13:47:57 -07:00
|
|
|
'responses': [],
|
2014-05-09 15:00:48 -07:00
|
|
|
'events': [],
|
2014-05-06 13:47:57 -07:00
|
|
|
}
|
2014-05-06 13:38:40 -07:00
|
|
|
for command in self.request_json['commands']:
|
2014-06-09 23:51:41 -07:00
|
|
|
logging.info('Command: %s', command)
|
2014-05-06 13:38:40 -07:00
|
|
|
callback = self._COMMANDS[command['command']]
|
2014-05-06 13:47:57 -07:00
|
|
|
result = callback(
|
|
|
|
|
self.verified_google_user,
|
|
|
|
|
self.client,
|
2014-06-08 22:04:53 -07:00
|
|
|
self.request.remote_addr,
|
2014-05-23 10:31:52 -07:00
|
|
|
self.request_json['instance_id'],
|
2014-05-06 13:47:57 -07:00
|
|
|
command.get('arguments', {}))
|
2014-05-09 15:00:48 -07:00
|
|
|
# Magic: if result contains "events", haul them up a level so the
|
2014-05-06 13:47:57 -07:00
|
|
|
# client can see them as a single stream.
|
2014-05-09 15:00:48 -07:00
|
|
|
ret['events'].extend(result.pop('events', []))
|
2014-05-06 13:47:57 -07:00
|
|
|
ret['responses'].append(result)
|
2014-05-06 13:38:40 -07:00
|
|
|
return ret
|
2014-03-25 13:43:11 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
app = webapp2.WSGIApplication([
|
2014-05-06 13:38:40 -07:00
|
|
|
(config.URL_PREFIX + '/api', APIWrapper),
|
2014-03-25 13:43:11 -07:00
|
|
|
])
|