Statistics
| Branch: | Tag: | Revision:

root / invitations / invitations.py @ 68a77896

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