Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / tests.py @ 34a76cdb

History | View | Annotate | Download (29.2 kB)

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

    
34
import datetime
35

    
36
from django.test import TestCase, Client
37
from django.conf import settings
38
from django.core import mail
39

    
40
from astakos.im.target.shibboleth import Tokens as ShibbolethTokens
41
from astakos.im.models import *
42
from astakos.im import functions
43
from astakos.im import settings as astakos_settings
44
from astakos.im import forms
45

    
46
from urllib import quote
47

    
48
from astakos.im import messages
49

    
50

    
51
astakos_settings.EMAILCHANGE_ENABLED = True
52

    
53
class ShibbolethClient(Client):
54
    """
55
    A shibboleth agnostic client.
56
    """
57
    VALID_TOKENS = filter(lambda x: not x.startswith("_"), dir(ShibbolethTokens))
58

    
59
    def __init__(self, *args, **kwargs):
60
        self.tokens = kwargs.pop('tokens', {})
61
        super(ShibbolethClient, self).__init__(*args, **kwargs)
62

    
63
    def set_tokens(self, **kwargs):
64
        for key, value in kwargs.iteritems():
65
            key = 'SHIB_%s' % key.upper()
66
            if not key in self.VALID_TOKENS:
67
                raise Exception('Invalid shibboleth token')
68

    
69
            self.tokens[key] = value
70

    
71
    def unset_tokens(self, *keys):
72
        for key in keys:
73
            key = 'SHIB_%s' % param.upper()
74
            if key in self.tokens:
75
                del self.tokens[key]
76

    
77
    def reset_tokens(self):
78
        self.tokens = {}
79

    
80
    def get_http_token(self, key):
81
        http_header = getattr(ShibbolethTokens, key)
82
        return http_header
83

    
84
    def request(self, **request):
85
        """
86
        Transform valid shibboleth tokens to http headers
87
        """
88
        for token, value in self.tokens.iteritems():
89
            request[self.get_http_token(token)] = value
90

    
91
        for param in request.keys():
92
            key = 'SHIB_%s' % param.upper()
93
            if key in self.VALID_TOKENS:
94
                request[self.get_http_token(key)] = request[param]
95
                del request[param]
96

    
97
        return super(ShibbolethClient, self).request(**request)
98

    
99

    
100
def get_local_user(username, **kwargs):
101
        try:
102
            return AstakosUser.objects.get(email=username)
103
        except:
104
            user_params = {
105
                'username': username,
106
                'email': username,
107
                'is_active': True,
108
                'activation_sent': datetime.now(),
109
                'email_verified': True,
110
                'provider': 'local'
111
            }
112
            user_params.update(kwargs)
113
            user = AstakosUser(**user_params)
114
            user.set_password(kwargs.get('password', 'password'))
115
            user.save()
116
            user.add_auth_provider('local', auth_backend='astakos')
117
            if kwargs.get('is_active', True):
118
                user.is_active = True
119
            else:
120
                user.is_active = False
121
            user.save()
122
            return user
123

    
124

    
125
def get_mailbox(email):
126
    mails = []
127
    for sent_email in mail.outbox:
128
        for recipient in sent_email.recipients():
129
            if email in recipient:
130
                mails.append(sent_email)
131
    return mails
132

    
133

    
134
class ShibbolethTests(TestCase):
135
    """
136
    Testing shibboleth authentication.
137
    """
138

    
139
    fixtures = ['groups']
140

    
141
    def setUp(self):
142
        self.client = ShibbolethClient()
143
        settings.ASTAKOS_IM_MODULES = ['local', 'shibboleth']
144
        settings.ASTAKOS_MODERATION_ENABLED = True
145

    
146
    def test_create_account(self):
147

    
148
        client = ShibbolethClient()
149

    
150
        # shibboleth views validation
151
        # eepn required
152
        r = client.get('/im/login/shibboleth?', follow=True)
153
        self.assertContains(r, messages.SHIBBOLETH_MISSING_EPPN)
154
        client.set_tokens(eppn="kpapeppn")
155

    
156
        astakos_settings.SHIBBOLETH_REQUIRE_NAME_INFO = True
157
        # shibboleth user info required
