Statistics
| Branch: | Tag: | Revision:

root / invitations / invitations.py @ 39ea29b0

History | View | Annotate | Download (9 kB)

1
# vim: set fileencoding=utf-8 :
2
# Copyright 2011 GRNET S.A. All rights reserved.
3
#
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions
6
# are met:
7
#
8
#   1. Redistributions of source code must retain the above copyright
9
#      notice, this list of conditions and the following disclaimer.
10
#
11
#  2. Redistributions in binary form must reproduce the above copyright
12
#     notice, this list of conditions and the following disclaimer in the
13
#     documentation and/or other materials provided with the distribution.
14
#
15
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
16
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
19
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25
# SUCH DAMAGE.
26
#
27
# The views and conclusions contained in the software and documentation are
28
# those of the authors and should not be interpreted as representing official
29
# policies, either expressed or implied, of GRNET S.A.
30

    
31

    
32
from datetime import timedelta
33
import base64
34
import time
35
import urllib
36

    
37
from django.conf import settings
38
from django.core.exceptions import ValidationError
39
from django.db import transaction
40
from django.http import HttpResponse, HttpResponseRedirect
41
from django.template.context import RequestContext
42
from django.template.loader import render_to_string
43
from django.core.validators import validate_email
44
from django.views.decorators.csrf import csrf_protect
45
from synnefo.logic.email_send import send_async
46

    
47
from synnefo.api.common import method_not_allowed
48
from synnefo.db.models import Invitations, SynnefoUser
49
from synnefo.logic import users
50

    
51
from Crypto.Cipher import AES
52

    
53

    
54
def process_form(request):
55
    errors = []
56
    valid_inv = filter(lambda x: x.startswith("name_"), request.POST.keys())
57

    
58
    for inv in valid_inv:
59
        (name, inv_id) = inv.split('_')
60

    
61
        email = ""
62
        name = ""
63
        try:
64
            email = request.POST['email_' + inv_id]
65
            name = request.POST[inv]
66

    
67
            validate_name(name)
68
            validate_email(email)
69

    
70
            inv = add_invitation(request.user, name, email)
71
            send_invitation(inv)
72

    
73
        except Exception as e:
74
            try :
75
                errors += ["Invitation to %s <%s> not sent. Reason: %s" %
76
                           (name, email, e.messages[0])]
77
            except:
78
                errors += ["Invitation to %s <%s> not sent. Reason: %s" %
79
                           (name, email, e.message)]
80

    
81
    respose = None
82
    if errors:
83
        data = render_to_string('invitations.html',
84
                                {'invitations': invitations_for_user(request),
85
                                    'errors': errors, 'ajax': request.is_ajax()},
86
                                context_instance=RequestContext(request))
87
        response =  HttpResponse(data)
88
    else:
89
        response = HttpResponseRedirect("/invitations/")
90

    
91
    return response
92

    
93

    
94
def validate_name(name):
95
    if name is None or name.strip() == '' :
96
        raise ValidationError("Name is empty")
97

    
98
    if name.find(' ') is -1:
99
        raise ValidationError("Name must contain at least one space")
100

    
101
    return True
102

    
103

    
104
def invitations_for_user(request):
105
    invitations = []
106

    
107
    for inv in Invitations.objects.filter(source = request.user):
108
        invitation = {}
109

    
110
        invitation['sourcename'] = inv.source.realname
111
        invitation['source'] = inv.source.uniq
112
        invitation['targetname'] = inv.target.realname
113
        invitation['target'] = inv.target.uniq
114
        invitation['accepted'] = inv.accepted
115
        invitation['sent'] = inv.created
116

    
117
        invitations.append(invitation)
118

    
119
    return invitations
120

    
121

    
122
@csrf_protect
123
def inv_demux(request):
124

    
125
    if request.method == 'GET':
126
        data = render_to_string('invitations.html',
127
                {'invitations': invitations_for_user(request), 'ajax': request.is_ajax()},
128
                                context_instance=RequestContext(request))
129
        return  HttpResponse(data)
130
    elif request.method == 'POST':
131
        return process_form(request)
132
    else:
133
        method_not_allowed(request)
134

    
135

    
136
def login(request):
137

    
138
    if not request.method == 'GET':
139
        method_not_allowed(request)
140

    
141
    key = request.GET['key']
142

    
143
    if key is None:
144
        return render_login_error("10", "Required key is missing")
145

    
146
    PADDING = '{'
147

    
148
    try :
149
        DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
150
        cipher = AES.new(settings.INVITATION_ENCR_KEY)
151
        decoded = DecodeAES(cipher, key)
152
    except Exception:
153
        return render_login_error("20", "Required key is invalid")
