Statistics
| Branch: | Tag: | Revision:

root / invitations / invitations.py @ b6b88056

History | View | Annotate | Download (9 kB)

1 f1bb3880 Georgios Gousios
# vim: set fileencoding=utf-8 :
2 48130e66 Georgios Gousios
# Copyright 2011 GRNET S.A. All rights reserved.
3 48130e66 Georgios Gousios
#
4 48130e66 Georgios Gousios
# Redistribution and use in source and binary forms, with or without
5 48130e66 Georgios Gousios
# modification, are permitted provided that the following conditions
6 48130e66 Georgios Gousios
# are met:
7 48130e66 Georgios Gousios
#
8 48130e66 Georgios Gousios
#   1. Redistributions of source code must retain the above copyright
9 48130e66 Georgios Gousios
#      notice, this list of conditions and the following disclaimer.
10 48130e66 Georgios Gousios
#
11 48130e66 Georgios Gousios
#  2. Redistributions in binary form must reproduce the above copyright
12 48130e66 Georgios Gousios
#     notice, this list of conditions and the following disclaimer in the
13 48130e66 Georgios Gousios
#     documentation and/or other materials provided with the distribution.
14 48130e66 Georgios Gousios
#
15 48130e66 Georgios Gousios
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
16 48130e66 Georgios Gousios
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 48130e66 Georgios Gousios
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 48130e66 Georgios Gousios
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
19 48130e66 Georgios Gousios
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 48130e66 Georgios Gousios
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 48130e66 Georgios Gousios
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 48130e66 Georgios Gousios
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 48130e66 Georgios Gousios
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 48130e66 Georgios Gousios
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 48130e66 Georgios Gousios
# SUCH DAMAGE.
26 48130e66 Georgios Gousios
#
27 48130e66 Georgios Gousios
# The views and conclusions contained in the software and documentation are
28 48130e66 Georgios Gousios
# those of the authors and should not be interpreted as representing official
29 48130e66 Georgios Gousios
# policies, either expressed or implied, of GRNET S.A.
30 48130e66 Georgios Gousios
31 48130e66 Georgios Gousios
32 e6d6603a Georgios Gousios
from datetime import timedelta
33 e6d6603a Georgios Gousios
import base64
34 3b09ff22 Georgios Gousios
import time
35 3b09ff22 Georgios Gousios
import urllib
36 e6d6603a Georgios Gousios
37 03e70572 Georgios Gousios
from django.conf import settings
38 566cd8b2 Georgios Gousios
from django.core.exceptions import ValidationError
39 03e70572 Georgios Gousios
from django.db import transaction
40 03e70572 Georgios Gousios
from django.http import HttpResponse, HttpResponseRedirect
41 a640b50d Georgios Gousios
from django.template.context import RequestContext
42 2a0eee64 Georgios Gousios
from django.template.loader import render_to_string
43 566cd8b2 Georgios Gousios
from django.core.validators import validate_email
44 a640b50d Georgios Gousios
from django.views.decorators.csrf import csrf_protect
45 b6b88056 Kostas Papadimitriou
from django.utils.translation import ugettext as _
46 7427d9a3 Georgios Gousios
from synnefo.logic.email_send import send_async
47 566cd8b2 Georgios Gousios
48 03e70572 Georgios Gousios
from synnefo.api.common import method_not_allowed
49 03e70572 Georgios Gousios
from synnefo.db.models import Invitations, SynnefoUser
50 03e70572 Georgios Gousios
from synnefo.logic import users
51 e6d6603a Georgios Gousios
52 e6d6603a Georgios Gousios
from Crypto.Cipher import AES
53 03e70572 Georgios Gousios
54 f1bb3880 Georgios Gousios
55 e6d6603a Georgios Gousios
def process_form(request):
56 a640b50d Georgios Gousios
    errors = []
57 566cd8b2 Georgios Gousios
    valid_inv = filter(lambda x: x.startswith("name_"), request.POST.keys())
58 566cd8b2 Georgios Gousios
59 566cd8b2 Georgios Gousios
    for inv in valid_inv:
