Statistics
| Branch: | Tag: | Revision:

root / invitations / invitations.py @ 33e00f02

History | View | Annotate | Download (7.4 kB)

1
# vim: set fileencoding=utf-8 :
2
from datetime import timedelta
3
import base64
4
import time
5
import urllib
6

    
7
from django.conf import settings
8
from django.core.exceptions import ValidationError
9
from django.db import transaction
10
from django.http import HttpResponse, HttpResponseRedirect
11
from django.template.context import RequestContext
12
from django.template.loader import render_to_string
13
from django.core.validators import validate_email
14
from django.views.decorators.csrf import csrf_protect
15
from synnefo.logic.email_send import send_async
16

    
17
from synnefo.api.common import method_not_allowed
18
from synnefo.db.models import Invitations, SynnefoUser
19
from synnefo.logic import users
20

    
21
from Crypto.Cipher import AES
22

    
23

    
24
def process_form(request):
25
    errors = []
26
    valid_inv = filter(lambda x: x.startswith("name_"), request.POST.keys())
27

    
28
    for inv in valid_inv:
29
        (name, inv_id) = inv.split('_')
30

    
31
        email = ""
32
        name = ""
33
        try:
34
            email = request.POST['email_' + inv_id]
35
            name = request.POST[inv]
36

    
37
            validate_name(name)
38
            validate_email(email)
39

    
40
            inv = add_invitation(request.user, name, email)
41
            send_invitation(inv)
42

    
43
        except Exception as e:
44
            try :
45
                errors += ["Invitation to %s <%s> not sent. Reason: %s" %
46
                           (name, email, e.messages[0])]
47
            except:
48
                errors += ["Invitation to %s <%s> not sent. Reason: %s" %
49
                           (name, email, e.message)]
50

    
51
    respose = None
52
    if errors:
53
        data = render_to_string('invitations.html',
54
                                {'invitations': invitations_for_user(request),
55
                                 'errors': errors},
56
                                context_instance=RequestContext(request))
57
        response =  HttpResponse(data)
58
    else:
59
        response = HttpResponseRedirect("/invitations/")
60

    
61
    return response
62

    
63

    
64
def validate_name(name):
65
    if name is None or name.strip() == '' :
66
        raise ValidationError("Name is empty")
67

    
68
    if name.find(' ') is -1:
69
        raise ValidationError("Name must contain at least one space")
70

    
71
    return True
72

    
73

    
74
def invitations_for_user(request):
75
    invitations = []
76

    
77
    for inv in Invitations.objects.filter(source = request.user):
78
        invitation = {}
79

    
80
        invitation['sourcename'] = inv.source.realname
81
        invitation['source'] = inv.source.uniq
82
        invitation['targetname'] = inv.target.realname
83
        invitation['target'] = inv.target.uniq
84
        invitation['accepted'] = inv.accepted
85
        invitation['sent'] = inv.created
86

    
87
        invitations.append(invitation)
88

    
89
    return invitations
90

    
91

    
92
@csrf_protect
93
def inv_demux(request):
94

    
95
    if request.method == 'GET':
96
        data = render_to_string('invitations.html',
97
                                {'invitations': invitations_for_user(request)},
98
                                context_instance=RequestContext(request))
99
        return  HttpResponse(data)
100
    elif request.method == 'POST':
101
        return process_form(request)
102
    else:
103
        method_not_allowed(request)
104

    
105

    
106
def login(request):
107

    
108
    if not request.method == 'GET':
109
        method_not_allowed(request)
110

    
111
    key = request.GET['key']
112

    
113
    if key is None:
114
        return render_login_error("10", "Required key is missing")
115

    
116
    PADDING = '{'
117

    
118
    try :
119
        DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
120
        cipher = AES.new(settings.INVITATION_ENCR_KEY)
121
        decoded = DecodeAES(cipher, key)
122
    except Exception:
123
        return render_login_error("20", "Required key is invalid")
124

    
125
    users = SynnefoUser.objects.filter(auth_token = decoded)
126

    
127
    if users.count() is 0:
128
        return render_login_error("20", "Required key is invalid")
129

    
130
    user = users[0]
131
    invitations = Invitations.objects.filter(target = user)
132

    
133
    if invitations.count() is 0:
