Statistics
| Branch: | Tag: | Revision:

root / invitations / invitations.py @ d53bbf68

History | View | Annotate | Download (10.3 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 566cd8b2 Georgios Gousios
47 d028ab18 Georgios Gousios
from synnefo.logic.email_send import send_async
48 03e70572 Georgios Gousios
from synnefo.api.common import method_not_allowed
49 03e70572 Georgios Gousios
from synnefo.db.models import Invitations, SynnefoUser
50 d028ab18 Georgios Gousios
from synnefo.logic import users, log
51 e6d6603a Georgios Gousios
52 e6d6603a Georgios Gousios
from Crypto.Cipher import AES
53 03e70572 Georgios Gousios
54 d028ab18 Georgios Gousios
_logger = log.get_logger("synnefo.invitations")
55 f1bb3880 Georgios Gousios
56 e6d6603a Georgios Gousios
def process_form(request):
57 a640b50d Georgios Gousios
    errors = []
58 566cd8b2 Georgios Gousios
    valid_inv = filter(lambda x: x.startswith("name_"), request.POST.keys())
59 566cd8b2 Georgios Gousios
60 566cd8b2 Georgios Gousios
    for inv in valid_inv:
61 566cd8b2 Georgios Gousios
        (name, inv_id) = inv.split('_')
62 566cd8b2 Georgios Gousios
63 05310288 Georgios Gousios
        email = ""
64 05310288 Georgios Gousios
        name = ""
65 566cd8b2 Georgios Gousios
        try:
66 566cd8b2 Georgios Gousios
            email = request.POST['email_' + inv_id]
67 566cd8b2 Georgios Gousios
            name = request.POST[inv]
68 566cd8b2 Georgios Gousios
69 566cd8b2 Georgios Gousios
            validate_name(name)
70 a640b50d Georgios Gousios
            validate_email(email)
71 a640b50d Georgios Gousios
72 e6d6603a Georgios Gousios
            inv = add_invitation(request.user, name, email)
73 3b09ff22 Georgios Gousios
            send_invitation(inv)
74 a640b50d Georgios Gousios
75 d028ab18 Georgios Gousios
        # FIXME: Delete invitation and user on error
76 d028ab18 Georgios Gousios
        except (InvitationException, ValidationError) as e:
77 d028ab18 Georgios Gousios
            errors += ["Invitation to %s <%s> not sent. Reason: %s" %
78 b6b88056 Kostas Papadimitriou
                       (name, email, e.messages[0])]
79 566cd8b2 Georgios Gousios
        except Exception as e:
80 d028ab18 Georgios Gousios
            _logger.exception(e)
81 d028ab18 Georgios Gousios
            errors += ["Invitation to %s <%s> not sent. Reason: %s" %
82 d028ab18 Georgios Gousios
                       (name, email, e.message)]
83 a640b50d Georgios Gousios
84 a640b50d Georgios Gousios
    respose = None
85 a640b50d Georgios Gousios
    if errors:
86 a640b50d Georgios Gousios
        data = render_to_string('invitations.html',
87 a640b50d Georgios Gousios
                                {'invitations': invitations_for_user(request),
88 8f50f91c Kostas Papadimitriou
                                    'errors': errors,
89 eabbd5b7 Kostas Papadimitriou
                                    'ajax': True,
90 8f50f91c Kostas Papadimitriou
                                    'invitations_left': get_invitations_left(request.user)
91 8f50f91c Kostas Papadimitriou
                                },
92 a640b50d Georgios Gousios
                                context_instance=RequestContext(request))
93 a640b50d Georgios Gousios
        response =  HttpResponse(data)
94 25c36228 Georgios Gousios
        _logger.warn("Error adding invitation %s -> %s: %s"%(request.user.uniq,
95 25c36228 Georgios Gousios
                                                             email, errors))
96 a640b50d Georgios Gousios
    else:
97 a640b50d Georgios Gousios
        response = HttpResponseRedirect("/invitations/")
98 25c36228 Georgios Gousios
        _logger.info("Added invitation %s -> %s"%(request.user.uniq, email))
99 566cd8b2 Georgios Gousios
100 566cd8b2 Georgios Gousios
    return response
101 566cd8b2 Georgios Gousios
102 f1bb3880 Georgios Gousios
103 566cd8b2 Georgios Gousios
def validate_name(name):
104 68a77896 Georgios Gousios
    if name is None or name.strip() == '':
105 a640b50d Georgios Gousios
        raise ValidationError("Name is empty")
106 a640b50d Georgios Gousios
107 a640b50d Georgios Gousios
    if name.find(' ') is -1:
108 b6b88056 Kostas Papadimitriou
        raise ValidationError(_("Name must contain at least one space"))
109 03e70572 Georgios Gousios
110 566cd8b2 Georgios Gousios
    return True
111 566cd8b2 Georgios Gousios
112 f1bb3880 Georgios Gousios
113 566cd8b2 Georgios Gousios
def invitations_for_user(request):
114 566cd8b2 Georgios Gousios
    invitations = []
115 566cd8b2 Georgios Gousios
116 566cd8b2 Georgios Gousios
    for inv in Invitations.objects.filter(source = request.user):
117 e6d6603a Georgios Gousios
        invitation = {}
118 566cd8b2 Georgios Gousios
119 e6d6603a Georgios Gousios
        invitation['sourcename'] = inv.source.realname
120 e6d6603a Georgios Gousios
        invitation['source'] = inv.source.uniq
121 e6d6603a Georgios Gousios
        invitation['targetname'] = inv.target.realname
122 e6d6603a Georgios Gousios
        invitation['target'] = inv.target.uniq
123 e6d6603a Georgios Gousios
        invitation['accepted'] = inv.accepted
124 e6d6603a Georgios Gousios
        invitation['sent'] = inv.created
125 566cd8b2 Georgios Gousios
126 e6d6603a Georgios Gousios
        invitations.append(invitation)
127 566cd8b2 Georgios Gousios
128 e6d6603a Georgios Gousios
    return invitations
129 03e70572 Georgios Gousios
130 f1bb3880 Georgios Gousios
131 a640b50d Georgios Gousios
@csrf_protect
132 03e70572 Georgios Gousios
def inv_demux(request):
133 3b09ff22 Georgios Gousios
134 03e70572 Georgios Gousios
    if request.method == 'GET':
135 566cd8b2 Georgios Gousios
        data = render_to_string('invitations.html',
136 8f50f91c Kostas Papadimitriou
                {'invitations': invitations_for_user(request),
137 8f50f91c Kostas Papadimitriou
                    'ajax': request.is_ajax(),
138 8f50f91c Kostas Papadimitriou
                    'invitations_left': get_invitations_left(request.user)
139 8f50f91c Kostas Papadimitriou
                },
140 a640b50d Georgios Gousios
                                context_instance=RequestContext(request))
141 03e70572 Georgios Gousios
        return  HttpResponse(data)
142 03e70572 Georgios Gousios
    elif request.method == 'POST':
143 e6d6603a Georgios Gousios
        return process_form(request)
144 03e70572 Georgios Gousios
    else:
145 03e70572 Georgios Gousios
        method_not_allowed(request)
146 03e70572 Georgios Gousios
147 33e00f02 Georgios Gousios
148 3b09ff22 Georgios Gousios
def login(request):
149 3b09ff22 Georgios Gousios
150 3b09ff22 Georgios Gousios
    if not request.method == 'GET':
151 3b09ff22 Georgios Gousios
        method_not_allowed(request)
152 3b09ff22 Georgios Gousios
153 3b09ff22 Georgios Gousios
    key = request.GET['key']
154 3b09ff22 Georgios Gousios
155 3b09ff22 Georgios Gousios
    if key is None:
156 914502af Georgios Gousios
        return render_login_error("10", "Required key is missing")
157 3b09ff22 Georgios Gousios
158 3b09ff22 Georgios Gousios
    PADDING = '{'
159 3b09ff22 Georgios Gousios
160 68a77896 Georgios Gousios
    try:
161 914502af Georgios Gousios
        DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
162 914502af Georgios Gousios
        cipher = AES.new(settings.INVITATION_ENCR_KEY)
163 914502af Georgios Gousios
        decoded = DecodeAES(cipher, key)
164 914502af Georgios Gousios
    except Exception:
165 914502af Georgios Gousios
        return render_login_error("20", "Required key is invalid")
166 3b09ff22 Georgios Gousios
167 3b09ff22 Georgios Gousios
    users = SynnefoUser.objects.filter(auth_token = decoded)
168 3b09ff22 Georgios Gousios
169 3b09ff22 Georgios Gousios
    if users.count() is 0:
170 914502af Georgios Gousios
        return render_login_error("20", "Required key is invalid")
171 3b09ff22 Georgios Gousios
172 3b09ff22 Georgios Gousios
    user = users[0]
173 3b09ff22 Georgios Gousios
    invitations = Invitations.objects.filter(target = user)
174 3b09ff22 Georgios Gousios
175 3b09ff22 Georgios Gousios
    if invitations.count() is 0:
176 914502af Georgios Gousios
        return render_login_error("30", "Non-existent invitation")
177 f1bb3880 Georgios Gousios
178 3b09ff22 Georgios Gousios
    inv = invitations[0]
179 3b09ff22 Georgios Gousios
180 3b09ff22 Georgios Gousios
    valid = timedelta(days = settings.INVITATION_VALID_DAYS)
181 3b09ff22 Georgios Gousios
    valid_until = inv.created + valid
182 3b09ff22 Georgios Gousios
183 3b09ff22 Georgios Gousios
    if (time.time() -
184 3b09ff22 Georgios Gousios
        time.mktime(inv.created.timetuple()) -
185 3b09ff22 Georgios Gousios
        settings.INVITATION_VALID_DAYS * 3600) > 0:
186 914502af Georgios Gousios
        return render_login_error("40",
187 914502af Georgios Gousios
                                  "Invitation expired (was valid until %s)"%
188 914502af Georgios Gousios
                                  valid_until.strftime('%A, %d %B %Y'))
189 914502af Georgios Gousios
    #if inv.accepted == False:
190 914502af Georgios Gousios
    #    return render_login_error("60", "Invitation already accepted")
191 3b09ff22 Georgios Gousios
192 3b09ff22 Georgios Gousios
    inv.accepted = True
193 3b09ff22 Georgios Gousios
    inv.save()
194 3b09ff22 Georgios Gousios
195 25c36228 Georgios Gousios
    _logger.info("Invited user %s logged in"%(inv.target.uniq))
196 25c36228 Georgios Gousios
197 914502af Georgios Gousios
    data = dict()
198 914502af Georgios Gousios
    data['user'] = user.realname
199 914502af Georgios Gousios
    data['url'] = settings.APP_INSTALL_URL
200 914502af Georgios Gousios
201 914502af Georgios Gousios
    welcome = render_to_string('welcome.html', {'data': data})
202 914502af Georgios Gousios
203 914502af Georgios Gousios
    response = HttpResponse(welcome)
204 3b09ff22 Georgios Gousios
205 3b09ff22 Georgios Gousios
    response.set_cookie('X-Auth-Token', value=user.auth_token,
206 3b09ff22 Georgios Gousios
                        expires = valid_until.strftime('%a, %d-%b-%Y %H:%M:%S %Z'),
207 3b09ff22 Georgios Gousios
                        path='/')
208 3b09ff22 Georgios Gousios
    response['X-Auth-Token'] = user.auth_token
209 914502af Georgios Gousios
    return response
210 914502af Georgios Gousios
211 914502af Georgios Gousios
212 914502af Georgios Gousios
def render_login_error(code, text):
213 914502af Georgios Gousios
    error = dict()
214 914502af Georgios Gousios
    error['id'] = code
215 914502af Georgios Gousios
    error['text'] = text
216 914502af Georgios Gousios
217 914502af Georgios Gousios
    data = render_to_string('error.html', {'error': error})
218 914502af Georgios Gousios
219 914502af Georgios Gousios
    response = HttpResponse(data)
220 3b09ff22 Georgios Gousios
    return response
221 3b09ff22 Georgios Gousios
222 3b09ff22 Georgios Gousios
223 3b09ff22 Georgios Gousios
def send_invitation(invitation):
224 e6d6603a Georgios Gousios
    email = {}
225 e6d6603a Georgios Gousios
    email['invitee'] = invitation.target.realname
226 e6d6603a Georgios Gousios
    email['inviter'] = invitation.source.realname
227 e6d6603a Georgios Gousios
228 e6d6603a Georgios Gousios
    valid = timedelta(days = settings.INVITATION_VALID_DAYS)
229 e6d6603a Georgios Gousios
    valid_until = invitation.created + valid
230 e6d6603a Georgios Gousios
    email['valid_until'] = valid_until.strftime('%A, %d %B %Y')
231 e6d6603a Georgios Gousios
232 e6d6603a Georgios Gousios
    PADDING = '{'
233 e6d6603a Georgios Gousios
    pad = lambda s: s + (32 - len(s) % 32) * PADDING
234 e6d6603a Georgios Gousios
    EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
235 e6d6603a Georgios Gousios
236 e6d6603a Georgios Gousios
    cipher = AES.new(settings.INVITATION_ENCR_KEY)
237 e6d6603a Georgios Gousios
    encoded = EncodeAES(cipher, invitation.target.auth_token)
238 e6d6603a Georgios Gousios
239 3b09ff22 Georgios Gousios
    url_safe = urllib.urlencode({'key': encoded})
240 3b09ff22 Georgios Gousios
241 3b09ff22 Georgios Gousios
    email['url'] = settings.APP_INSTALL_URL + "/invitations/login?" + url_safe
242 e6d6603a Georgios Gousios
243 e6d6603a Georgios Gousios
    data = render_to_string('invitation.txt', {'email': email})
244 3b09ff22 Georgios Gousios
245 844ff4db Georgios Gousios
    _logger.debug("Invitation URL: %s" % email['url'])
246 844ff4db Georgios Gousios
247 f1bb3880 Georgios Gousios
    send_async(
248 3d3c58d7 Georgios Gousios
        frm = "%s"%(settings.DEFAULT_FROM_EMAIL),
249 f1bb3880 Georgios Gousios
        to = "%s <%s>"%(invitation.target.realname,invitation.target.uniq),
250 68a77896 Georgios Gousios
        subject = _('Invitation to IaaS service Okeanos'),
251 f1bb3880 Georgios Gousios
        body = data
252 f1bb3880 Georgios Gousios
    )
253 f1bb3880 Georgios Gousios
254 9f8ad4c5 Georgios Gousios
def get_invitee_level(source):
255 9f8ad4c5 Georgios Gousios
    return get_user_inv_level(source) + 1
256 9f8ad4c5 Georgios Gousios
257 9f8ad4c5 Georgios Gousios
258 9f8ad4c5 Georgios Gousios
def get_user_inv_level(u):
259 9f8ad4c5 Georgios Gousios
    inv = Invitations.objects.filter(target = u)
260 9f8ad4c5 Georgios Gousios
261 cf8482f2 Giorgos Verigakis
    if not inv:
262 9f8ad4c5 Georgios Gousios
        raise Exception("User without invitation", u)
263 9f8ad4c5 Georgios Gousios
264 9f8ad4c5 Georgios Gousios
    return inv[0].level
265 9f8ad4c5 Georgios Gousios
266 9f8ad4c5 Georgios Gousios
267 03e70572 Georgios Gousios
@transaction.commit_on_success
268 03e70572 Georgios Gousios
def add_invitation(source, name, email):
269 03e70572 Georgios Gousios
    """
270 03e70572 Georgios Gousios
        Adds an invitation, if the source user has not gone over his/her
271 33e00f02 Georgios Gousios
        invitation limit or the target user has not been invited already
272 03e70572 Georgios Gousios
    """
273 03e70572 Georgios Gousios
    num_inv = Invitations.objects.filter(source = source).count()
274 03e70572 Georgios Gousios
275 33e00f02 Georgios Gousios
    if num_inv >= source.max_invitations:
276 d652580d Georgios Gousios
        raise TooManyInvitations("User invitation limit (%d) exhausted" %
277 33e00f02 Georgios Gousios
                                 source.max_invitations)
278 03e70572 Georgios Gousios
279 f1bb3880 Georgios Gousios
    target = SynnefoUser.objects.filter(uniq = email)
280 03e70572 Georgios Gousios
281 03e70572 Georgios Gousios
    if target.count() is not 0:
282 f1bb3880 Georgios Gousios
        raise AlreadyInvited("User with email %s already invited" % (email))
283 03e70572 Georgios Gousios
284 53e6717b Georgios Gousios
    users.register_user(name, email)
285 53e6717b Georgios Gousios
286 53e6717b Georgios Gousios
    target = SynnefoUser.objects.filter(uniq = email)
287 53e6717b Georgios Gousios
288 53e6717b Georgios Gousios
    r = list(target[:1])
289 53e6717b Georgios Gousios
    if not r:
290 bd1548a7 Georgios Gousios
        raise Exception("Invited user cannot be added")
291 03e70572 Georgios Gousios
292 9f8ad4c5 Georgios Gousios
    u = target[0]
293 9f8ad4c5 Georgios Gousios
    invitee_level = get_invitee_level(source)
294 9f8ad4c5 Georgios Gousios
295 9f8ad4c5 Georgios Gousios
    u.max_invitations = settings.INVITATIONS_PER_LEVEL[invitee_level]
296 9f8ad4c5 Georgios Gousios
    u.save()
297 9f8ad4c5 Georgios Gousios
298 03e70572 Georgios Gousios
    inv = Invitations()
299 03e70572 Georgios Gousios
    inv.source = source
300 9f8ad4c5 Georgios Gousios
    inv.target = u
301 9f8ad4c5 Georgios Gousios
    inv.level = invitee_level
302 03e70572 Georgios Gousios
    inv.save()
303 e6d6603a Georgios Gousios
    return inv
304 03e70572 Georgios Gousios
305 f1bb3880 Georgios Gousios
306 03e70572 Georgios Gousios
@transaction.commit_on_success
307 03e70572 Georgios Gousios
def invitation_accepted(invitation):
308 03e70572 Georgios Gousios
    """
309 03e70572 Georgios Gousios
        Mark an invitation as accepted
310 03e70572 Georgios Gousios
    """
311 03e70572 Georgios Gousios
    invitation.accepted = True
312 03e70572 Georgios Gousios
    invitation.save()
313 03e70572 Georgios Gousios
314 8f50f91c Kostas Papadimitriou
315 8f50f91c Kostas Papadimitriou
def get_invitations_left(user):
316 8f50f91c Kostas Papadimitriou
    """
317 8f50f91c Kostas Papadimitriou
    Get user invitations left
318 8f50f91c Kostas Papadimitriou
    """
319 8f50f91c Kostas Papadimitriou
    num_inv = Invitations.objects.filter(source = user).count()
320 8f50f91c Kostas Papadimitriou
    return user.max_invitations - num_inv
321 8f50f91c Kostas Papadimitriou
322 d028ab18 Georgios Gousios
class InvitationException(Exception):
323 a640b50d Georgios Gousios
    def __init__(self, msg):
324 d53bbf68 Georgios Gousios
        self.messages = [msg]
325 03e70572 Georgios Gousios
326 d53bbf68 Georgios Gousios
class TooManyInvitations(InvitationException):
327 d53bbf68 Georgios Gousios
    pass
328 03e70572 Georgios Gousios
329 d028ab18 Georgios Gousios
class AlreadyInvited(InvitationException):
330 d53bbf68 Georgios Gousios
    pass