158
        r = client.get('/im/login/shibboleth?', follow=True)
159
        self.assertContains(r, messages.SHIBBOLETH_MISSING_NAME)
160
        astakos_settings.SHIBBOLETH_REQUIRE_NAME_INFO = False
161

    
162
        # shibboleth logged us in
163
        client.set_tokens(mail="kpap@grnet.gr", eppn="kpapeppn",
164
                          cn="Kostas Papadimitriou",
165
                          ep_affiliation="Test Affiliation")
166
        r = client.get('/im/login/shibboleth?')
167
        self.assertEqual(r.status_code, 200)
168

    
169
        # astakos asks if we want to add shibboleth
170
        self.assertContains(r, "Already have an account?")
171

    
172
        # a new pending user created
173
        pending_user = PendingThirdPartyUser.objects.get(
174
            third_party_identifier="kpapeppn")
175
        self.assertEqual(PendingThirdPartyUser.objects.count(), 1)
176
        # keep the token for future use
177
        token = pending_user.token
178
        # from now on no shibboleth headers are sent to the server
179
        client.reset_tokens()
180

    
181
        # this is the old way, it should fail, to avoid pending user take over
182
        r = client.get('/im/shibboleth/signup/%s' % pending_user.username)
183
        self.assertEqual(r.status_code, 404)
184

    
185
        # this is the signup unique url associated with the pending user created
186
        r = client.get('/im/signup/?third_party_token=%s' % token)
187
        form = r.context['form']
188
        post_data = {'third_party_identifier': pending_user.third_party_identifier,
189
                     'first_name': 'Kostas',
190
                     'third_party_token': token,
191
                     'last_name': 'Mitroglou',
192
                     'provider': 'shibboleth'
193
                    }
194

    
195
        # invlid email
196
        post_data['email'] = 'kpap'
197
        r = client.post('/im/signup', post_data)
198
        self.assertContains(r, token)
199

    
200
        # existing email
201
        existing_user = get_local_user('test@test.com')
202
        post_data['email'] = 'test@test.com'
203
        r = client.post('/im/signup', post_data)
204
        self.assertContains(r, messages.EMAIL_USED)
205
        existing_user.delete()
206

    
207
        # and finally a valid signup
208
        post_data['email'] = 'kpap@grnet.gr'
209
        r = client.post('/im/signup', post_data, follow=True)
210
        self.assertContains(r, messages.NOTIFICATION_SENT)
211

    
212
        # everything is ok in our db
213
        self.assertEqual(AstakosUser.objects.count(), 1)
214
        self.assertEqual(AstakosUserAuthProvider.objects.count(), 1)
215
        self.assertEqual(PendingThirdPartyUser.objects.count(), 0)
216

    
217
        # provider info stored
218
        provider = AstakosUserAuthProvider.objects.get(module="shibboleth")
219
        self.assertEqual(provider.affiliation, 'Test Affiliation')
220
        self.assertEqual(provider.info, {u'email': u'kpap@grnet.gr',
221
                                         u'eppn': u'kpapeppn',
222
                                         u'name': u'Kostas Papadimitriou'})
223

    
224
        # lets login (not activated yet)
225
        client.set_tokens(mail="kpap@grnet.gr", eppn="kpapeppn",
226
                          cn="Kostas Papadimitriou", )
227
        r = client.get("/im/login/shibboleth?", follow=True)
228
        self.assertContains(r, messages.ACCOUNT_PENDING_MODERATION)
229
        r = client.get("/im/profile", follow=True)
230
        self.assertRedirects(r, 'http://testserver/im/?next=%2Fim%2Fprofile')
231

    
232
        # admin activates our user
233
        u = AstakosUser.objects.get(username="kpap@grnet.gr")
234
        functions.activate(u)
235
        self.assertEqual(u.is_active, True)
236

    
237
        # we see our profile
238
        r = client.get("/im/login/shibboleth?", follow=True)
239
        self.assertRedirects(r, '/im/profile')
240
        self.assertEqual(r.status_code, 200)
241

    
242
    def test_existing(self):
243
        """
244
        Test adding of third party login to an existing account
245
        """
246

    
247
        # this is our existing user
