Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / tests.py @ fcc1e93f

History | View | Annotate | Download (29.3 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
            'domain': astakos_settings.BASEURL,
155
            'contact_email': astakos_settings.DEFAULT_CONTACT_EMAIL
156
        })
157
        client.set_tokens(eppn="kpapeppn")
158

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

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

    
172
        # astakos asks if we want to add shibboleth
173
        self.assertContains(r, "Already have an account?")
174

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

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

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

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

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

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

    
215
        # everything is ok in our db
216
        self.assertEqual(AstakosUser.objects.count(), 1)
217
        self.assertEqual(AstakosUserAuthProvider.objects.count(), 1)
218
        self.assertEqual(PendingThirdPartyUser.objects.count(), 0)
219

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

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

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

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

    
245
    def test_existing(self):
246
        """
247
        Test adding of third party login to an existing account
248
        """
249

    
250
        # this is our existing user
251
        existing_user = get_local_user('kpap@grnet.gr')
252

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

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

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

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

    
278
        self.assertTrue(existing_user.has_auth_provider('shibboleth'))
279
        self.assertTrue(existing_user.has_auth_provider('local',
280
                                                        auth_backend='astakos'))
281
        client.logout()
282

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

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

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

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

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

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

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

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

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

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

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

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

    
392
        client.reset_tokens()
393

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

    
408

    
409
class LocalUserTests(TestCase):
410

    
411
    fixtures = ['groups']
412

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

    
418
    def test_no_moderation(self):
419
        # disable moderation
420
        astakos_settings.MODERATION_ENABLED = False
421

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

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

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

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

    
450
        form = forms.LocalUserCreationForm(data)
451
        self.assertTrue(form.is_valid())
452
        user = form.save()
453
        form.store_user(user, {})
454

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

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

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

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

    
478
    def test_local_provider(self):
479
        # enable moderation
480
        astakos_settings.MODERATION_ENABLED = True
481

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

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

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

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

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

    
522
        # hmmm, email exists; lets request a password change
523
        r = self.client.get('/im/local/password_reset')
524
        self.assertEqual(r.status_code, 200)
525
        r = self.client.post('/im/local/password_reset', {'email':
526
                                                          'kpap@grnet.gr'},
527
                            follow=True)
528
        # she can't because account is not active yet
529
        self.assertContains(r, "doesn't have an associated user account")
530

    
531
        # moderation is enabled so no automatic activation can be send
532
        r = self.client.get('/im/send/activation/%d' % user.pk)
533
        self.assertEqual(r.status_code, 403)
534
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
535

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

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

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

    
561
        # switch back moderation setting
562
        astakos_settings.MODERATION_ENABLED = True
563
        r = self.client.get(user.get_activation_url(), follow=True)
564
        self.assertRedirects(r, "/im/profile")
565
        self.assertContains(r, "kpap@grnet.gr")
566
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 3)
567

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

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

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

    
602
        # user visits change password link
603
        r = self.client.get(user.get_password_reset_url())
604
        r = self.client.post(user.get_password_reset_url(),
605
                            {'new_password1':'newpass',
606
                             'new_password2':'newpass'})
607

    
608
        user = AstakosUser.objects.get(pk=user.pk)
609
        self.assertNotEqual(old_pass, user.password)
610

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

    
621
        # tests of special local backends
622
        user = AstakosUser.objects.get(pk=user.pk)
623
        user.auth_providers.filter(module='local').update(auth_backend='ldap')
624
        user.save()
625

    
626
        # non astakos local backends do not support password reset
627
        r = self.client.get('/im/local/password_reset')
628
        self.assertEqual(r.status_code, 200)
629
        r = self.client.post('/im/local/password_reset', {'email':
630
                                                          'kpap@grnet.gr'})
631
        # she can't because account is not active yet
632
        self.assertContains(r, messages.AUTH_PROVIDER_CANNOT_CHANGE_PASSWORD)
633

    
634
class UserActionsTests(TestCase):
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