2012-09-19 19:39:34 +02:00
|
|
|
from django.conf import settings
|
2012-08-28 18:44:51 +02:00
|
|
|
from django.contrib.auth import authenticate, login
|
|
|
|
from django.contrib.auth.decorators import login_required
|
|
|
|
from django.core.urlresolvers import reverse
|
2012-10-17 20:43:52 +02:00
|
|
|
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest, HttpResponseForbidden
|
2012-08-28 18:44:51 +02:00
|
|
|
from django.shortcuts import render_to_response
|
|
|
|
from django.template import RequestContext
|
2012-08-28 23:41:04 +02:00
|
|
|
from django.utils.timezone import utc
|
2012-09-29 00:49:34 +02:00
|
|
|
from django.core.exceptions import ValidationError
|
|
|
|
from django.contrib.auth.views import login as django_login_page
|
2012-10-10 22:53:24 +02:00
|
|
|
from zephyr.models import Message, UserProfile, Stream, Subscription, \
|
2012-10-15 23:07:33 +02:00
|
|
|
Recipient, get_display_recipient, get_huddle, Realm, UserMessage, \
|
2012-10-20 21:43:13 +02:00
|
|
|
do_add_subscription, do_remove_subscription, \
|
2012-10-19 21:37:37 +02:00
|
|
|
create_user, do_send_message, create_user_if_needed, \
|
2012-10-19 21:30:42 +02:00
|
|
|
create_stream_if_needed, PreregistrationUser, get_client
|
2012-09-29 00:49:34 +02:00
|
|
|
from zephyr.forms import RegistrationForm, HomepageForm, is_unique
|
2012-10-01 21:36:44 +02:00
|
|
|
from django.views.decorators.csrf import csrf_exempt
|
2012-08-28 18:44:51 +02:00
|
|
|
|
2012-08-28 22:56:21 +02:00
|
|
|
from zephyr.decorator import asynchronous
|
2012-09-29 01:38:03 +02:00
|
|
|
from zephyr.lib.query import last_n
|
2012-10-17 04:07:35 +02:00
|
|
|
from zephyr.lib.avatar import gravatar_hash
|
2012-08-28 22:56:21 +02:00
|
|
|
|
2012-09-28 22:47:05 +02:00
|
|
|
from confirmation.models import Confirmation
|
|
|
|
|
2012-08-28 18:44:51 +02:00
|
|
|
import datetime
|
|
|
|
import simplejson
|
2012-09-05 01:11:25 +02:00
|
|
|
import socket
|
2012-09-07 19:20:04 +02:00
|
|
|
import re
|
2012-10-04 20:27:49 +02:00
|
|
|
import urllib
|
2012-10-16 21:15:01 +02:00
|
|
|
import time
|
2012-10-17 23:10:34 +02:00
|
|
|
import requests
|
2012-10-16 21:15:01 +02:00
|
|
|
|
|
|
|
SERVER_GENERATION = int(time.time())
|
2012-08-28 18:44:51 +02:00
|
|
|
|
2012-09-05 23:38:20 +02:00
|
|
|
def require_post(view_func):
|
|
|
|
def _wrapped_view_func(request, *args, **kwargs):
|
|
|
|
if request.method != "POST":
|
|
|
|
return HttpResponseBadRequest('This form can only be submitted by POST.')
|
|
|
|
return view_func(request, *args, **kwargs)
|
|
|
|
return _wrapped_view_func
|
|
|
|
|
2012-10-01 21:36:44 +02:00
|
|
|
# api_key_required will add the authenticated user's user_profile to
|
|
|
|
# the view function's arguments list, since we have to look it up
|
|
|
|
# anyway.
|
2012-10-16 22:32:47 +02:00
|
|
|
def login_required_api_view(view_func):
|
|
|
|
@csrf_exempt
|
|
|
|
@require_post
|
2012-10-01 21:36:44 +02:00
|
|
|
def _wrapped_view_func(request, *args, **kwargs):
|
|
|
|
# Arguably @require_post should protect us from having to do
|
|
|
|
# this, but I don't want to count on us always getting the
|
|
|
|
# decorator ordering right.
|
2012-10-04 21:52:27 +02:00
|
|
|
try:
|
|
|
|
user_profile = UserProfile.objects.get(user__email=request.POST.get("email"))
|
|
|
|
except UserProfile.DoesNotExist:
|
|
|
|
return json_error("Invalid user")
|
2012-10-01 21:36:44 +02:00
|
|
|
if user_profile is None or request.POST.get("api-key") != user_profile.api_key:
|
|
|
|
return json_error('Invalid API user/key pair.')
|
|
|
|
return view_func(request, user_profile, *args, **kwargs)
|
|
|
|
return _wrapped_view_func
|
|
|
|
|
2012-10-16 22:10:48 +02:00
|
|
|
# Checks if the request is a POST request and that the user is logged
|
|
|
|
# in. If not, return an error (the @login_required behavior of
|
|
|
|
# redirecting to a login page doesn't make sense for json views)
|
|
|
|
def login_required_json_view(view_func):
|
|
|
|
def _wrapped_view_func(request, *args, **kwargs):
|
|
|
|
# Arguably @require_post should protect us from having to do
|
|
|
|
# this, but I don't want to count on us always getting the
|
|
|
|
# decorator ordering right.
|
|
|
|
if request.method != "POST":
|
|
|
|
return HttpResponseBadRequest('This form can only be submitted by POST.')
|
|
|
|
if not request.user.is_authenticated():
|
|
|
|
return json_error("Not logged in")
|
|
|
|
return view_func(request, *args, **kwargs)
|
|
|
|
return _wrapped_view_func
|
|
|
|
|
2012-09-18 16:22:37 +02:00
|
|
|
def json_response(res_type="success", msg="", data={}, status=200):
|
|
|
|
content = {"result":res_type, "msg":msg}
|
|
|
|
content.update(data)
|
|
|
|
return HttpResponse(content=simplejson.dumps(content),
|
2012-09-05 22:21:25 +02:00
|
|
|
mimetype='application/json', status=status)
|
|
|
|
|
2012-09-18 16:22:37 +02:00
|
|
|
def json_success(data={}):
|
|
|
|
return json_response(data=data)
|
2012-09-05 22:21:25 +02:00
|
|
|
|
2012-09-18 16:22:37 +02:00
|
|
|
def json_error(msg, data={}):
|
|
|
|
return json_response(res_type="error", msg=msg, data=data, status=400)
|
2012-09-05 22:21:25 +02:00
|
|
|
|
2012-10-10 23:01:28 +02:00
|
|
|
def get_stream(stream_name, realm):
|
|
|
|
stream = Stream.objects.filter(name__iexact=stream_name, realm=realm)
|
2012-10-10 22:53:24 +02:00
|
|
|
if stream:
|
|
|
|
return stream[0]
|
2012-10-02 17:53:14 +02:00
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2012-09-28 22:47:05 +02:00
|
|
|
@require_post
|
2012-10-16 21:42:40 +02:00
|
|
|
def accounts_register(request):
|
2012-09-28 22:47:05 +02:00
|
|
|
key = request.POST['key']
|
|
|
|
email = Confirmation.objects.get(confirmation_key=key).content_object.email
|
|
|
|
company_name = email.split('@')[-1]
|
2012-09-25 22:58:59 +02:00
|
|
|
|
2012-09-29 00:49:34 +02:00
|
|
|
try:
|
|
|
|
is_unique(email)
|
|
|
|
except ValidationError:
|
2012-10-04 20:27:49 +02:00
|
|
|
return HttpResponseRedirect(reverse('django.contrib.auth.views.login') + '?email=' + urllib.quote_plus(email))
|
2012-09-29 00:49:34 +02:00
|
|
|
|
2012-10-02 22:20:07 +02:00
|
|
|
if request.POST.get('from_confirmation'):
|
2012-09-28 22:47:05 +02:00
|
|
|
form = RegistrationForm()
|
2012-10-02 22:20:07 +02:00
|
|
|
else:
|
2012-08-28 18:44:51 +02:00
|
|
|
form = RegistrationForm(request.POST)
|
|
|
|
if form.is_valid():
|
2012-10-10 21:16:23 +02:00
|
|
|
password = form.cleaned_data['password']
|
2012-10-11 19:15:41 +02:00
|
|
|
full_name = form.cleaned_data['full_name']
|
|
|
|
short_name = email.split('@')[0]
|
|
|
|
domain = form.cleaned_data['domain']
|
2012-10-19 23:40:44 +02:00
|
|
|
(realm, _) = Realm.objects.get_or_create(domain=domain)
|
2012-10-11 16:57:47 +02:00
|
|
|
|
2012-09-21 00:26:59 +02:00
|
|
|
# FIXME: sanitize email addresses
|
2012-09-21 16:40:46 +02:00
|
|
|
create_user(email, password, realm, full_name, short_name)
|
2012-09-21 16:10:36 +02:00
|
|
|
login(request, authenticate(username=email, password=password))
|
2012-08-28 18:44:51 +02:00
|
|
|
return HttpResponseRedirect(reverse('zephyr.views.home'))
|
|
|
|
|
2012-10-15 22:52:08 +02:00
|
|
|
return render_to_response('zephyr/register.html',
|
|
|
|
{ 'form': form, 'company_name': company_name, 'email': email, 'key': key },
|
|
|
|
context_instance=RequestContext(request))
|
2012-08-28 18:44:51 +02:00
|
|
|
|
2012-09-29 00:49:34 +02:00
|
|
|
def login_page(request, **kwargs):
|
|
|
|
template_response = django_login_page(request, **kwargs)
|
|
|
|
try:
|
2012-10-11 19:15:41 +02:00
|
|
|
template_response.context_data['email'] = request.GET['email']
|
2012-09-29 00:49:34 +02:00
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
return template_response
|
|
|
|
|
2012-08-28 18:44:51 +02:00
|
|
|
def accounts_home(request):
|
2012-09-28 22:47:05 +02:00
|
|
|
if request.method == 'POST':
|
|
|
|
form = HomepageForm(request.POST)
|
|
|
|
if form.is_valid():
|
|
|
|
try:
|
2012-09-29 00:49:34 +02:00
|
|
|
email = form.cleaned_data['email']
|
|
|
|
user = PreregistrationUser.objects.get(email=email)
|
2012-09-28 22:47:05 +02:00
|
|
|
except PreregistrationUser.DoesNotExist:
|
|
|
|
user = PreregistrationUser()
|
2012-09-29 00:49:34 +02:00
|
|
|
user.email = email
|
2012-09-28 22:47:05 +02:00
|
|
|
user.save()
|
|
|
|
Confirmation.objects.send_confirmation(user, user.email)
|
|
|
|
return HttpResponseRedirect(reverse('send_confirm', kwargs={'email':user.email}))
|
2012-09-29 00:49:34 +02:00
|
|
|
try:
|
|
|
|
email = request.POST['email']
|
|
|
|
is_unique(email)
|
|
|
|
except ValidationError:
|
2012-10-10 22:30:51 +02:00
|
|
|
return HttpResponseRedirect(reverse('django.contrib.auth.views.login') + '?email=' + urllib.quote_plus(email))
|
2012-08-28 18:44:51 +02:00
|
|
|
return render_to_response('zephyr/accounts_home.html',
|
2012-09-04 23:21:30 +02:00
|
|
|
context_instance=RequestContext(request))
|
|
|
|
|
2012-08-28 18:44:51 +02:00
|
|
|
def home(request):
|
|
|
|
if not request.user.is_authenticated():
|
2012-10-15 22:31:06 +02:00
|
|
|
return HttpResponseRedirect(reverse(settings.NOT_LOGGED_IN_REDIRECT))
|
2012-09-07 17:04:41 +02:00
|
|
|
user_profile = UserProfile.objects.get(user=request.user)
|
2012-08-28 18:44:51 +02:00
|
|
|
|
2012-10-15 23:07:33 +02:00
|
|
|
num_messages = UserMessage.objects.filter(user_profile=user_profile).count()
|
2012-08-28 21:27:42 +02:00
|
|
|
|
2012-10-15 23:07:33 +02:00
|
|
|
if user_profile.pointer == -1 and num_messages > 0:
|
2012-10-20 02:43:25 +02:00
|
|
|
# Put the new user's pointer at the bottom
|
|
|
|
#
|
|
|
|
# This improves performance, because we limit backfilling of messages
|
|
|
|
# before the pointer. It's also likely that someone joining an
|
|
|
|
# organization is interested in recent messages more than the very
|
|
|
|
# first messages on the system.
|
|
|
|
|
|
|
|
max_id = (UserMessage.objects.filter(user_profile=user_profile)
|
|
|
|
.order_by('message')
|
|
|
|
.reverse()[0]).message_id
|
|
|
|
user_profile.pointer = max_id
|
2012-10-17 17:42:40 +02:00
|
|
|
user_profile.last_pointer_updater = request.session.session_key
|
2012-09-12 22:55:37 +02:00
|
|
|
|
2012-09-07 17:15:03 +02:00
|
|
|
# Populate personals autocomplete list based on everyone in your
|
|
|
|
# realm. Later we might want a 2-layer autocomplete, where we
|
|
|
|
# consider specially some sort of "buddy list" who e.g. you've
|
|
|
|
# talked to before, but for small organizations, the right list is
|
|
|
|
# everyone in your realm.
|
2012-10-12 17:26:04 +02:00
|
|
|
people = [{'email' : profile.user.email,
|
|
|
|
'full_name' : profile.full_name}
|
|
|
|
for profile in
|
2012-10-15 17:39:10 +02:00
|
|
|
UserProfile.objects.select_related().filter(realm=user_profile.realm) if
|
2012-09-07 17:26:58 +02:00
|
|
|
profile != user_profile]
|
2012-09-04 20:31:23 +02:00
|
|
|
|
2012-10-22 20:15:25 +02:00
|
|
|
subscriptions = Subscription.objects.select_related().filter(user_profile_id=user_profile, active=True)
|
2012-10-10 23:01:28 +02:00
|
|
|
streams = [get_display_recipient(sub.recipient) for sub in subscriptions
|
2012-10-10 22:57:21 +02:00
|
|
|
if sub.recipient.type == Recipient.STREAM]
|
2012-09-05 22:15:38 +02:00
|
|
|
|
2012-08-30 19:56:15 +02:00
|
|
|
return render_to_response('zephyr/index.html',
|
2012-09-26 19:44:21 +02:00
|
|
|
{'user_profile': user_profile,
|
2012-10-17 04:07:35 +02:00
|
|
|
'email_hash' : gravatar_hash(user_profile.user.email),
|
2012-10-10 23:53:00 +02:00
|
|
|
'people' : people,
|
|
|
|
'streams' : streams,
|
2012-09-27 22:12:57 +02:00
|
|
|
'have_initial_messages':
|
2012-10-15 23:07:33 +02:00
|
|
|
'true' if num_messages > 0 else 'false',
|
2012-09-26 00:26:35 +02:00
|
|
|
'show_debug':
|
2012-10-17 00:24:29 +02:00
|
|
|
settings.DEBUG and ('show_debug' in request.GET) },
|
2012-08-28 18:44:51 +02:00
|
|
|
context_instance=RequestContext(request))
|
|
|
|
|
2012-10-21 19:33:14 +02:00
|
|
|
@login_required_api_view
|
|
|
|
def api_update_pointer(request, user_profile):
|
|
|
|
return update_pointer_backend(request, user_profile)
|
|
|
|
|
2012-10-16 22:10:48 +02:00
|
|
|
@login_required_json_view
|
2012-10-16 21:42:40 +02:00
|
|
|
def json_update_pointer(request):
|
2012-09-06 20:52:23 +02:00
|
|
|
user_profile = UserProfile.objects.get(user=request.user)
|
2012-10-21 19:33:14 +02:00
|
|
|
return update_pointer_backend(request, user_profile)
|
|
|
|
|
|
|
|
def update_pointer_backend(request, user_profile):
|
2012-09-06 20:52:23 +02:00
|
|
|
pointer = request.POST.get('pointer')
|
|
|
|
if not pointer:
|
|
|
|
return json_error("Missing pointer")
|
|
|
|
|
|
|
|
try:
|
|
|
|
pointer = int(pointer)
|
|
|
|
except ValueError:
|
|
|
|
return json_error("Invalid pointer: must be an integer")
|
|
|
|
|
|
|
|
if pointer < 0:
|
|
|
|
return json_error("Invalid pointer value")
|
|
|
|
|
|
|
|
user_profile.pointer = pointer
|
2012-10-17 17:42:40 +02:00
|
|
|
user_profile.last_pointer_updater = request.session.session_key
|
2012-09-06 20:52:23 +02:00
|
|
|
user_profile.save()
|
2012-10-17 23:10:23 +02:00
|
|
|
|
2012-10-17 23:10:34 +02:00
|
|
|
if settings.HAVE_TORNADO_SERVER:
|
|
|
|
requests.post(settings.NOTIFY_POINTER_UPDATE_URL, data=[
|
|
|
|
('secret', settings.SHARED_SECRET),
|
|
|
|
('user', user_profile.user.id),
|
2012-10-22 23:06:22 +02:00
|
|
|
('new_pointer', pointer),
|
2012-10-23 20:03:45 +02:00
|
|
|
('pointer_updater', request.session.session_key)])
|
2012-10-17 23:10:34 +02:00
|
|
|
|
2012-09-05 22:21:25 +02:00
|
|
|
return json_success()
|
2012-08-28 18:44:51 +02:00
|
|
|
|
2012-10-24 21:07:43 +02:00
|
|
|
@login_required_json_view
|
|
|
|
def json_get_old_messages(request):
|
|
|
|
if not ('start' in request.POST):
|
|
|
|
return json_error("Missing 'start' parameter")
|
|
|
|
if not ('which in request.post'):
|
|
|
|
return json_error("Missing 'which' parameter")
|
|
|
|
if not ('number in request.post'):
|
|
|
|
return json_error("Missing 'number' parameter")
|
|
|
|
|
|
|
|
start = int(request.POST.get("start"))
|
|
|
|
which = request.POST.get("which")
|
|
|
|
number = int(request.POST.get("number"))
|
|
|
|
|
|
|
|
user_profile = UserProfile.objects.get(user=request.user)
|
|
|
|
|
|
|
|
return json_success(get_old_messages_backend(start, which, number, user_profile,
|
|
|
|
apply_markdown=True))
|
|
|
|
|
|
|
|
def get_old_messages_backend(start, which, number, user_profile,
|
|
|
|
apply_markdown=True):
|
|
|
|
query = Message.objects.select_related().filter(usermessage__user_profile = user_profile).order_by('id')
|
|
|
|
|
|
|
|
if which == "older":
|
|
|
|
messages = last_n(number, query.filter(id__lte=start))
|
|
|
|
elif which == "newer":
|
|
|
|
messages = query.filter(id__gte=start)[:number]
|
|
|
|
elif which == "around":
|
|
|
|
num_older = number / 2
|
|
|
|
num_newer = number / 2
|
|
|
|
if number % 2 != 0:
|
|
|
|
num_older += 1
|
|
|
|
messages = (last_n(num_older, query.filter(id__lte=start))
|
|
|
|
+ list(query.filter(id__gt=start)[:num_newer]))
|
|
|
|
else:
|
|
|
|
return json_error("Bad value for 'which' argument")
|
|
|
|
|
|
|
|
ret = {'messages': [message.to_dict(apply_markdown) for message in messages],
|
|
|
|
"result": "success",
|
|
|
|
"msg": "",
|
|
|
|
'server_generation': SERVER_GENERATION}
|
|
|
|
return ret
|
|
|
|
|
2012-10-24 20:42:53 +02:00
|
|
|
@asynchronous
|
|
|
|
@login_required_json_view
|
|
|
|
def json_get_updates(request, handler):
|
|
|
|
if not ('last' in request.POST and 'first' in request.POST):
|
|
|
|
return json_error("Missing message range")
|
|
|
|
user_profile = UserProfile.objects.get(user=request.user)
|
|
|
|
|
|
|
|
return get_updates_backend(request, user_profile, handler, apply_markdown=True)
|
|
|
|
|
|
|
|
@asynchronous
|
|
|
|
@login_required_api_view
|
|
|
|
def api_get_messages(request, user_profile, handler):
|
|
|
|
return get_updates_backend(request, user_profile, handler,
|
|
|
|
apply_markdown=(request.POST.get("apply_markdown") is not None),
|
|
|
|
mirror=request.POST.get("mirror"))
|
|
|
|
|
2012-10-24 20:32:17 +02:00
|
|
|
def format_updates_response(messages=[], apply_markdown=True, reason_empty=None,
|
2012-10-19 21:37:37 +02:00
|
|
|
user_profile=None, new_pointer=None, where='bottom',
|
2012-10-23 22:13:27 +02:00
|
|
|
mirror=None):
|
2012-10-19 17:34:48 +02:00
|
|
|
max_message_id = None
|
|
|
|
if user_profile is not None:
|
|
|
|
try:
|
|
|
|
max_message_id = Message.objects.filter(usermessage__user_profile=user_profile).order_by('-id')[0].id
|
|
|
|
except:
|
|
|
|
pass
|
2012-10-19 21:37:37 +02:00
|
|
|
if mirror is not None:
|
|
|
|
messages = [m for m in messages if m.sending_client.name != mirror]
|
2012-10-19 17:34:48 +02:00
|
|
|
ret = {'messages': [message.to_dict(apply_markdown) for message in messages],
|
|
|
|
"result": "success",
|
|
|
|
"msg": "",
|
|
|
|
'where': where,
|
|
|
|
'server_generation': SERVER_GENERATION}
|
2012-10-19 22:20:13 +02:00
|
|
|
if reason_empty is not None:
|
|
|
|
ret['reason_empty'] = reason_empty
|
2012-10-19 17:34:48 +02:00
|
|
|
if max_message_id is not None:
|
|
|
|
# TODO: Figure out how to accurately return this always
|
|
|
|
ret["max_message_id"] = max_message_id
|
2012-10-23 22:13:27 +02:00
|
|
|
if new_pointer is not None:
|
2012-10-17 23:10:34 +02:00
|
|
|
ret['new_pointer'] = new_pointer
|
2012-10-19 17:34:48 +02:00
|
|
|
return ret
|
|
|
|
|
2012-10-23 22:13:27 +02:00
|
|
|
def format_delayed_updates_response(request=None, user_profile=None,
|
|
|
|
new_pointer=None, pointer_updater=None,
|
|
|
|
**kwargs):
|
|
|
|
client_pointer = request.POST.get("pointer")
|
|
|
|
client_wants_ptr_updates = False
|
|
|
|
if client_pointer is not None:
|
|
|
|
client_pointer = int(client_pointer)
|
|
|
|
client_wants_ptr_updates = True
|
|
|
|
|
|
|
|
reason_empty = None
|
|
|
|
if (client_wants_ptr_updates
|
|
|
|
and str(user_profile.last_pointer_updater) != str(request.session.session_key)
|
|
|
|
and client_pointer != new_pointer):
|
|
|
|
if not kwargs.get('messages', False):
|
|
|
|
reason_empty = 'pointer_update'
|
|
|
|
else:
|
|
|
|
new_pointer = None
|
|
|
|
|
|
|
|
return format_updates_response(reason_empty=reason_empty,
|
|
|
|
new_pointer=new_pointer,
|
|
|
|
**kwargs)
|
2012-10-23 20:48:02 +02:00
|
|
|
|
2012-10-24 20:28:36 +02:00
|
|
|
def return_messages_immediately(request, user_profile, **kwargs):
|
2012-09-29 03:08:39 +02:00
|
|
|
first = request.POST.get("first")
|
|
|
|
last = request.POST.get("last")
|
2012-10-17 23:10:34 +02:00
|
|
|
client_pointer = request.POST.get("pointer")
|
2012-10-02 23:42:45 +02:00
|
|
|
failures = request.POST.get("failures")
|
2012-10-19 21:42:40 +02:00
|
|
|
want_old_messages = (request.POST.get("want_old_messages") == "true")
|
2012-10-16 21:15:01 +02:00
|
|
|
client_server_generation = request.POST.get("server_generation")
|
2012-10-17 23:45:03 +02:00
|
|
|
client_reload_pending = request.POST.get("reload_pending")
|
2012-09-29 03:08:39 +02:00
|
|
|
if first is None or last is None:
|
2012-10-02 23:42:45 +02:00
|
|
|
# When an API user is first querying the server to subscribe,
|
|
|
|
# there's no reason to reply immediately.
|
2012-10-19 17:34:48 +02:00
|
|
|
# TODO: Make this work with server_generation/failures
|
2012-10-24 20:28:36 +02:00
|
|
|
return None
|
2012-10-01 16:14:47 +02:00
|
|
|
first = int(first)
|
|
|
|
last = int(last)
|
2012-10-23 22:13:27 +02:00
|
|
|
client_wants_ptr_updates = False
|
2012-10-17 23:10:34 +02:00
|
|
|
if client_pointer is not None:
|
|
|
|
client_pointer = int(client_pointer)
|
2012-10-23 22:13:27 +02:00
|
|
|
client_wants_ptr_updates = True
|
2012-10-02 23:42:45 +02:00
|
|
|
if failures is not None:
|
|
|
|
failures = int(failures)
|
2012-10-18 21:25:53 +02:00
|
|
|
if client_reload_pending is not None:
|
|
|
|
client_reload_pending = int(client_reload_pending)
|
2012-09-28 21:53:20 +02:00
|
|
|
|
2012-10-17 23:10:34 +02:00
|
|
|
messages = []
|
2012-09-29 01:38:03 +02:00
|
|
|
where = 'bottom'
|
2012-10-17 23:10:34 +02:00
|
|
|
new_pointer = None
|
2012-10-15 17:54:12 +02:00
|
|
|
query = Message.objects.select_related().filter(usermessage__user_profile = user_profile).order_by('id')
|
2012-10-17 23:10:34 +02:00
|
|
|
ptr = user_profile.pointer
|
2012-09-29 01:38:03 +02:00
|
|
|
|
|
|
|
if last == -1:
|
|
|
|
# User has no messages yet
|
|
|
|
# Get a range around the pointer
|
|
|
|
messages = (last_n(200, query.filter(id__lt=ptr))
|
|
|
|
+ list(query.filter(id__gte=ptr)[:200]))
|
|
|
|
else:
|
|
|
|
messages = query.filter(id__gt=last)[:400]
|
2012-10-19 21:42:40 +02:00
|
|
|
if want_old_messages and not messages:
|
2012-09-29 01:38:03 +02:00
|
|
|
# No more messages in the future; try filling in from the past.
|
|
|
|
messages = last_n(400, query.filter(id__lt=first))
|
|
|
|
where = 'top'
|
|
|
|
|
2012-10-19 21:37:37 +02:00
|
|
|
# Filter for mirroring before checking whether there are any
|
2012-10-11 18:59:54 +02:00
|
|
|
# messages to pass on. If we don't do this, when the only message
|
2012-10-19 21:37:37 +02:00
|
|
|
# to forward is one that was sent via the mirroring, the API
|
|
|
|
# client will end up in an endless loop requesting more data from
|
|
|
|
# us.
|
|
|
|
if "mirror" in kwargs:
|
|
|
|
messages = [m for m in messages if
|
|
|
|
m.sending_client.name != kwargs["mirror"]]
|
2012-10-11 18:59:54 +02:00
|
|
|
|
2012-09-28 22:25:31 +02:00
|
|
|
if messages:
|
2012-10-24 20:28:36 +02:00
|
|
|
return format_updates_response(messages=messages, where=where, **kwargs)
|
2012-09-28 22:25:31 +02:00
|
|
|
|
2012-10-19 22:20:13 +02:00
|
|
|
# We might want to return an empty list to the client immediately.
|
|
|
|
# In that case, we tell the client why.
|
|
|
|
reason_empty = None
|
2012-10-02 23:42:45 +02:00
|
|
|
|
2012-10-19 22:28:57 +02:00
|
|
|
if want_old_messages:
|
|
|
|
# Tell the client to hide the "Load more messages" button.
|
|
|
|
reason_empty = 'no_old_messages'
|
|
|
|
elif failures >= 4:
|
2012-10-19 22:20:13 +02:00
|
|
|
# Tell the client to hide the connection failure message.
|
|
|
|
reason_empty = 'reset_failures'
|
|
|
|
elif (client_server_generation is not None
|
2012-10-17 23:45:03 +02:00
|
|
|
and int(client_server_generation) != SERVER_GENERATION
|
|
|
|
and not client_reload_pending):
|
2012-10-19 22:20:13 +02:00
|
|
|
# Inform the client that they should reload.
|
|
|
|
reason_empty = 'client_reload'
|
2012-10-23 22:13:27 +02:00
|
|
|
elif (client_wants_ptr_updates
|
2012-10-23 21:24:35 +02:00
|
|
|
and str(user_profile.last_pointer_updater) != str(request.session.session_key)
|
2012-10-23 22:13:27 +02:00
|
|
|
and ptr != client_pointer):
|
2012-10-23 21:24:35 +02:00
|
|
|
reason_empty = 'pointer_update'
|
|
|
|
new_pointer = ptr
|
2012-10-19 22:20:13 +02:00
|
|
|
|
|
|
|
if reason_empty is not None:
|
2012-10-24 20:28:36 +02:00
|
|
|
return format_updates_response(
|
2012-10-19 22:20:13 +02:00
|
|
|
where="bottom",
|
|
|
|
user_profile=user_profile,
|
|
|
|
reason_empty=reason_empty,
|
2012-10-17 23:10:34 +02:00
|
|
|
new_pointer=new_pointer,
|
2012-10-24 20:28:36 +02:00
|
|
|
**kwargs)
|
2012-10-16 21:15:01 +02:00
|
|
|
|
2012-10-24 20:28:36 +02:00
|
|
|
return None
|
2012-09-28 22:25:31 +02:00
|
|
|
|
2012-10-24 20:55:31 +02:00
|
|
|
def send_with_safety_check(response, handler, apply_markdown=True, **kwargs):
|
|
|
|
# Make sure that Markdown rendering really happened, if requested.
|
|
|
|
# This is a security issue because it's where we escape HTML.
|
|
|
|
# c.f. ticket #64
|
|
|
|
#
|
|
|
|
# apply_markdown=True is the fail-safe default.
|
|
|
|
if apply_markdown:
|
|
|
|
for msg in response['messages']:
|
|
|
|
if msg['content_type'] != 'text/html':
|
|
|
|
handler.set_status(500)
|
|
|
|
handler.finish('Internal error: bad message format')
|
|
|
|
return
|
|
|
|
handler.finish(response)
|
|
|
|
|
2012-10-01 21:36:44 +02:00
|
|
|
def get_updates_backend(request, user_profile, handler, **kwargs):
|
2012-10-24 20:28:36 +02:00
|
|
|
resp = return_messages_immediately(request, user_profile, **kwargs)
|
|
|
|
if resp is not None:
|
2012-10-24 20:55:31 +02:00
|
|
|
send_with_safety_check(resp, handler, **kwargs)
|
2012-09-28 22:25:31 +02:00
|
|
|
return
|
2012-09-28 21:53:20 +02:00
|
|
|
|
2012-10-17 23:10:34 +02:00
|
|
|
def cb(**cb_kwargs):
|
2012-08-28 22:56:21 +02:00
|
|
|
if handler.request.connection.stream.closed():
|
|
|
|
return
|
|
|
|
try:
|
2012-10-17 23:10:34 +02:00
|
|
|
kwargs.update(cb_kwargs)
|
2012-10-23 22:13:27 +02:00
|
|
|
res = format_delayed_updates_response(request=request,
|
|
|
|
user_profile=user_profile,
|
|
|
|
**kwargs)
|
2012-10-24 20:55:31 +02:00
|
|
|
send_with_safety_check(res, handler, **kwargs)
|
2012-09-07 19:46:50 +02:00
|
|
|
except socket.error:
|
2012-08-28 22:56:21 +02:00
|
|
|
pass
|
|
|
|
|
2012-10-17 23:10:34 +02:00
|
|
|
user_profile.add_receive_callback(handler.async_callback(cb))
|
|
|
|
user_profile.add_pointer_update_callback(handler.async_callback(cb))
|
2012-09-27 22:14:14 +02:00
|
|
|
|
2012-10-21 03:53:03 +02:00
|
|
|
@login_required_api_view
|
|
|
|
def api_get_profile(request, user_profile):
|
|
|
|
return json_success({"pointer": user_profile.pointer})
|
|
|
|
|
2012-10-16 22:32:47 +02:00
|
|
|
@login_required_api_view
|
2012-10-01 21:36:44 +02:00
|
|
|
def api_send_message(request, user_profile):
|
2012-10-23 16:53:34 +02:00
|
|
|
return send_message_backend(request, user_profile, user_profile,
|
|
|
|
client_name=request.POST.get("client", "API"))
|
2012-10-01 21:36:44 +02:00
|
|
|
|
2012-10-16 22:10:48 +02:00
|
|
|
@login_required_json_view
|
2012-10-16 21:42:40 +02:00
|
|
|
def json_send_message(request):
|
2012-10-01 21:36:44 +02:00
|
|
|
user_profile = UserProfile.objects.get(user=request.user)
|
2012-09-24 20:29:33 +02:00
|
|
|
if 'time' in request.POST:
|
|
|
|
return json_error("Invalid field 'time'")
|
2012-10-23 16:53:34 +02:00
|
|
|
return send_message_backend(request, user_profile, user_profile,
|
|
|
|
client_name=request.POST.get("client"))
|
2012-09-06 21:52:03 +02:00
|
|
|
|
2012-10-03 22:32:50 +02:00
|
|
|
# TODO: This should have a real superuser security check
|
|
|
|
def is_super_user_api(request):
|
|
|
|
return request.POST.get("api-key") == "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
2012-09-21 16:40:46 +02:00
|
|
|
|
2012-10-22 20:48:39 +02:00
|
|
|
def already_sent_mirrored_message(request):
|
2012-10-25 06:09:49 +02:00
|
|
|
utc_from_ts = datetime.datetime.utcfromtimestamp
|
|
|
|
req_time = float(request.POST['time'])
|
2012-10-11 19:15:41 +02:00
|
|
|
email = request.POST['sender'].lower()
|
2012-10-03 21:05:48 +02:00
|
|
|
if Message.objects.filter(sender__user__email=email,
|
|
|
|
content=request.POST['content'],
|
2012-10-25 06:09:49 +02:00
|
|
|
pub_date__gt=utc_from_ts(req_time - 10).replace(tzinfo=utc),
|
|
|
|
pub_date__lt=utc_from_ts(req_time + 10).replace(tzinfo=utc)):
|
2012-10-03 22:32:50 +02:00
|
|
|
return True
|
|
|
|
return False
|
2012-09-27 20:54:57 +02:00
|
|
|
|
2012-10-22 22:34:56 +02:00
|
|
|
# Validte that the passed in object is an email address from the user's realm
|
|
|
|
# TODO: Check that it's a real email address here.
|
|
|
|
def same_realm_email(user_profile, email):
|
|
|
|
try:
|
|
|
|
domain = email.split("@", 1)[1]
|
2012-10-22 23:20:38 +02:00
|
|
|
return user_profile.realm.domain == domain
|
2012-10-22 22:34:56 +02:00
|
|
|
except:
|
|
|
|
return False
|
|
|
|
|
|
|
|
# Parse out the sender and huddle/personal recipients
|
|
|
|
def parse_named_users(request):
|
|
|
|
sender = {}
|
|
|
|
recipients = set()
|
|
|
|
try:
|
2012-10-22 23:59:08 +02:00
|
|
|
if 'sender' in request.POST:
|
2012-10-22 22:34:56 +02:00
|
|
|
sender = {'email': request.POST["sender"],
|
|
|
|
'full_name': request.POST["fullname"],
|
|
|
|
'short_name': request.POST["shortname"]}
|
|
|
|
|
|
|
|
if request.POST['type'] == 'personal':
|
|
|
|
if ',' in request.POST['recipient']:
|
|
|
|
# Huddle message
|
|
|
|
for user_email in [e.strip().lower() for e in
|
|
|
|
request.POST["recipient"].split(",")]:
|
|
|
|
recipients.add(user_email)
|
|
|
|
else:
|
|
|
|
user_email = request.POST["recipient"].strip().lower()
|
|
|
|
recipients.add(user_email)
|
|
|
|
except:
|
|
|
|
return (False, None, None)
|
|
|
|
|
|
|
|
return (True, sender, list(recipients))
|
|
|
|
|
2012-10-22 20:48:39 +02:00
|
|
|
def create_mirrored_message_users(request, user_profile):
|
2012-10-22 22:34:56 +02:00
|
|
|
(valid_input, sender_data, huddle_recipients) = parse_named_users(request)
|
|
|
|
if not valid_input:
|
|
|
|
return (False, None)
|
|
|
|
|
|
|
|
# First, check that the sender is in our realm:
|
|
|
|
if 'email' in sender_data and not same_realm_email(user_profile,
|
|
|
|
sender_data['email']):
|
|
|
|
return (False, None)
|
|
|
|
# Then, check that all huddle/personal recipients are in our realm:
|
|
|
|
for recipient in huddle_recipients:
|
|
|
|
if not same_realm_email(user_profile, recipient):
|
|
|
|
return (False, None)
|
|
|
|
|
2012-10-03 22:32:50 +02:00
|
|
|
# Create a user for the sender, if needed
|
2012-10-22 22:34:56 +02:00
|
|
|
if 'email' in sender_data:
|
|
|
|
sender = create_user_if_needed(user_profile.realm, sender_data['email'],
|
|
|
|
sender_data['full_name'], sender_data['short_name'])
|
|
|
|
else:
|
|
|
|
sender = user_profile
|
|
|
|
|
|
|
|
# Create users for huddle/personal recipients, if needed.
|
|
|
|
for recipient in huddle_recipients:
|
|
|
|
create_user_if_needed(user_profile.realm, recipient,
|
|
|
|
recipient.split('@')[0],
|
|
|
|
recipient.split('@')[0])
|
|
|
|
|
|
|
|
return (True, sender)
|
2012-09-06 22:00:39 +02:00
|
|
|
|
2012-10-03 21:31:44 +02:00
|
|
|
# We do not @require_login for send_message_backend, since it is used
|
|
|
|
# both from the API and the web service. Code calling
|
|
|
|
# send_message_backend should either check the API key or check that
|
|
|
|
# the user is logged in.
|
2012-10-23 16:53:34 +02:00
|
|
|
def send_message_backend(request, user_profile, sender, client_name=None):
|
2012-09-07 19:20:04 +02:00
|
|
|
if "type" not in request.POST:
|
|
|
|
return json_error("Missing type")
|
2012-10-02 23:25:14 +02:00
|
|
|
if "content" not in request.POST:
|
2012-09-07 19:20:04 +02:00
|
|
|
return json_error("Missing message contents")
|
2012-10-23 16:53:34 +02:00
|
|
|
if client_name is None:
|
2012-10-19 21:30:42 +02:00
|
|
|
return json_error("Missing client")
|
2012-10-22 22:34:56 +02:00
|
|
|
message_type_name = request.POST["type"]
|
|
|
|
forged = "forged" in request.POST
|
|
|
|
is_super_user = is_super_user_api(request)
|
|
|
|
if forged and not is_super_user:
|
|
|
|
return json_error("User not authorized for this query")
|
|
|
|
|
2012-10-23 16:53:34 +02:00
|
|
|
if client_name == "zephyr_mirror":
|
2012-10-22 22:34:56 +02:00
|
|
|
# Here's how security works for non-superuser mirroring:
|
|
|
|
#
|
|
|
|
# The message must be (1) a huddle/personal message (2) that
|
|
|
|
# is both sent and received exclusively by other users in your
|
|
|
|
# realm which (3) must be the MIT realm and (4) you must have
|
|
|
|
# received the message.
|
|
|
|
#
|
|
|
|
# If that's the case, we let it through, but we still have the
|
|
|
|
# security flaw that we're trusting your Hesiod data for users
|
|
|
|
# you report having sent you a message.
|
|
|
|
if "sender" not in request.POST:
|
|
|
|
return json_error("Missing sender")
|
|
|
|
if message_type_name != "personal" and not is_super_user:
|
|
|
|
return json_error("User not authorized for this query")
|
|
|
|
(valid_input, mirror_sender) = create_mirrored_message_users(request, user_profile)
|
|
|
|
if not valid_input:
|
|
|
|
return json_error("Invalid mirrored message")
|
|
|
|
if user_profile.realm.domain != "mit.edu":
|
|
|
|
return json_error("Invalid mirrored realm")
|
2012-10-22 20:48:39 +02:00
|
|
|
if already_sent_mirrored_message(request):
|
2012-10-03 22:32:50 +02:00
|
|
|
return json_success()
|
2012-10-22 22:34:56 +02:00
|
|
|
sender = mirror_sender
|
2012-09-07 19:20:04 +02:00
|
|
|
|
2012-10-10 22:57:21 +02:00
|
|
|
if message_type_name == 'stream':
|
2012-10-10 23:09:16 +02:00
|
|
|
if "stream" not in request.POST:
|
|
|
|
return json_error("Missing stream")
|
2012-10-11 00:01:39 +02:00
|
|
|
if "subject" not in request.POST:
|
|
|
|
return json_error("Missing subject")
|
2012-10-11 19:29:37 +02:00
|
|
|
stream_name = request.POST['stream'].strip()
|
|
|
|
subject_name = request.POST['subject'].strip()
|
2012-09-07 19:20:04 +02:00
|
|
|
|
2012-10-10 22:59:40 +02:00
|
|
|
if not valid_stream_name(stream_name):
|
2012-10-10 23:01:28 +02:00
|
|
|
return json_error("Invalid stream name")
|
2012-10-05 00:11:12 +02:00
|
|
|
## FIXME: Commented out temporarily while we figure out what we want
|
2012-10-11 00:01:39 +02:00
|
|
|
# if not valid_stream_name(subject_name):
|
|
|
|
# return json_error("Invalid subject name")
|
2012-10-04 20:23:51 +02:00
|
|
|
|
2012-10-10 22:55:26 +02:00
|
|
|
stream = create_stream_if_needed(user_profile.realm, stream_name)
|
2012-10-10 22:57:21 +02:00
|
|
|
recipient = Recipient.objects.get(type_id=stream.id, type=Recipient.STREAM)
|
2012-10-03 21:31:01 +02:00
|
|
|
elif message_type_name == 'personal':
|
2012-09-07 19:20:04 +02:00
|
|
|
if "recipient" not in request.POST:
|
|
|
|
return json_error("Missing recipient")
|
2012-10-22 22:34:56 +02:00
|
|
|
(valid_input, _, huddle_recipients) = parse_named_users(request)
|
|
|
|
if not valid_input:
|
|
|
|
return json_error("Unable to parse recipients")
|
2012-10-23 16:53:34 +02:00
|
|
|
if client_name == "zephyr_mirror":
|
2012-10-22 22:34:56 +02:00
|
|
|
if user_profile.user.email not in huddle_recipients and not forged:
|
|
|
|
return json_error("User not authorized for this query")
|
|
|
|
|
|
|
|
recipient_profile_ids = set()
|
|
|
|
for recipient in huddle_recipients:
|
2012-10-24 20:25:45 +02:00
|
|
|
if recipient == "":
|
|
|
|
continue
|
2012-10-22 22:34:56 +02:00
|
|
|
try:
|
|
|
|
recipient_profile_ids.add(UserProfile.objects.get(user__email=recipient).id)
|
|
|
|
except UserProfile.DoesNotExist:
|
|
|
|
return json_error("Invalid email '%s'" % (recipient))
|
|
|
|
if len(recipient_profile_ids) > 1:
|
2012-09-05 17:32:05 +02:00
|
|
|
# Make sure the sender is included in the huddle
|
2012-10-22 22:34:56 +02:00
|
|
|
recipient_profile_ids.add(sender.id)
|
|
|
|
huddle = get_huddle(list(recipient_profile_ids))
|
2012-09-10 19:43:11 +02:00
|
|
|
recipient = Recipient.objects.get(type_id=huddle.id, type=Recipient.HUDDLE)
|
2012-09-04 23:20:21 +02:00
|
|
|
else:
|
2012-10-22 22:34:56 +02:00
|
|
|
recipient = Recipient.objects.get(type_id=list(recipient_profile_ids)[0],
|
2012-09-07 20:14:13 +02:00
|
|
|
type=Recipient.PERSONAL)
|
2012-09-04 23:43:56 +02:00
|
|
|
else:
|
2012-10-03 00:10:55 +02:00
|
|
|
return json_error("Invalid message type")
|
2012-08-28 21:27:42 +02:00
|
|
|
|
2012-10-03 21:05:48 +02:00
|
|
|
message = Message()
|
2012-10-22 22:34:56 +02:00
|
|
|
message.sender = sender
|
2012-10-11 19:30:33 +02:00
|
|
|
message.content = request.POST['content']
|
2012-10-03 21:05:48 +02:00
|
|
|
message.recipient = recipient
|
2012-10-10 22:57:21 +02:00
|
|
|
if message_type_name == 'stream':
|
2012-10-11 00:01:39 +02:00
|
|
|
message.subject = subject_name
|
2012-10-22 22:34:56 +02:00
|
|
|
if forged:
|
2012-10-03 21:05:48 +02:00
|
|
|
# Forged messages come with a timestamp
|
|
|
|
message.pub_date = datetime.datetime.utcfromtimestamp(float(request.POST['time'])).replace(tzinfo=utc)
|
2012-09-21 23:23:15 +02:00
|
|
|
else:
|
2012-10-03 21:05:48 +02:00
|
|
|
message.pub_date = datetime.datetime.utcnow().replace(tzinfo=utc)
|
2012-10-23 16:53:34 +02:00
|
|
|
message.sending_client = get_client(client_name)
|
2012-10-19 21:37:37 +02:00
|
|
|
do_send_message(message)
|
2012-08-28 21:27:42 +02:00
|
|
|
|
2012-09-06 21:36:44 +02:00
|
|
|
return json_success()
|
2012-08-30 18:04:35 +02:00
|
|
|
|
2012-10-11 19:31:21 +02:00
|
|
|
|
2012-10-17 23:10:34 +02:00
|
|
|
def validate_notify(request, handler):
|
2012-10-09 22:21:03 +02:00
|
|
|
# Check the shared secret.
|
|
|
|
# Also check the originating IP, at least for now.
|
2012-10-20 04:34:46 +02:00
|
|
|
if ((request.META['REMOTE_ADDR'] not in ('127.0.0.1', '::1'))
|
2012-10-09 22:21:03 +02:00
|
|
|
or (request.POST.get('secret') != settings.SHARED_SECRET)):
|
|
|
|
|
|
|
|
handler.set_status(403)
|
|
|
|
handler.finish('Access denied')
|
2012-10-17 23:10:34 +02:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
@asynchronous
|
|
|
|
@csrf_exempt
|
|
|
|
@require_post
|
|
|
|
def notify_new_message(request, handler):
|
|
|
|
if not validate_notify(request, handler):
|
2012-10-09 22:21:03 +02:00
|
|
|
return
|
|
|
|
|
|
|
|
# FIXME: better query
|
2012-10-17 01:58:20 +02:00
|
|
|
users = [UserProfile.objects.get(id=user)
|
|
|
|
for user in request.POST['users'].split(',')]
|
2012-10-09 22:21:03 +02:00
|
|
|
message = Message.objects.get(id=request.POST['message'])
|
|
|
|
|
|
|
|
for user in users:
|
|
|
|
user.receive(message)
|
|
|
|
|
|
|
|
handler.finish()
|
|
|
|
|
2012-10-17 23:10:34 +02:00
|
|
|
@asynchronous
|
|
|
|
@csrf_exempt
|
|
|
|
@require_post
|
|
|
|
def notify_pointer_update(request, handler):
|
|
|
|
if not validate_notify(request, handler):
|
|
|
|
return
|
|
|
|
|
|
|
|
# FIXME: better query
|
|
|
|
user_profile = UserProfile.objects.get(id=request.POST['user'])
|
|
|
|
new_pointer = int(request.POST['new_pointer'])
|
2012-10-23 20:03:45 +02:00
|
|
|
pointer_updater = request.POST['pointer_updater']
|
2012-10-17 23:10:34 +02:00
|
|
|
|
2012-10-23 20:03:45 +02:00
|
|
|
user_profile.update_pointer(new_pointer, pointer_updater)
|
2012-10-17 23:10:34 +02:00
|
|
|
|
|
|
|
handler.finish()
|
|
|
|
|
2012-10-16 22:32:47 +02:00
|
|
|
@login_required_api_view
|
2012-10-11 19:31:21 +02:00
|
|
|
def api_get_public_streams(request, user_profile):
|
|
|
|
streams = sorted([stream.name for stream in
|
|
|
|
Stream.objects.filter(realm=user_profile.realm)])
|
|
|
|
return json_success({"streams": streams})
|
|
|
|
|
2012-09-21 19:34:05 +02:00
|
|
|
def gather_subscriptions(user_profile):
|
2012-10-22 20:15:25 +02:00
|
|
|
subscriptions = Subscription.objects.filter(user_profile=user_profile, active=True)
|
2012-09-18 16:30:25 +02:00
|
|
|
# For now, don't display the subscription for your ability to receive personals.
|
2012-09-21 20:34:00 +02:00
|
|
|
return sorted([get_display_recipient(sub.recipient) for sub in subscriptions
|
2012-10-10 22:57:21 +02:00
|
|
|
if sub.recipient.type == Recipient.STREAM])
|
2012-09-18 16:30:25 +02:00
|
|
|
|
2012-10-16 22:32:47 +02:00
|
|
|
@login_required_api_view
|
2012-10-11 19:31:21 +02:00
|
|
|
def api_get_subscriptions(request, user_profile):
|
|
|
|
return json_success({"streams": gather_subscriptions(user_profile)})
|
|
|
|
|
2012-10-16 22:10:48 +02:00
|
|
|
@login_required_json_view
|
2012-09-21 23:22:15 +02:00
|
|
|
def json_list_subscriptions(request):
|
2012-09-18 16:30:25 +02:00
|
|
|
subs = gather_subscriptions(UserProfile.objects.get(user=request.user))
|
2012-10-16 22:50:46 +02:00
|
|
|
return json_success({"subscriptions": subs})
|
2012-09-18 16:30:25 +02:00
|
|
|
|
2012-10-16 22:10:48 +02:00
|
|
|
@login_required_json_view
|
2012-09-21 23:22:15 +02:00
|
|
|
def json_remove_subscription(request):
|
2012-08-30 18:04:35 +02:00
|
|
|
user_profile = UserProfile.objects.get(user=request.user)
|
2012-09-07 19:20:04 +02:00
|
|
|
if 'subscription' not in request.POST:
|
|
|
|
return json_error("Missing subscriptions")
|
2012-08-30 18:04:35 +02:00
|
|
|
|
2012-09-21 22:30:29 +02:00
|
|
|
sub_name = request.POST.get('subscription')
|
2012-10-10 23:00:50 +02:00
|
|
|
stream = get_stream(sub_name, user_profile.realm)
|
2012-10-10 22:53:24 +02:00
|
|
|
if not stream:
|
2012-10-20 23:05:13 +02:00
|
|
|
return json_error("Stream does not exist")
|
|
|
|
did_remove = do_remove_subscription(user_profile, stream)
|
|
|
|
if not did_remove:
|
2012-10-02 17:53:14 +02:00
|
|
|
return json_error("Not subscribed, so you can't unsubscribe")
|
2012-08-30 18:04:35 +02:00
|
|
|
|
2012-09-21 22:30:29 +02:00
|
|
|
return json_success({"data": sub_name})
|
2012-08-30 20:00:04 +02:00
|
|
|
|
2012-10-10 22:59:40 +02:00
|
|
|
def valid_stream_name(name):
|
2012-10-22 18:58:39 +02:00
|
|
|
# Streams must start with a letter or number or a dot.
|
|
|
|
return re.match(r'^[\w.][\w. -]*$', name, flags=re.UNICODE)
|
2012-10-01 21:31:30 +02:00
|
|
|
|
2012-10-16 22:32:47 +02:00
|
|
|
@login_required_api_view
|
2012-10-11 21:34:17 +02:00
|
|
|
def api_subscribe(request, user_profile):
|
|
|
|
if "streams" not in request.POST:
|
|
|
|
return json_error("Missing streams argument.")
|
|
|
|
streams = simplejson.loads(request.POST.get("streams"))
|
2012-10-12 16:45:53 +02:00
|
|
|
for stream_name in streams:
|
|
|
|
if len(stream_name) > 30:
|
2012-10-12 16:48:39 +02:00
|
|
|
return json_error("Stream name (%s) too long." % (stream_name,))
|
|
|
|
if not valid_stream_name(stream_name):
|
|
|
|
return json_error("Invalid characters in stream name (%s)." % (stream_name,))
|
2012-10-11 21:34:17 +02:00
|
|
|
res = add_subscriptions_backend(request, user_profile, streams)
|
|
|
|
return json_success(res)
|
|
|
|
|
2012-10-16 22:10:48 +02:00
|
|
|
@login_required_json_view
|
2012-09-21 23:22:15 +02:00
|
|
|
def json_add_subscription(request):
|
2012-08-30 20:00:04 +02:00
|
|
|
user_profile = UserProfile.objects.get(user=request.user)
|
|
|
|
|
2012-09-21 22:30:29 +02:00
|
|
|
if "new_subscription" not in request.POST:
|
2012-10-16 22:50:46 +02:00
|
|
|
return json_error("Missing new_subscription argument")
|
2012-10-12 16:48:39 +02:00
|
|
|
stream_name = request.POST.get('new_subscription').strip()
|
|
|
|
if not valid_stream_name(stream_name):
|
2012-10-10 23:01:28 +02:00
|
|
|
return json_error("Invalid characters in stream names")
|
2012-10-12 16:48:39 +02:00
|
|
|
if len(stream_name) > 30:
|
|
|
|
return json_error("Stream name %s too long." % (stream_name,))
|
2012-10-11 21:34:17 +02:00
|
|
|
res = add_subscriptions_backend(request,user_profile,
|
|
|
|
[request.POST["new_subscription"]])
|
|
|
|
if len(res["already_subscribed"]) != 0:
|
|
|
|
return json_error("Subscription already exists")
|
|
|
|
return json_success({"data": res["subscribed"][0]})
|
|
|
|
|
|
|
|
def add_subscriptions_backend(request, user_profile, streams):
|
|
|
|
subscribed = []
|
|
|
|
already_subscribed = []
|
2012-10-19 23:55:49 +02:00
|
|
|
for stream_name in list(set(streams)):
|
2012-10-11 21:34:17 +02:00
|
|
|
stream = create_stream_if_needed(user_profile.realm, stream_name)
|
2012-10-20 21:43:13 +02:00
|
|
|
did_subscribe = do_add_subscription(user_profile, stream)
|
|
|
|
if did_subscribe:
|
|
|
|
subscribed.append(stream_name)
|
|
|
|
else:
|
|
|
|
already_subscribed.append(stream_name)
|
2012-10-11 21:34:17 +02:00
|
|
|
|
|
|
|
return {"subscribed": subscribed,
|
|
|
|
"already_subscribed": already_subscribed}
|
2012-09-05 23:38:20 +02:00
|
|
|
|
2012-10-16 22:10:48 +02:00
|
|
|
@login_required_json_view
|
2012-10-16 21:42:40 +02:00
|
|
|
def json_change_settings(request):
|
2012-09-21 19:32:01 +02:00
|
|
|
user_profile = UserProfile.objects.get(user=request.user)
|
|
|
|
|
|
|
|
# First validate all the inputs
|
|
|
|
if "full_name" not in request.POST:
|
|
|
|
return json_error("Invalid settings request -- missing full_name.")
|
|
|
|
if "new_password" not in request.POST:
|
|
|
|
return json_error("Invalid settings request -- missing new_password.")
|
|
|
|
if "old_password" not in request.POST:
|
|
|
|
return json_error("Invalid settings request -- missing old_password.")
|
|
|
|
if "confirm_password" not in request.POST:
|
|
|
|
return json_error("Invalid settings request -- missing confirm_password.")
|
|
|
|
|
|
|
|
old_password = request.POST['old_password']
|
|
|
|
new_password = request.POST['new_password']
|
|
|
|
confirm_password = request.POST['confirm_password']
|
2012-10-11 19:15:41 +02:00
|
|
|
full_name = request.POST['full_name']
|
2012-09-21 19:32:01 +02:00
|
|
|
|
|
|
|
if new_password != "":
|
|
|
|
if new_password != confirm_password:
|
|
|
|
return json_error("New password must match confirmation password!")
|
|
|
|
if not authenticate(username=user_profile.user.email, password=old_password):
|
|
|
|
return json_error("Wrong password!")
|
|
|
|
user_profile.user.set_password(new_password)
|
|
|
|
|
|
|
|
result = {}
|
|
|
|
if user_profile.full_name != full_name:
|
|
|
|
user_profile.full_name = full_name
|
|
|
|
result['full_name'] = full_name
|
2012-10-02 22:20:07 +02:00
|
|
|
|
2012-09-21 19:32:01 +02:00
|
|
|
user_profile.user.save()
|
|
|
|
user_profile.save()
|
|
|
|
|
|
|
|
return json_success(result)
|
|
|
|
|
2012-10-16 22:10:48 +02:00
|
|
|
@login_required_json_view
|
2012-10-17 20:05:17 +02:00
|
|
|
def json_stream_exists(request):
|
|
|
|
if "stream" not in request.POST:
|
|
|
|
return json_error("Missing stream argument.")
|
|
|
|
stream = request.POST.get("stream")
|
2012-10-10 22:59:40 +02:00
|
|
|
if not valid_stream_name(stream):
|
2012-10-10 23:00:50 +02:00
|
|
|
return json_error("Invalid characters in stream name")
|
2012-10-16 22:50:46 +02:00
|
|
|
exists = bool(get_stream(stream, UserProfile.objects.get(user=request.user).realm))
|
|
|
|
return json_success({"exists": exists})
|
2012-10-17 20:43:52 +02:00
|
|
|
|
2012-10-17 22:36:49 +02:00
|
|
|
@csrf_exempt
|
|
|
|
@require_post
|
2012-10-17 23:13:33 +02:00
|
|
|
def api_fetch_api_key(request):
|
2012-10-17 20:43:52 +02:00
|
|
|
try:
|
2012-10-17 22:36:49 +02:00
|
|
|
username = request.POST['username']
|
|
|
|
password = request.POST['password']
|
2012-10-17 20:43:52 +02:00
|
|
|
except KeyError:
|
2012-10-17 22:52:30 +02:00
|
|
|
return HttpResponseBadRequest("You must specify the username and password via POST.")
|
2012-10-17 20:43:52 +02:00
|
|
|
user = authenticate(username=username, password=password)
|
|
|
|
if user is None:
|
|
|
|
return HttpResponseForbidden("Your username or password is incorrect.")
|
|
|
|
if not user.is_active:
|
|
|
|
return HttpResponseForbidden("Your account has been disabled.")
|
2012-10-23 18:04:30 +02:00
|
|
|
return HttpResponse(user.userprofile.api_key)
|
2012-10-17 22:26:59 +02:00
|
|
|
|
|
|
|
@login_required_json_view
|
|
|
|
def json_fetch_api_key(request):
|
|
|
|
try:
|
|
|
|
password = request.POST['password']
|
|
|
|
except KeyError:
|
|
|
|
return json_error("You must specify your password to get your API key.")
|
|
|
|
if not request.user.check_password(password):
|
|
|
|
return json_error("Your username or password is incorrect.")
|
2012-10-23 18:04:30 +02:00
|
|
|
return json_success({"api_key": request.user.userprofile.api_key})
|