248
        existing_user = get_local_user('kpap@grnet.gr')
249

    
250
        client = ShibbolethClient()
251
        # shibboleth logged us in, notice that we use different email
252
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn",
253
                          cn="Kostas Papadimitriou", )
254
        r = client.get("/im/login/shibboleth?")
255

    
256
        # astakos asks if we want to switch a local account to shibboleth
257
        self.assertContains(r, "Already have an account?")
258

    
259
        # a new pending user created
260
        pending_user = PendingThirdPartyUser.objects.get()
261
        self.assertEqual(PendingThirdPartyUser.objects.count(), 1)
262
        pending_key = pending_user.token
263
        client.reset_tokens()
264

    
265
        # we choose to add shibboleth to an our existing account
266
        # we get redirected to login page with the pending token set
267
        r = client.get('/im/login?key=%s' % pending_key)
268
        post_data = {'password': 'password',
269
                     'username': 'kpap@grnet.gr',
270
                     'key': pending_key}
271
        r = client.post('/im/local', post_data, follow=True)
272
        self.assertRedirects(r, "/im/profile")
273
        self.assertContains(r, messages.AUTH_PROVIDER_ADDED)
274

    
275
        self.assertTrue(existing_user.has_auth_provider('shibboleth'))
276
        self.assertTrue(existing_user.has_auth_provider('local',
277
                                                        auth_backend='astakos'))
278
        client.logout()
279

    
280
        # check that we cannot assign same third party provide twice
281
        r = client.get('/im/login?key=%s' % pending_key)
282
        post_data = {'password': 'password',
283
                     'username': 'kpap@grnet.gr',
284
                     'key': pending_key}
285
        r = self.client.post('/im/local', post_data, follow=True)
286
        self.assertContains(r, messages.AUTH_PROVIDER_ADD_FAILED)
287
        self.client.logout()
288
        client.logout()
289

    
290
        # look Ma, i can login with both my shibboleth and local account
291
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn",
292
                          cn="Kostas Papadimitriou")
293
        r = client.get("/im/login/shibboleth?", follow=True)
294
        self.assertTrue(r.context['request'].user.is_authenticated())
295
        self.assertTrue(r.context['request'].user.email == "kpap@grnet.gr")
296
        self.assertRedirects(r, '/im/profile')
297
        self.assertEqual(r.status_code, 200)
298
        client.logout()
299
        client.reset_tokens()
300

    
301
        # logged out
302
        r = client.get("/im/profile", follow=True)
303
        self.assertFalse(r.context['request'].user.is_authenticated())
304

    
305
        # login with local account also works
306
        post_data = {'password': 'password',
307
                     'username': 'kpap@grnet.gr'}
308
        r = self.client.post('/im/local', post_data, follow=True)
309
        self.assertTrue(r.context['request'].user.is_authenticated())
310
        self.assertTrue(r.context['request'].user.email == "kpap@grnet.gr")
311
        self.assertRedirects(r, '/im/profile')
312
        self.assertEqual(r.status_code, 200)
313

    
314
        # cannot add the same eppn
315
        client.set_tokens(mail="secondary@shibboleth.gr", eppn="kpapeppn",
316
                          cn="Kostas Papadimitriou", )
317
        r = client.get("/im/login/shibboleth?", follow=True)
318
        self.assertRedirects(r, '/im/profile')
319
        self.assertTrue(r.status_code, 200)
320
        self.assertEquals(existing_user.auth_providers.count(), 2)
321

    
322
        # but can add additional eppn
323
        client.set_tokens(mail="secondary@shibboleth.gr", eppn="kpapeppn2",
324
                          cn="Kostas Papadimitriou", ep_affiliation="affil2")
325
        r = client.get("/im/login/shibboleth?", follow=True)
326
        new_provider = existing_user.auth_providers.get(identifier="kpapeppn2")
327
        self.assertRedirects(r, '/im/profile')
328
        self.assertTrue(r.status_code, 200)
329
        self.assertEquals(existing_user.auth_providers.count(), 3)
330
        self.assertEqual(new_provider.affiliation, 'affil2')
331
        client.logout()
332
        client.reset_tokens()
333

    
334
        # cannot login with another eppn