60 566cd8b2 Georgios Gousios
        (name, inv_id) = inv.split('_')
61 566cd8b2 Georgios Gousios
62 05310288 Georgios Gousios
        email = ""
63 05310288 Georgios Gousios
        name = ""
64 566cd8b2 Georgios Gousios
        try:
65 566cd8b2 Georgios Gousios
            email = request.POST['email_' + inv_id]
66 566cd8b2 Georgios Gousios
            name = request.POST[inv]
67 566cd8b2 Georgios Gousios
68 566cd8b2 Georgios Gousios
            validate_name(name)
69 a640b50d Georgios Gousios
            validate_email(email)
70 a640b50d Georgios Gousios
71 e6d6603a Georgios Gousios
            inv = add_invitation(request.user, name, email)
72 3b09ff22 Georgios Gousios
            send_invitation(inv)
73 a640b50d Georgios Gousios
74 b6b88056 Kostas Papadimitriou
        except ValidationError as e:
75 b6b88056 Kostas Papadimitriou
            errors += [_("Invitation to %s <%s> not sent. Reason: %s") %
76 b6b88056 Kostas Papadimitriou
                       (name, email, e.messages[0])]
77 b6b88056 Kostas Papadimitriou
        except Exception:
78 b6b88056 Kostas Papadimitriou
            # generic error
79 b6b88056 Kostas Papadimitriou
            errors += [_("Invitation to %s <%s> cannot be sent.") % (name, email)]
80 a640b50d Georgios Gousios
81 a640b50d Georgios Gousios
    respose = None
82 a640b50d Georgios Gousios
    if errors:
83 a640b50d Georgios Gousios
        data = render_to_string('invitations.html',
84 a640b50d Georgios Gousios
                                {'invitations': invitations_for_user(request),
85 39ea29b0 Kostas Papadimitriou
                                    'errors': errors, 'ajax': request.is_ajax()},
86 a640b50d Georgios Gousios
                                context_instance=RequestContext(request))
87 a640b50d Georgios Gousios
        response =  HttpResponse(data)
88 a640b50d Georgios Gousios
    else:
89 a640b50d Georgios Gousios
        response = HttpResponseRedirect("/invitations/")
90 566cd8b2 Georgios Gousios
91 566cd8b2 Georgios Gousios
    return response
92 566cd8b2 Georgios Gousios
93 f1bb3880 Georgios Gousios
94 566cd8b2 Georgios Gousios
def validate_name(name):
95 566cd8b2 Georgios Gousios
    if name is None or name.strip() == '' :
96 b6b88056 Kostas Papadimitriou
        raise ValidationError(_("Name is empty"))
97 a640b50d Georgios Gousios
98 a640b50d Georgios Gousios
    if name.find(' ') is -1:
99 b6b88056 Kostas Papadimitriou
        raise ValidationError(_("Name must contain at least one space"))
100 03e70572 Georgios Gousios
101 566cd8b2 Georgios Gousios
    return True
102 566cd8b2 Georgios Gousios
103 f1bb3880 Georgios Gousios
104 566cd8b2 Georgios Gousios
def invitations_for_user(request):
105 566cd8b2 Georgios Gousios
    invitations = []
106 566cd8b2 Georgios Gousios
107 566cd8b2 Georgios Gousios
    for inv in Invitations.objects.filter(source = request.user):
108 e6d6603a Georgios Gousios
        invitation = {}
109 566cd8b2 Georgios Gousios
110 e6d6603a Georgios Gousios
        invitation['sourcename'] = inv.source.realname
111 e6d6603a Georgios Gousios
        invitation['source'] = inv.source.uniq
112 e6d6603a Georgios Gousios
        invitation['targetname'] = inv.target.realname
113 e6d6603a Georgios Gousios
        invitation['target'] = inv.target.uniq
114 e6d6603a Georgios Gousios
        invitation['accepted'] = inv.accepted
115 e6d6603a Georgios Gousios
        invitation['sent'] = inv.created