154

    
155
    users = SynnefoUser.objects.filter(auth_token = decoded)
156

    
157
    if users.count() is 0:
158
        return render_login_error("20", "Required key is invalid")
159

    
160
    user = users[0]
161
    invitations = Invitations.objects.filter(target = user)
162

    
163
    if invitations.count() is 0:
164
        return render_login_error("30", "Non-existent invitation")
165

    
166
    inv = invitations[0]
167

    
168
    valid = timedelta(days = settings.INVITATION_VALID_DAYS)
169
    valid_until = inv.created + valid
170

    
171
    if (time.time() -
172
        time.mktime(inv.created.timetuple()) -
173
        settings.INVITATION_VALID_DAYS * 3600) > 0:
174
        return render_login_error("40",
175
                                  "Invitation expired (was valid until %s)"%
176
                                  valid_until.strftime('%A, %d %B %Y'))
177
    #if inv.accepted == False:
178
    #    return render_login_error("60", "Invitation already accepted")
179

    
180
    inv.accepted = True
181
    inv.save()
182

    
183
    data = dict()
184
    data['user'] = user.realname
185
    data['url'] = settings.APP_INSTALL_URL
186

    
187
    welcome = render_to_string('welcome.html', {'data': data})
188

    
189
    response = HttpResponse(welcome)
190

    
191
    response.set_cookie('X-Auth-Token', value=user.auth_token,
192
                        expires = valid_until.strftime('%a, %d-%b-%Y %H:%M:%S %Z'),
193
                        path='/')
194
    response['X-Auth-Token'] = user.auth_token
195
    return response
196

    
197

    
198
def render_login_error(code, text):
199
    error = dict()
200
    error['id'] = code
201
    error['text'] = text
202

    
203
    data = render_to_string('error.html', {'error': error})
204

    
205
    response = HttpResponse(data)
206
    return response
207

    
208

    
209
def send_invitation(invitation):
210
    email = {}
211
    email['invitee'] = invitation.target.realname
212
    email['inviter'] = invitation.source.realname
213

    
214
    valid = timedelta(days = settings.INVITATION_VALID_DAYS)
215
    valid_until = invitation.created + valid
216
    email['valid_until'] = valid_until.strftime('%A, %d %B %Y')
217

    
218
    PADDING = '{'
219
    pad = lambda s: s + (32 - len(s) % 32) * PADDING
220
    EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
221

    
222
    cipher = AES.new(settings.INVITATION_ENCR_KEY)
223
    encoded = EncodeAES(cipher, invitation.target.auth_token)
224

    
225
    url_safe = urllib.urlencode({'key': encoded})
226

    
227
    email['url'] = settings.APP_INSTALL_URL + "/invitations/login?" + url_safe
228

    
229
    data = render_to_string('invitation.txt', {'email': email})
230

    
231
    print data
232

    
233
    send_async(
234
        frm = "%s <%s>"%(invitation.source.realname,invitation.source.uniq),
235
        to = "%s <%s>"%(invitation.target.realname,invitation.target.uniq),
236
        subject = u'Πρόσκληση για την υπηρεσία Ωκεανός',
237
        body = data
238
    )
239

    
240

    
241
@transaction.commit_on_success
242
def add_invitation(source, name, email):
243
    """
244
        Adds an invitation, if the source user has not gone over his/her
245
        invitation limit or the target user has not been invited already
246
    """
247
    num_inv = Invitations.objects.filter(source = source).count()
248

    
249
    if num_inv >= source.max_invitations:
250
        raise TooManyInvitations("User invitation limit (%d) exhausted" %
251
                                 source.max_invitations)
252

    
253
    target = SynnefoUser.objects.filter(uniq = email)
254

    
255
    if target.count() is not 0:
256
        raise AlreadyInvited("User with email %s already invited" % (email))
257

    
258
    users.register_user(name, email)
259

    
260
    target = SynnefoUser.objects.filter(uniq = email)
261

    
262
    r = list(target[:1])
263
    if not r:
264
        raise Exception("Invited user cannot be added")
265

    
266
    inv = Invitations()
267
    inv.source = source
268
    inv.target = target[0]
269
    inv.save()
270
    return inv
271

    
272

    
273
@transaction.commit_on_success
274
def invitation_accepted(invitation):
275
    """
276
        Mark an invitation as accepted
277
    """
278
    invitation.accepted = True
279
    invitation.save()
280

    
281

    
282
class TooManyInvitations(Exception):
283
    messages = []
284

    
285
    def __init__(self, msg):
286
        self.messages.append(msg)
287

    
288

    
289
class AlreadyInvited(Exception):
290
    messages = []
291

    
292
    def __init__(self, msg):
293
        self.messages.append(msg)