335
        client.set_tokens(mail="kpap@grnet.gr", eppn="kpapeppninvalid",
336
                          cn="Kostas Papadimitriou")
337
        r = client.get("/im/login/shibboleth?", follow=True)
338
        self.assertFalse(r.context['request'].user.is_authenticated())
339

    
340
        # lets remove local password
341
        user = AstakosUser.objects.get(username="kpap@grnet.gr",
342
                                       email="kpap@grnet.gr")
343
        remove_local_url = user.get_provider_remove_url('local')
344
        remove_shibbo_url = user.get_provider_remove_url('shibboleth',
345
                                                         identifier='kpapeppn')
346
        remove_shibbo2_url = user.get_provider_remove_url('shibboleth',
347
                                                         identifier='kpapeppn2')
348
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn",
349
                          cn="Kostas Papadimtriou")
350
        r = client.get("/im/login/shibboleth?", follow=True)
351
        client.reset_tokens()
352

    
353
        # TODO: this view should use POST
354
        r = client.get(remove_local_url)
355
        # 2 providers left
356
        self.assertEqual(user.auth_providers.count(), 2)
357
        r = client.get(remove_shibbo2_url)
358
        # 1 provider left
359
        self.assertEqual(user.auth_providers.count(), 1)
360
        # cannot remove last provider
361
        r = client.get(remove_shibbo_url)
362
        self.assertEqual(r.status_code, 403)
363
        self.client.logout()
364

    
365
        # cannot login using local credentials (notice we use another client)
366
        post_data = {'password': 'password',
367
                     'username': 'kpap@grnet.gr'}
368
        r = self.client.post('/im/local', post_data, follow=True)
369
        self.assertFalse(r.context['request'].user.is_authenticated())
370

    
371
        # we can reenable the local provider by setting a password
372
        r = client.get("/im/password_change", follow=True)
373
        r = client.post("/im/password_change", {'new_password1':'111',
374
                                                'new_password2': '111'},
375
                        follow=True)
376
        user = r.context['request'].user
377
        self.assertTrue(user.has_auth_provider('local'))
378
        self.assertTrue(user.has_auth_provider('shibboleth'))
379
        self.assertTrue(user.check_password('111'))
380
        self.assertTrue(user.has_usable_password())
381
        self.client.logout()
382

    
383
        # now we can login
384
        post_data = {'password': '111',
385
                     'username': 'kpap@grnet.gr'}
386
        r = self.client.post('/im/local', post_data, follow=True)
387
        self.assertTrue(r.context['request'].user.is_authenticated())
388

    
389
        client.reset_tokens()
390

    
391
        # we cannot take over another shibboleth identifier
392
        user2 = get_local_user('another@grnet.gr')
393
        user2.add_auth_provider('shibboleth', identifier='existingeppn')
394
        # login
395
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn",
396
                          cn="Kostas Papadimitriou")
397
        r = client.get("/im/login/shibboleth?", follow=True)
398
        # try to assign existing shibboleth identifier of another user
399
        client.set_tokens(mail="kpap_second@shibboleth.gr", eppn="existingeppn",
400
                          cn="Kostas Papadimitriou")
401
        r = client.get("/im/login/shibboleth?", follow=True)
402
        self.assertContains(r, messages.AUTH_PROVIDER_ADD_FAILED)
403
        self.assertContains(r, messages.AUTH_PROVIDER_ADD_EXISTS)
404

    
405

    
406
class LocalUserTests(TestCase):
407

    
408
    fixtures = ['groups']
409

    
410
    def setUp(self):
411
        from django.conf import settings
412
        settings.ADMINS = (('admin', 'support@cloud.grnet.gr'),)
413
        settings.SERVER_EMAIL = 'no-reply@grnet.gr'
414

    
415
    def test_no_moderation(self):
416
        # disable moderation
417
        astakos_settings.MODERATION_ENABLED = False
418

    
419
        # create a new user
420
        r = self.client.get("/im/signup")
421
        self.assertEqual(r.status_code, 200)
422
        data = {'email':'kpap@grnet.gr', 'password1':'password',
423
                'password2':'password', 'first_name': 'Kostas',
424
                'last_name': 'Mitroglou', 'provider': 'local'}
