Statistics
| Branch: | Tag: | Revision:

root / invitations / invitations.py @ 7427d9a3

History | View | Annotate | Download (7.3 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
        try:
32
            email = request.POST['email_' + inv_id]
33
            name = request.POST[inv]
34

    
35
            validate_name(name)
36
            validate_email(email)
37

    
38
            inv = add_invitation(request.user, name, email)
39
            send_invitation(inv)
40

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

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

    
57
    return response
58

    
59

    
60
def validate_name(name):
61
    if name is None or name.strip() == '' :
62
        raise ValidationError("Name is empty")
63

    
64
    if name.find(' ') is -1:
65
        raise ValidationError("Name must contain at least one space")
66

    
67
    return True
68

    
69

    
70
def invitations_for_user(request):
71
    invitations = []
72

    
73
    for inv in Invitations.objects.filter(source = request.user):
74
        invitation = {}
75

    
76
        invitation['sourcename'] = inv.source.realname
77
        invitation['source'] = inv.source.uniq
78
        invitation['targetname'] = inv.target.realname
79
        invitation['target'] = inv.target.uniq
80
        invitation['accepted'] = inv.accepted
81
        invitation['sent'] = inv.created
82

    
83
        invitations.append(invitation)
84

    
85
    return invitations
86

    
87

    
88
@csrf_protect
89
def inv_demux(request):
90

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

    
101
def login(request):
102

    
103
    if not request.method == 'GET':
104
        method_not_allowed(request)
105

    
106
    key = request.GET['key']
107

    
108
    if key is None:
109
        return render_login_error("10", "Required key is missing")
110

    
111
    PADDING = '{'
112

    
113
    try :
114
        DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
115
        cipher = AES.new(settings.INVITATION_ENCR_KEY)
116
        decoded = DecodeAES(cipher, key)
117
    except Exception:
118
        return render_login_error("20", "Required key is invalid")
119

    
120
    users = SynnefoUser.objects.filter(auth_token = decoded)
121

    
122
    if users.count() is 0:
123
        return render_login_error("20", "Required key is invalid")
124

    
125
    user = users[0]
126
    invitations = Invitations.objects.filter(target = user)
127

    
128
    if invitations.count() is 0:
129
        return render_login_error("30", "Non-existent invitation")
130

    
131
    inv = invitations[0]
132

    
133
    valid = timedelta(days = settings.INVITATION_VALID_DAYS)
134
    valid_until = inv.created + valid
135

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

    
145
    inv.accepted = True
146
    inv.save()
147

    
148
    data = dict()
149
    data['user'] = user.realname
150
    data['url'] = settings.APP_INSTALL_URL
151

    
152
    welcome = render_to_string('welcome.html', {'data': data})
153

    
154
    response = HttpResponse(welcome)
155

    
156
    response.set_cookie('X-Auth-Token', value=user.auth_token,
157
                        expires = valid_until.strftime('%a, %d-%b-%Y %H:%M:%S %Z'),
158
                        path='/')
159
    response['X-Auth-Token'] = user.auth_token
160
    return response
161

    
162

    
163
def render_login_error(code, text):
164
    error = dict()
165
    error['id'] = code
166
    error['text'] = text
167

    
168
    data = render_to_string('error.html', {'error': error})
169

    
170
    response = HttpResponse(data)
171
    return response
172

    
173

    
174
def send_invitation(invitation):
175
    email = {}
176
    email['invitee'] = invitation.target.realname
177
    email['inviter'] = invitation.source.realname
178

    
179
    valid = timedelta(days = settings.INVITATION_VALID_DAYS)
180
    valid_until = invitation.created + valid
181
    email['valid_until'] = valid_until.strftime('%A, %d %B %Y')
182

    
183
    PADDING = '{'
184
    pad = lambda s: s + (32 - len(s) % 32) * PADDING
185
    EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
186

    
187
    cipher = AES.new(settings.INVITATION_ENCR_KEY)
188
    encoded = EncodeAES(cipher, invitation.target.auth_token)
189

    
190
    url_safe = urllib.urlencode({'key': encoded})
191

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

    
194
    data = render_to_string('invitation.txt', {'email': email})
195

    
196
    print data
197

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

    
205

    
206
@transaction.commit_on_success
207
def add_invitation(source, name, email):
208
    """
209
        Adds an invitation, if the source user has not gone over his/her
210
        invitation count or the target user has not been invited already
211
    """
212
    num_inv = Invitations.objects.filter(source = source).count()
213

    
214
    if num_inv >= settings.MAX_INVITATIONS:
215
        raise TooManyInvitations("User invitation limit (%d) exhausted" % settings.MAX_INVITATIONS)
216

    
217
    target = SynnefoUser.objects.filter(uniq = email)
218

    
219
    if target.count() is not 0:
220
        raise AlreadyInvited("User with email %s already invited" % (email))
221

    
222
    users.register_user(name, email)
223

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

    
226
    r = list(target[:1])
227
    if not r:
228
        raise Exception("Invited user cannot be added")
229

    
230
    inv = Invitations()
231
    inv.source = source
232
    inv.target = target[0]
233
    inv.save()
234
    return inv
235

    
236

    
237
@transaction.commit_on_success
238
def invitation_accepted(invitation):
239
    """
240
        Mark an invitation as accepted
241
    """
242
    invitation.accepted = True
243
    invitation.save()
244

    
245

    
246
class TooManyInvitations(Exception):
247
    messages = []
248

    
249
    def __init__(self, msg):
250
        self.messages.append(msg)
251

    
252

    
253
class AlreadyInvited(Exception):
254
    messages = []
255

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