134
        return render_login_error("30", "Non-existent invitation")
135

    
136
    inv = invitations[0]
137

    
138
    valid = timedelta(days = settings.INVITATION_VALID_DAYS)
139
    valid_until = inv.created + valid
140

    
141
    if (time.time() -
142
        time.mktime(inv.created.timetuple()) -
143
        settings.INVITATION_VALID_DAYS * 3600) > 0:
144
        return render_login_error("40",
145
                                  "Invitation expired (was valid until %s)"%
146
                                  valid_until.strftime('%A, %d %B %Y'))
147
    #if inv.accepted == False:
148
    #    return render_login_error("60", "Invitation already accepted")
149

    
150
    inv.accepted = True
151
    inv.save()
152

    
153
    data = dict()
154
    data['user'] = user.realname
155
    data['url'] = settings.APP_INSTALL_URL
156

    
157
    welcome = render_to_string('welcome.html', {'data': data})
158

    
159
    response = HttpResponse(welcome)
160

    
161
    response.set_cookie('X-Auth-Token', value=user.auth_token,
162
                        expires = valid_until.strftime('%a, %d-%b-%Y %H:%M:%S %Z'),
163
                        path='/')
164
    response['X-Auth-Token'] = user.auth_token
165
    return response
166

    
167

    
168
def render_login_error(code, text):
169
    error = dict()
170
    error['id'] = code
171
    error['text'] = text
172

    
173
    data = render_to_string('error.html', {'error': error})
174

    
175
    response = HttpResponse(data)
176
    return response
177

    
178

    
179
def send_invitation(invitation):
180
    email = {}
181
    email['invitee'] = invitation.target.realname
182
    email['inviter'] = invitation.source.realname
183

    
184
    valid = timedelta(days = settings.INVITATION_VALID_DAYS)
185
    valid_until = invitation.created + valid
186
    email['valid_until'] = valid_until.strftime('%A, %d %B %Y')
187

    
188
    PADDING = '{'
189
    pad = lambda s: s + (32 - len(s) % 32) * PADDING
190
    EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
191

    
192
    cipher = AES.new(settings.INVITATION_ENCR_KEY)
193
    encoded = EncodeAES(cipher, invitation.target.auth_token)
194

    
195
    url_safe = urllib.urlencode({'key': encoded})
196

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

    
199
    data = render_to_string('invitation.txt', {'email': email})
200

    
201
    print data
202

    
203
    send_async(
204
        frm = "%s <%s>"%(invitation.source.realname,invitation.source.uniq),
205
        to = "%s <%s>"%(invitation.target.realname,invitation.target.uniq),
206
        subject = u'Πρόσκληση για την υπηρεσία Ωκεανός',
207
        body = data
208
    )
209

    
210

    
211
@transaction.commit_on_success
212
def add_invitation(source, name, email):
213
    """
214
        Adds an invitation, if the source user has not gone over his/her
215
        invitation limit or the target user has not been invited already
216
    """
217
    num_inv = Invitations.objects.filter(source = source).count()
218

    
219
    if num_inv >= source.max_invitations:
220
        raise TooManyInvitations("User invitation limit (%d) exhausted" %
221
                                 source.max_invitations)
222

    
223
    target = SynnefoUser.objects.filter(uniq = email)
224

    
225
    if target.count() is not 0:
226
        raise AlreadyInvited("User with email %s already invited" % (email))
227

    
228
    users.register_user(name, email)
229

    
230
    target = SynnefoUser.objects.filter(uniq = email)
231

    
232
    r = list(target[:1])
233
    if not r:
234
        raise Exception("Invited user cannot be added")
235

    
236
    inv = Invitations()
237
    inv.source = source
238
    inv.target = target[0]
239
    inv.save()
240
    return inv
241

    
242

    
243
@transaction.commit_on_success
244
def invitation_accepted(invitation):
245
    """
246
        Mark an invitation as accepted
247
    """
248
    invitation.accepted = True
249
    invitation.save()
250

    
251

    
252
class TooManyInvitations(Exception):
253
    messages = []
254

    
255
    def __init__(self, msg):
256
        self.messages.append(msg)
257

    
258

    
259
class AlreadyInvited(Exception):
260
    messages = []
261

    
262
    def __init__(self, msg):
263
        self.messages.append(msg)