425
        r = self.client.post("/im/signup", data)
426

    
427
        # user created
428
        self.assertEqual(AstakosUser.objects.count(), 1)
429
        user = AstakosUser.objects.get(username="kpap@grnet.gr",
430
                                       email="kpap@grnet.gr")
431
        self.assertEqual(user.username, 'kpap@grnet.gr')
432
        self.assertEqual(user.has_auth_provider('local'), True)
433
        self.assertFalse(user.is_active)
434

    
435
        # user (but not admin) gets notified
436
        self.assertEqual(len(get_mailbox('support@cloud.grnet.gr')), 0)
437
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
438
        astakos_settings.MODERATION_ENABLED = True
439

    
440
    def test_email_case(self):
441
        data = {
442
          'email': 'kPap@grnet.gr',
443
          'password1': '1234',
444
          'password2': '1234'
445
        }
446

    
447
        form = forms.LocalUserCreationForm(data)
448
        self.assertTrue(form.is_valid())
449
        user = form.save()
450
        form.store_user(user, {})
451

    
452
        u = AstakosUser.objects.get(pk=1)
453
        self.assertEqual(u.email, 'kPap@grnet.gr')
454
        self.assertEqual(u.username, 'kpap@grnet.gr')
455
        u.is_active = True
456
        u.email_verified = True
457
        u.save()
458

    
459
        data = {'username': 'kpap@grnet.gr', 'password': '1234'}
460
        login = forms.LoginForm(data=data)
461
        self.assertTrue(login.is_valid())
462

    
463
        data = {'username': 'KpaP@grnet.gr', 'password': '1234'}
464
        login = forms.LoginForm(data=data)
465
        self.assertTrue(login.is_valid())
466

    
467
        data = {
468
          'email': 'kpap@grnet.gr',
469
          'password1': '1234',
470
          'password2': '1234'
471
        }
472
        form = forms.LocalUserCreationForm(data)
473
        self.assertFalse(form.is_valid())
474

    
475
    def test_local_provider(self):
476
        # enable moderation
477
        astakos_settings.MODERATION_ENABLED = True
478

    
479
        # create a user
480
        r = self.client.get("/im/signup")
481
        self.assertEqual(r.status_code, 200)
482
        data = {'email':'kpap@grnet.gr', 'password1':'password',
483
                'password2':'password', 'first_name': 'Kostas',
484
                'last_name': 'Mitroglou', 'provider': 'local'}
485
        r = self.client.post("/im/signup", data)
486

    
487
        # user created
488
        self.assertEqual(AstakosUser.objects.count(), 1)
489
        user = AstakosUser.objects.get(username="kpap@grnet.gr",
490
                                       email="kpap@grnet.gr")
491
        self.assertEqual(user.username, 'kpap@grnet.gr')
492
        self.assertEqual(user.has_auth_provider('local'), True)
493
        self.assertFalse(user.is_active) # not activated
494
        self.assertFalse(user.email_verified) # not verified
495
        self.assertFalse(user.activation_sent) # activation automatically sent
496

    
497
        # admin gets notified and activates the user from the command line
498
        self.assertEqual(len(get_mailbox('support@cloud.grnet.gr')), 1)
499
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
500
                                                 'password': 'password'})
501
        self.assertContains(r, messages.ACCOUNT_PENDING_MODERATION)
502
        functions.send_activation(user)
503

    
504
        # user activation fields updated and user gets notified via email
505
        user = AstakosUser.objects.get(pk=user.pk)
506
        self.assertTrue(user.activation_sent)
507
        self.assertFalse(user.email_verified)
508
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
509

    
510
        # user forgot she got registered and tries to submit registration
511
        # form. Notice the upper case in email
512
        data = {'email':'KPAP@grnet.gr', 'password1':'password',
513
                'password2':'password', 'first_name': 'Kostas',
514
                'last_name': 'Mitroglou', 'provider': 'local'}
515
        r = self.client.post("/im/signup", data)
516
        self.assertContains(r, messages.EMAIL_USED)
517

    
518
        # hmmm, email exists; lets get the password
519
        r = self.client.get('/im/local/password_reset')