116 566cd8b2 Georgios Gousios
117 e6d6603a Georgios Gousios
        invitations.append(invitation)
118 566cd8b2 Georgios Gousios
119 e6d6603a Georgios Gousios
    return invitations
120 03e70572 Georgios Gousios
121 f1bb3880 Georgios Gousios
122 a640b50d Georgios Gousios
@csrf_protect
123 03e70572 Georgios Gousios
def inv_demux(request):
124 3b09ff22 Georgios Gousios
125 03e70572 Georgios Gousios
    if request.method == 'GET':
126 566cd8b2 Georgios Gousios
        data = render_to_string('invitations.html',
127 39ea29b0 Kostas Papadimitriou
                {'invitations': invitations_for_user(request), 'ajax': request.is_ajax()},
128 a640b50d Georgios Gousios
                                context_instance=RequestContext(request))
129 03e70572 Georgios Gousios
        return  HttpResponse(data)
130 03e70572 Georgios Gousios
    elif request.method == 'POST':
131 e6d6603a Georgios Gousios
        return process_form(request)
132 03e70572 Georgios Gousios
    else:
133 03e70572 Georgios Gousios
        method_not_allowed(request)
134 03e70572 Georgios Gousios
135 33e00f02 Georgios Gousios
136 3b09ff22 Georgios Gousios
def login(request):
137 3b09ff22 Georgios Gousios
138 3b09ff22 Georgios Gousios
    if not request.method == 'GET':
139 3b09ff22 Georgios Gousios
        method_not_allowed(request)
140 3b09ff22 Georgios Gousios
141 3b09ff22 Georgios Gousios
    key = request.GET['key']
142 3b09ff22 Georgios Gousios
143 3b09ff22 Georgios Gousios
    if key is None:
144 914502af Georgios Gousios
        return render_login_error("10", "Required key is missing")
145 3b09ff22 Georgios Gousios
146 3b09ff22 Georgios Gousios
    PADDING = '{'
147 3b09ff22 Georgios Gousios
148 914502af Georgios Gousios
    try :
149 914502af Georgios Gousios
        DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
150 914502af Georgios Gousios
        cipher = AES.new(settings.INVITATION_ENCR_KEY)
151 914502af Georgios Gousios
        decoded = DecodeAES(cipher, key)
152 914502af Georgios Gousios
    except Exception:
153 914502af Georgios Gousios
        return render_login_error("20", "Required key is invalid")
154 3b09ff22 Georgios Gousios
155 3b09ff22 Georgios Gousios
    users = SynnefoUser.objects.filter(auth_token = decoded)
156 3b09ff22 Georgios Gousios
157 3b09ff22 Georgios Gousios
    if users.count() is 0:
158 914502af Georgios Gousios
        return render_login_error("20", "Required key is invalid")
159 3b09ff22 Georgios Gousios
160 3b09ff22 Georgios Gousios
    user = users[0]
161 3b09ff22 Georgios Gousios
    invitations = Invitations.objects.filter(target = user)
162 3b09ff22 Georgios Gousios
163 3b09ff22 Georgios Gousios
    if invitations.count() is 0:
164 914502af Georgios Gousios
        return render_login_error("30", "Non-existent invitation")
165 f1bb3880 Georgios Gousios
166 3b09ff22 Georgios Gousios
    inv = invitations[0]
167 3b09ff22 Georgios Gousios
168 3b09ff22 Georgios Gousios
    valid = timedelta(days = settings.INVITATION_VALID_DAYS)
169 3b09ff22 Georgios Gousios
    valid_until = inv.created + valid
170 3b09ff22 Georgios Gousios
171 3b09ff22 Georgios Gousios
    if (time.time() -
172 3b09ff22 Georgios Gousios
        time.mktime(inv.created.timetuple()) -
173 3b09ff22 Georgios Gousios
        settings.INVITATION_VALID_DAYS * 3600) > 0:
