1
|
# -*- coding: utf-8 -*-
|
2
|
|
3
|
import json
|
4
|
import time
|
5
|
import mock
|
6
|
import os
|
7
|
import pytest
|
8
|
|
9
|
from django.conf import settings
|
10
|
from django.core.management import call_command
|
11
|
from django.http.request import HttpRequest, QueryDict
|
12
|
from django.forms.fields import DateField
|
13
|
from django.test.client import RequestFactory, Client
|
14
|
from django.core.urlresolvers import reverse
|
15
|
|
16
|
from mandayejs.mandaye.models import UserCredentials
|
17
|
from mandayejs.mandaye.utils import exec_phantom, get_login_info
|
18
|
from mandayejs.mandaye.forms import FormFactory
|
19
|
from mandayejs.mandaye.views import post_login_do
|
20
|
|
21
|
|
22
|
pytestmark = pytest.mark.django_db
|
23
|
|
24
|
|
25
|
LOGIN_INFO = {
|
26
|
"address": "https://whatever.com",
|
27
|
"auth_checker": "tenants/static/js/auth.checker.js",
|
28
|
"cookies": [],
|
29
|
"form_submit_element": "input[type=submit], button",
|
30
|
"locators": [
|
31
|
{
|
32
|
"#username": "myusername",
|
33
|
"#password": "mypassoword"
|
34
|
}
|
35
|
]
|
36
|
}
|
37
|
|
38
|
MOCKED_SITE_LOCATORS = [
|
39
|
{'id': '#login',
|
40
|
'label': 'Login',
|
41
|
'name': 'login',
|
42
|
'kind': 'string', },
|
43
|
{'id': '#password',
|
44
|
'label': 'Password',
|
45
|
'name': 'password',
|
46
|
'kind': 'password'},
|
47
|
{'id': '#birth_date',
|
48
|
'label': 'Birthdata',
|
49
|
'name': 'birth_date',
|
50
|
'kind': 'date'}]
|
51
|
|
52
|
|
53
|
class MockedPopen(mock.Mock):
|
54
|
stall_for = False
|
55
|
|
56
|
def communicate(self, data):
|
57
|
if self.stall_for:
|
58
|
time.sleep(13)
|
59
|
return self.expected_output
|
60
|
|
61
|
|
62
|
# ENCRYPTION/DECRYPTION
|
63
|
def test_encryption(credentials):
|
64
|
decrypted = credentials.decrypt()
|
65
|
assert decrypted.get('password') == u'jôhn password'
|
66
|
|
67
|
|
68
|
@pytest.fixture(params=['command_ldap', 'command_csv'])
|
69
|
def command(request, command_ldap, command_csv):
|
70
|
return locals().get(request.param)
|
71
|
|
72
|
|
73
|
@mock.patch('mandayejs.mandaye.management.commands.migrate-users.get_idps')
|
74
|
def test_command_migrate_users(mocked_idps, command):
|
75
|
mocked_idps.return_value = iter(settings.MELLON_IDENTITY_PROVIDERS)
|
76
|
call_command(command.name, *command.args, **command.opts)
|
77
|
if command.opts.get('ldap'):
|
78
|
credentials = UserCredentials.objects.filter(user__last_name__in=[
|
79
|
'ldap_user1',
|
80
|
'ldap_user2',
|
81
|
'ldap_user3'])
|
82
|
|
83
|
assert len(credentials) == 3
|
84
|
|
85
|
for cred in credentials:
|
86
|
assert cred.to_login_info(decrypt=True)['#password'] == 'password_{}'.format(cred.user.last_name)
|
87
|
else:
|
88
|
credentials = UserCredentials.objects.all().exclude(user__last_name__in=[
|
89
|
'ldap_user1',
|
90
|
'ldap_user2',
|
91
|
'ldap_user3'
|
92
|
])
|
93
|
|
94
|
assert len(credentials) == 4
|
95
|
|
96
|
for cred in credentials:
|
97
|
assert cred.to_login_info(decrypt=True)['#password'] == cred.to_login_info()['#username']
|
98
|
|
99
|
|
100
|
@mock.patch('mandayejs.mandaye.utils.subprocess.Popen')
|
101
|
@mock.patch('mandayejs.applications.Test.SITE_LOCATORS', MOCKED_SITE_LOCATORS)
|
102
|
def test_phantom_invalid_json(mocked_popen, caplog, user_john):
|
103
|
expected_output = ('This is not a valid JSON', None)
|
104
|
mocked_popen.return_value = MockedPopen(expected_output=expected_output)
|
105
|
|
106
|
UserCredentials.objects.create(user=user_john,
|
107
|
locators={
|
108
|
'login': 'johnny', 'password': 'jumper',
|
109
|
'birth_date': '1995-06-11'})
|
110
|
|
111
|
client = Client()
|
112
|
client.login(username='john', password='john')
|
113
|
response = client.get(reverse('post-login-do'))
|
114
|
assert 'window.top.location = "/_mandaye/associate/"' in response.content
|
115
|
|
116
|
for message in response.context['messages']:
|
117
|
assert message.level_tag == 'error'
|
118
|
assert message.message == 'invalid response from server'
|
119
|
|
120
|
for record in caplog.records:
|
121
|
if record.levelname == 'ERROR':
|
122
|
assert record.message == 'invalid json: This is not a valid JSON'
|
123
|
|
124
|
|
125
|
@mock.patch('mandayejs.mandaye.utils.subprocess.Popen')
|
126
|
def test_phantom_js_errors(mocked_popen, caplog):
|
127
|
stdout = {
|
128
|
"stderr": "ERROR: TypeError: undefined is not a function (evaluating \'$( \"#datepicker\" )",
|
129
|
"url": "https://whatever.com/someurl",
|
130
|
"result": "ok"
|
131
|
}
|
132
|
|
133
|
expected_output = ('<mandayejs>%s</mandayejs>' % json.dumps(stdout), None)
|
134
|
|
135
|
mocked_popen.return_value = MockedPopen(expected_output=expected_output)
|
136
|
result = exec_phantom(LOGIN_INFO)
|
137
|
|
138
|
for record in caplog.records:
|
139
|
assert record.levelname == 'WARNING'
|
140
|
assert record.message == "ERROR: TypeError: undefined is not a function (evaluating \'$( \"#datepicker\" )"
|
141
|
|
142
|
assert result['result'] == 'ok'
|
143
|
assert result['url'] == 'https://whatever.com/someurl'
|
144
|
|
145
|
|
146
|
@mock.patch('mandayejs.mandaye.utils.subprocess.Popen')
|
147
|
def test_phantom_js_timeout(mocked_popen, caplog):
|
148
|
mocked_popen.return_value = MockedPopen(expected_output=json.dumps({'whatever': 'whatever'}), stall_for=True)
|
149
|
result = exec_phantom(LOGIN_INFO)
|
150
|
|
151
|
for record in caplog.records:
|
152
|
assert record.levelname == 'ERROR'
|
153
|
assert 'https://whatever.com' in record.message
|
154
|
assert 'tenants/static/js/auth.checker.js' in record.message
|
155
|
assert 'input[type=submit], button' in record.message
|
156
|
|
157
|
assert result['result'] == 'timeout'
|
158
|
|
159
|
|
160
|
@mock.patch('mandayejs.applications.Test.SITE_LOCATORS', MOCKED_SITE_LOCATORS)
|
161
|
def test_credentials_json_encoding(user_john):
|
162
|
|
163
|
request = HttpRequest()
|
164
|
request.POST = QueryDict('', mutable=True)
|
165
|
|
166
|
formdata = {
|
167
|
'login': 'johnny',
|
168
|
'password': 'jumper',
|
169
|
'birth_date': '1995-06-11'
|
170
|
}
|
171
|
|
172
|
request.POST.update(formdata)
|
173
|
|
174
|
form = FormFactory(request.POST)
|
175
|
form.is_valid()
|
176
|
|
177
|
assert isinstance(form.fields['birth_date'], DateField) is True
|
178
|
|
179
|
UserCredentials.objects.create(user=user_john, locators=form.cleaned_data)
|
180
|
|
181
|
cred = UserCredentials.objects.get(user=user_john)
|
182
|
|
183
|
assert cred.locators['login'] == 'johnny'
|
184
|
assert cred.locators['birth_date'] == '1995-06-11'
|
185
|
|
186
|
|
187
|
@pytest.fixture(params=[
|
188
|
{'url1': 'http://mydomain.com/update_password.aspx'},
|
189
|
{'url2': 'http://mydomain.com/index?path=change_pass'}])
|
190
|
def redirect_url(request):
|
191
|
return request.param
|
192
|
|
193
|
|
194
|
@mock.patch('mandayejs.mandaye.utils.subprocess.Popen')
|
195
|
@mock.patch('mandayejs.applications.Test.SITE_LOCATORS', MOCKED_SITE_LOCATORS)
|
196
|
def test_password_redirection(mocked_popen, user_john, redirect_url):
|
197
|
expected_output = {
|
198
|
"result": "redirect",
|
199
|
"reason": "password change required",
|
200
|
"url": redirect_url.get('url1') or redirect_url.get('url2')
|
201
|
}
|
202
|
expected_output = '<mandayejs>%s</mandayejs>' % json.dumps(expected_output)
|
203
|
mocked_popen.return_value = MockedPopen(expected_output=(expected_output, None))
|
204
|
|
205
|
UserCredentials.objects.create(user=user_john,
|
206
|
locators={
|
207
|
'login': 'johnny', 'password': 'jumper',
|
208
|
'birth_date': '1995-06-11'})
|
209
|
|
210
|
request = RequestFactory()
|
211
|
request = request.get(reverse('post-login-do'))
|
212
|
request.user = user_john
|
213
|
response = post_login_do(request)
|
214
|
if 'url1' in redirect_url:
|
215
|
assert 'window.top.location = "/update_password.aspx"' in response.content
|
216
|
else:
|
217
|
assert 'window.top.location = "/index?path=change_pass"' in response.content
|
218
|
|
219
|
|
220
|
@mock.patch('mandayejs.mandaye.utils.subprocess.Popen')
|
221
|
@mock.patch('mandayejs.applications.Test.SITE_LOCATORS', MOCKED_SITE_LOCATORS)
|
222
|
def test_enclosed_response(mocked_popen):
|
223
|
output = """<mandayejs>{"result": "ok",
|
224
|
"authentication": "success"} </mandayejs>
|
225
|
this is just a random error"""
|
226
|
|
227
|
mocked_popen.return_value = MockedPopen(expected_output=(output, None))
|
228
|
result = exec_phantom(LOGIN_INFO)
|
229
|
assert result['result'] == 'ok'
|
230
|
|
231
|
# with no match
|
232
|
mocked_popen.return_value = MockedPopen(expected_output=('<mandayejs></mandayejs>', None))
|
233
|
result = exec_phantom(LOGIN_INFO)
|
234
|
assert result['result'] == 'json_error'
|
235
|
|
236
|
|
237
|
@mock.patch('mandayejs.mandaye.utils.subprocess.Popen')
|
238
|
@mock.patch('mandayejs.applications.Test.SITE_LOCATORS', MOCKED_SITE_LOCATORS)
|
239
|
def test_post_login_do_with_next_url(mocked_popen, user_john):
|
240
|
# when sso fails
|
241
|
expected_output = {
|
242
|
"result": "redirect",
|
243
|
"reason": "password change required",
|
244
|
"url": "http://mydomain.com/update_password.aspx"
|
245
|
}
|
246
|
expected_output = '<mandayejs>%s</mandayejs>' % json.dumps(expected_output)
|
247
|
mocked_popen.return_value = MockedPopen(expected_output=(expected_output, None))
|
248
|
|
249
|
UserCredentials.objects.create(user=user_john,
|
250
|
locators={
|
251
|
'login': 'johnny', 'password': 'jumper',
|
252
|
'birth_date': '1995-06-11'})
|
253
|
|
254
|
request = RequestFactory()
|
255
|
url = '%s?next=http://example.net/' % reverse('post-login-do')
|
256
|
request = request.get(url)
|
257
|
request.user = user_john
|
258
|
response = post_login_do(request)
|
259
|
assert 'window.top.location = "http://example.net/"' not in response.content
|
260
|
|
261
|
# when SSO succeeds
|
262
|
expected_output = {
|
263
|
"result": "ok",
|
264
|
"url": "http://mydomain.com/account.aspx"
|
265
|
}
|
266
|
expected_output = '<mandayejs>%s</mandayejs>' % json.dumps(expected_output)
|
267
|
mocked_popen.return_value = MockedPopen(expected_output=(expected_output, None))
|
268
|
request = RequestFactory()
|
269
|
url = '%s?next_url=http://example.net/' % reverse('post-login-do')
|
270
|
request = request.get(url)
|
271
|
request.user = user_john
|
272
|
response = post_login_do(request)
|
273
|
assert 'window.top.location = "http://example.net/"' in response.content
|
274
|
|
275
|
|
276
|
@mock.patch('mandayejs.applications.Test.SITE_LOCATORS', MOCKED_SITE_LOCATORS)
|
277
|
def test_app_settings_overriding(settings, cred_john):
|
278
|
request = RequestFactory().get('/')
|
279
|
data = get_login_info(request, cred_john)
|
280
|
assert data['address'] == 'http://testserver/'
|
281
|
assert data['auth_checker'] == os.path.join(settings.STATIC_ROOT, 'js/test/auth.checker.js')
|
282
|
assert data['form_submit_element'] == 'input[type=submit], button'
|
283
|
# when overriding settings
|
284
|
settings.SITE_LOGIN_PATH = 'account/login'
|
285
|
settings.SITE_AUTH_CHECKER = 'js/global.auth.checker.js'
|
286
|
settings.SITE_FORM_SUBMIT_ELEMENT = 'button'
|
287
|
data = get_login_info(request, cred_john)
|
288
|
assert data['address'] == 'http://testserver/account/login'
|
289
|
assert data['auth_checker'] == os.path.join(settings.STATIC_ROOT, 'js/global.auth.checker.js')
|
290
|
assert data['form_submit_element'] == 'button'
|
291
|
settings.SITE_LOGIN_PATH = ''
|
292
|
settings.SITE_FORM_SUBMIT_ELEMENT = ''
|
293
|
data = get_login_info(request, cred_john)
|
294
|
assert data['address'] == 'http://testserver/'
|
295
|
assert data['form_submit_element'] == ''
|
296
|
|
297
|
|
298
|
def test_app_name_slug_in_association_context(settings, user_john):
|
299
|
client = Client()
|
300
|
client.login(username='john', password='john')
|
301
|
response = client.get(reverse('associate'))
|
302
|
assert response.context['app']['name'] == 'Test'
|
303
|
assert response.context['app']['slug'] == 'test'
|
304
|
# when overidding the appname in settings
|
305
|
settings.SITE_APP_NAME = 'Test A'
|
306
|
response = client.get(reverse('associate'))
|
307
|
assert response.context['app']['name'] == 'Test A'
|
308
|
assert response.context['app']['slug'] == 'test'
|