520
        self.assertEqual(r.status_code, 200)
521
        r = self.client.post('/im/local/password_reset', {'email':
522
                                                          'kpap@grnet.gr'})
523
        # she can't because account is not active yet
524
        self.assertContains(r, "doesn't have an associated user account")
525

    
526
        # moderation is enabled so no automatic activation can be send
527
        r = self.client.get('/im/send/activation/%d' % user.pk)
528
        self.assertEqual(r.status_code, 403)
529
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
530

    
531
        # also she cannot login
532
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
533
                                                 'password': 'password'})
534
        self.assertContains(r, messages.ACCOUNT_PENDING_ACTIVATION_HELP)
535
        self.assertContains(r, messages.ACCOUNT_PENDING_ACTIVATION)
536
        self.assertNotContains(r, 'Resend activation')
537
        self.assertFalse(r.context['request'].user.is_authenticated())
538
        self.assertFalse('_pithos2_a' in self.client.cookies)
539

    
540
        # same with disabled moderation
541
        astakos_settings.MODERATION_ENABLED = False
542
        r = self.client.post('/im/local/password_reset', {'email':
543
                                                          'kpap@grnet.gr'})
544
        self.assertContains(r, "doesn't have an associated user account")
545
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
546
                                                 'password': 'password'})
547
        self.assertContains(r, messages.ACCOUNT_PENDING_ACTIVATION)
548
        self.assertContains(r, 'Resend activation')
549
        self.assertFalse(r.context['request'].user.is_authenticated())
550
        self.assertFalse('_pithos2_a' in self.client.cookies)
551

    
552
        # user sees the message and resends activation
553
        r = self.client.get('/im/send/activation/%d' % user.pk)
554
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 2)
555

    
556
        # switch back moderation setting
557
        astakos_settings.MODERATION_ENABLED = True
558
        r = self.client.get(user.get_activation_url(), follow=True)
559
        self.assertRedirects(r, "/im/profile")
560
        self.assertContains(r, "kpap@grnet.gr")
561
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 3)
562

    
563
        user = AstakosUser.objects.get(pk=user.pk)
564
        # user activated and logged in, token cookie set
565
        self.assertTrue(r.context['request'].user.is_authenticated())
566
        self.assertTrue('_pithos2_a' in self.client.cookies)
567
        cookies = self.client.cookies
568
        self.assertTrue(quote(user.auth_token) in cookies.get('_pithos2_a').value)
569
        r = self.client.get('/im/logout', follow=True)
570
        r = self.client.get('/im/')
571
        # user logged out, token cookie removed
572
        self.assertFalse(r.context['request'].user.is_authenticated())
573
        self.assertFalse(self.client.cookies.get('_pithos2_a').value)
574
        # https://docs.djangoproject.com/en/dev/topics/testing/#persistent-state
575
        del self.client.cookies['_pithos2_a']
576

    
577
        # user can login
578
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
579
                                           'password': 'password'},
580
                                          follow=True)
581
        self.assertTrue(r.context['request'].user.is_authenticated())
582
        self.assertTrue('_pithos2_a' in self.client.cookies)
583
        cookies = self.client.cookies
584
        self.assertTrue(quote(user.auth_token) in cookies.get('_pithos2_a').value)
585
        self.client.get('/im/logout', follow=True)
586

    
587
        # user forgot password
588
        old_pass = user.password
589
        r = self.client.get('/im/local/password_reset')
590
        self.assertEqual(r.status_code, 200)
591
        r = self.client.post('/im/local/password_reset', {'email':
592
                                                          'kpap@grnet.gr'})
593
        self.assertEqual(r.status_code, 302)
594
        # email sent
595
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 4)
596

    
597
        # user visits change password link
598
        r = self.client.get(user.get_password_reset_url())
599
        r = self.client.post(user.get_password_reset_url(),
600
                            {'new_password1':'newpass',
601
                             'new_password2':'newpass'})
602

    
603
        user = AstakosUser.objects.get(pk=user.pk)
604
        self.assertNotEqual(old_pass, user.password)
605

    
606
        # old pass is not usable
607
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
608
                                           'password': 'password'})
609
        self.assertContains(r, 'Please enter a correct username and password')