174 914502af Georgios Gousios
        return render_login_error("40",
175 914502af Georgios Gousios
                                  "Invitation expired (was valid until %s)"%
176 914502af Georgios Gousios
                                  valid_until.strftime('%A, %d %B %Y'))
177 914502af Georgios Gousios
    #if inv.accepted == False:
178 914502af Georgios Gousios
    #    return render_login_error("60", "Invitation already accepted")
179 3b09ff22 Georgios Gousios
180 3b09ff22 Georgios Gousios
    inv.accepted = True
181 3b09ff22 Georgios Gousios
    inv.save()
182 3b09ff22 Georgios Gousios
183 914502af Georgios Gousios
    data = dict()
184 914502af Georgios Gousios
    data['user'] = user.realname
185 914502af Georgios Gousios
    data['url'] = settings.APP_INSTALL_URL
186 914502af Georgios Gousios
187 914502af Georgios Gousios
    welcome = render_to_string('welcome.html', {'data': data})
188 914502af Georgios Gousios
189 914502af Georgios Gousios
    response = HttpResponse(welcome)
190 3b09ff22 Georgios Gousios
191 3b09ff22 Georgios Gousios
    response.set_cookie('X-Auth-Token', value=user.auth_token,
192 3b09ff22 Georgios Gousios
                        expires = valid_until.strftime('%a, %d-%b-%Y %H:%M:%S %Z'),
193 3b09ff22 Georgios Gousios
                        path='/')
194 3b09ff22 Georgios Gousios
    response['X-Auth-Token'] = user.auth_token
195 914502af Georgios Gousios
    return response
196 914502af Georgios Gousios
197 914502af Georgios Gousios
198 914502af Georgios Gousios
def render_login_error(code, text):
199 914502af Georgios Gousios
    error = dict()
200 914502af Georgios Gousios
    error['id'] = code
201 914502af Georgios Gousios
    error['text'] = text
202 914502af Georgios Gousios
203 914502af Georgios Gousios
    data = render_to_string('error.html', {'error': error})
204 914502af Georgios Gousios
205 914502af Georgios Gousios
    response = HttpResponse(data)
206 3b09ff22 Georgios Gousios
    return response
207 3b09ff22 Georgios Gousios
208 3b09ff22 Georgios Gousios
209 3b09ff22 Georgios Gousios
def send_invitation(invitation):
210 e6d6603a Georgios Gousios
    email = {}
211 e6d6603a Georgios Gousios
    email['invitee'] = invitation.target.realname
212 e6d6603a Georgios Gousios
    email['inviter'] = invitation.source.realname
213 e6d6603a Georgios Gousios
214 e6d6603a Georgios Gousios
    valid = timedelta(days = settings.INVITATION_VALID_DAYS)
215 e6d6603a Georgios Gousios
    valid_until = invitation.created + valid
216 e6d6603a Georgios Gousios
    email['valid_until'] = valid_until.strftime('%A, %d %B %Y')
217 e6d6603a Georgios Gousios
218 e6d6603a Georgios Gousios
    PADDING = '{'
219 e6d6603a Georgios Gousios
    pad = lambda s: s + (32 - len(s) % 32) * PADDING
220 e6d6603a Georgios Gousios
    EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
221 e6d6603a Georgios Gousios
222 e6d6603a Georgios Gousios
    cipher = AES.new(settings.INVITATION_ENCR_KEY)
223 e6d6603a Georgios Gousios
    encoded = EncodeAES(cipher, invitation.target.auth_token)
224 e6d6603a Georgios Gousios
225 3b09ff22 Georgios Gousios
    url_safe = urllib.urlencode({'key': encoded})
226 3b09ff22 Georgios Gousios
227 3b09ff22 Georgios Gousios
    email['url'] = settings.APP_INSTALL_URL + "/invitations/login?" + url_safe
228 e6d6603a Georgios Gousios
229 e6d6603a Georgios Gousios
    data = render_to_string('invitation.txt', {'email': email})
230 3b09ff22 Georgios Gousios
231 3b09ff22 Georgios Gousios
    print data