610
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
611
                                           'password': 'newpass'},
612
                                           follow=True)
613
        self.assertTrue(r.context['request'].user.is_authenticated())
614
        self.client.logout()
615

    
616
        # tests of special local backends
617
        user = AstakosUser.objects.get(pk=user.pk)
618
        user.auth_providers.filter(module='local').update(auth_backend='ldap')
619
        user.save()
620

    
621
        # non astakos local backends do not support password reset
622
        r = self.client.get('/im/local/password_reset')
623
        self.assertEqual(r.status_code, 200)
624
        r = self.client.post('/im/local/password_reset', {'email':
625
                                                          'kpap@grnet.gr'})
626
        # she can't because account is not active yet
627
        self.assertContains(r, "Password change for this account is not"
628
                                " supported")
629

    
630
class UserActionsTests(TestCase):
631

    
632
    def setUp(self):
633
        kind = GroupKind.objects.create(name="default")
634
        AstakosGroup.objects.create(name="default", kind=kind)
635

    
636
    def test_email_change(self):
637
        # to test existing email validation
638
        existing_user = get_local_user('existing@grnet.gr')
639

    
640
        # local user
641
        user = get_local_user('kpap@grnet.gr')
642

    
643
        # login as kpap
644
        self.client.login(username='kpap@grnet.gr', password='password')
645
        r = self.client.get('/im/profile', follow=True)
646
        user = r.context['request'].user
647
        self.assertTrue(user.is_authenticated())
648

    
649
        # change email is enabled
650
        r = self.client.get('/im/email_change')
651
        self.assertEqual(r.status_code, 200)
652
        self.assertFalse(user.email_change_is_pending())
653

    
654
        # request email change to an existing email fails
655
        data = {'new_email_address': 'existing@grnet.gr'}
656
        r = self.client.post('/im/email_change', data)
657
        self.assertContains(r, messages.EMAIL_USED)
658

    
659
        # proper email change
660
        data = {'new_email_address': 'kpap@gmail.com'}
661
        r = self.client.post('/im/email_change', data, follow=True)
662
        self.assertRedirects(r, '/im/profile')
663
        self.assertContains(r, messages.EMAIL_CHANGE_REGISTERED)
664
        change1 = EmailChange.objects.get()
665

    
666
        # user sees a warning
667
        r = self.client.get('/im/email_change')
668
        self.assertEqual(r.status_code, 200)
669
        self.assertContains(r, messages.PENDING_EMAIL_CHANGE_REQUEST)
670
        self.assertTrue(user.email_change_is_pending())
671

    
672
        # link was sent
673
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 0)
674
        self.assertEqual(len(get_mailbox('kpap@gmail.com')), 1)
675

    
676
        # proper email change
677
        data = {'new_email_address': 'kpap@yahoo.com'}
678
        r = self.client.post('/im/email_change', data, follow=True)
679
        self.assertRedirects(r, '/im/profile')
680
        self.assertContains(r, messages.EMAIL_CHANGE_REGISTERED)
681
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 0)
682
        self.assertEqual(len(get_mailbox('kpap@yahoo.com')), 1)
683
        change2 = EmailChange.objects.get()
684

    
685
        r = self.client.get(change1.get_url())
686
        self.assertEquals(r.status_code, 302)
687
        self.client.logout()
688

    
689
        r = self.client.post('/im/local?next=' + change2.get_url(),
690
                             {'username': 'kpap@grnet.gr',
691
                              'password': 'password',
692
                              'next': change2.get_url()},
693
                             follow=True)
694
        self.assertRedirects(r, '/im/profile')
695
        user = r.context['request'].user
696
        self.assertEquals(user.email, 'kpap@yahoo.com')
697
        self.assertEquals(user.username, 'kpap@yahoo.com')
698

    
699

    
700
        self.client.logout()
701
        r = self.client.post('/im/local?next=' + change2.get_url(),
702
                             {'username': 'kpap@grnet.gr',
703
                              'password': 'password',
704
                              'next': change2.get_url()},
705
                             follow=True)
706
        self.assertContains(r, "Please enter a correct username and password")
707
        self.assertEqual(user.emailchanges.count(), 0)
708