232 3b09ff22 Georgios Gousios
233 f1bb3880 Georgios Gousios
    send_async(
234 f1bb3880 Georgios Gousios
        frm = "%s <%s>"%(invitation.source.realname,invitation.source.uniq),
235 f1bb3880 Georgios Gousios
        to = "%s <%s>"%(invitation.target.realname,invitation.target.uniq),
236 f1bb3880 Georgios Gousios
        subject = u'Πρόσκληση για την υπηρεσία Ωκεανός',
237 f1bb3880 Georgios Gousios
        body = data
238 f1bb3880 Georgios Gousios
    )
239 f1bb3880 Georgios Gousios
240 e6d6603a Georgios Gousios
241 03e70572 Georgios Gousios
@transaction.commit_on_success
242 03e70572 Georgios Gousios
def add_invitation(source, name, email):
243 03e70572 Georgios Gousios
    """
244 03e70572 Georgios Gousios
        Adds an invitation, if the source user has not gone over his/her
245 33e00f02 Georgios Gousios
        invitation limit or the target user has not been invited already
246 03e70572 Georgios Gousios
    """
247 03e70572 Georgios Gousios
    num_inv = Invitations.objects.filter(source = source).count()
248 03e70572 Georgios Gousios
249 33e00f02 Georgios Gousios
    if num_inv >= source.max_invitations:
250 d652580d Georgios Gousios
        raise TooManyInvitations("User invitation limit (%d) exhausted" %
251 33e00f02 Georgios Gousios
                                 source.max_invitations)
252 03e70572 Georgios Gousios
253 f1bb3880 Georgios Gousios
    target = SynnefoUser.objects.filter(uniq = email)
254 03e70572 Georgios Gousios
255 03e70572 Georgios Gousios
    if target.count() is not 0:
256 f1bb3880 Georgios Gousios
        raise AlreadyInvited("User with email %s already invited" % (email))
257 03e70572 Georgios Gousios
258 53e6717b Georgios Gousios
    users.register_user(name, email)
259 53e6717b Georgios Gousios
260 53e6717b Georgios Gousios
    target = SynnefoUser.objects.filter(uniq = email)
261 53e6717b Georgios Gousios
262 53e6717b Georgios Gousios
    r = list(target[:1])
263 53e6717b Georgios Gousios
    if not r:
264 bd1548a7 Georgios Gousios
        raise Exception("Invited user cannot be added")
265 03e70572 Georgios Gousios
266 03e70572 Georgios Gousios
    inv = Invitations()
267 03e70572 Georgios Gousios
    inv.source = source
268 53e6717b Georgios Gousios
    inv.target = target[0]
269 03e70572 Georgios Gousios
    inv.save()
270 e6d6603a Georgios Gousios
    return inv
271 03e70572 Georgios Gousios
272 f1bb3880 Georgios Gousios
273 03e70572 Georgios Gousios
@transaction.commit_on_success
274 03e70572 Georgios Gousios
def invitation_accepted(invitation):
275 03e70572 Georgios Gousios
    """
276 03e70572 Georgios Gousios
        Mark an invitation as accepted
277 03e70572 Georgios Gousios
    """
278 03e70572 Georgios Gousios
    invitation.accepted = True
279 03e70572 Georgios Gousios
    invitation.save()
280 03e70572 Georgios Gousios
281 03e70572 Georgios Gousios
282 a640b50d Georgios Gousios
class TooManyInvitations(Exception):
283 f1bb3880 Georgios Gousios
    messages = []
284 a640b50d Georgios Gousios
285 a640b50d Georgios Gousios
    def __init__(self, msg):
286 a640b50d Georgios Gousios
        self.messages.append(msg)
287 03e70572 Georgios Gousios
288 03e70572 Georgios Gousios
289 a640b50d Georgios Gousios
class AlreadyInvited(Exception):
290 a640b50d Georgios Gousios
    messages = []
291 03e70572 Georgios Gousios
292 03e70572 Georgios Gousios
    def __init__(self, msg):
293 a640b50d Georgios Gousios
        self.messages.append(msg)