1
+ import hmac
2
+ import uuid
3
+ from hashlib import sha1
4
+ from typing import Type
5
+ from unittest import mock
1
6
from urllib .parse import parse_qs
2
7
from urllib .parse import urlparse
3
8
4
9
import pytest
10
+ import pytest_django
11
+ from components import helpers
5
12
from components .accounts .views import get_oidc_logout_url
13
+ from django .contrib .auth .models import User
14
+ from django .test import Client
15
+ from django .test import RequestFactory
16
+ from django .urls import reverse
17
+ from tastypie .models import ApiKey
6
18
7
19
8
- def test_get_oidc_logout_url_fails_if_token_is_not_set (rf ) :
20
+ def test_get_oidc_logout_url_fails_if_token_is_not_set (rf : RequestFactory ) -> None :
9
21
request = rf .get ("/" )
10
22
request .session = {}
11
23
12
24
with pytest .raises (ValueError , match = "ID token not found in session." ):
13
25
get_oidc_logout_url (request )
14
26
15
27
16
- def test_get_oidc_logout_url_fails_if_logout_endpoint_is_not_set (rf ):
28
+ def test_get_oidc_logout_url_fails_if_logout_endpoint_is_not_set (
29
+ rf : RequestFactory ,
30
+ ) -> None :
17
31
request = rf .get ("/" )
18
32
request .session = {"oidc_id_token" : "mytoken" }
19
33
@@ -23,7 +37,9 @@ def test_get_oidc_logout_url_fails_if_logout_endpoint_is_not_set(rf):
23
37
get_oidc_logout_url (request )
24
38
25
39
26
- def test_get_oidc_logout_url_returns_logout_url (rf , settings ):
40
+ def test_get_oidc_logout_url_returns_logout_url (
41
+ rf : RequestFactory , settings : pytest_django .fixtures .SettingsWrapper
42
+ ) -> None :
27
43
settings .OIDC_OP_LOGOUT_ENDPOINT = "http://example.com/logout"
28
44
token = "mytoken"
29
45
request = rf .get ("/" )
@@ -36,3 +52,256 @@ def test_get_oidc_logout_url_returns_logout_url(rf, settings):
36
52
assert set (query_dict ) == {"id_token_hint" , "post_logout_redirect_uri" }
37
53
assert query_dict ["id_token_hint" ] == [token ]
38
54
assert query_dict ["post_logout_redirect_uri" ] == ["http://testserver/" ]
55
+
56
+
57
+ @pytest .fixture
58
+ def dashboard_uuid () -> None :
59
+ helpers .set_setting ("dashboard_uuid" , str (uuid .uuid4 ()))
60
+
61
+
62
+ @pytest .fixture
63
+ def non_administrative_user (django_user_model : Type [User ]) -> User :
64
+ return django_user_model .objects .create_user (
65
+ username = "test" ,
66
+ password = "test" ,
67
+ first_name = "Foo" ,
68
+ last_name = "Bar" ,
69
+
70
+ )
71
+
72
+
73
+ @pytest .mark .django_db
74
+ def test_edit_user_view_denies_access_to_non_admin_users_editing_others (
75
+ dashboard_uuid : None ,
76
+ non_administrative_user : User ,
77
+ admin_user : User ,
78
+ client : Client ,
79
+ ) -> None :
80
+ client .force_login (non_administrative_user )
81
+
82
+ response = client .get (
83
+ reverse ("accounts:edit" , kwargs = {"id" : admin_user .id }), follow = True
84
+ )
85
+ assert response .status_code == 200
86
+
87
+ content = response .content .decode ()
88
+ assert "Forbidden" in content
89
+ assert "You do not have enough access privileges for this operation" in content
90
+
91
+
92
+ @pytest .fixture
93
+ def non_administrative_user_apikey (non_administrative_user : User ) -> ApiKey :
94
+ return ApiKey .objects .create (user = non_administrative_user )
95
+
96
+
97
+ @pytest .mark .django_db
98
+ def test_edit_user_view_renders_user_profile_fields (
99
+ dashboard_uuid : None ,
100
+ non_administrative_user : User ,
101
+ non_administrative_user_apikey : ApiKey ,
102
+ admin_client : Client ,
103
+ ) -> None :
104
+ response = admin_client .get (
105
+ reverse ("accounts:edit" , kwargs = {"id" : non_administrative_user .id }), follow = True
106
+ )
107
+ assert response .status_code == 200
108
+
109
+ content = response .content .decode ()
110
+ assert f"Edit user { non_administrative_user .username } " in content
111
+ assert f'name="username" value="{ non_administrative_user .username } "' in content
112
+ assert f'name="first_name" value="{ non_administrative_user .first_name } "' in content
113
+ assert f'name="last_name" value="{ non_administrative_user .last_name } "' in content
114
+ assert f'name="email" value="{ non_administrative_user .email } "' in content
115
+ assert f"<code>{ non_administrative_user_apikey .key } </code>" in content
116
+
117
+
118
+ @pytest .mark .django_db
119
+ def test_edit_user_view_updates_user_profile_fields (
120
+ dashboard_uuid : None ,
121
+ non_administrative_user : User ,
122
+ admin_client : Client ,
123
+ ) -> None :
124
+ new_username = "newusername"
125
+ new_password = "newpassword"
126
+ new_first_name = "bar"
127
+ new_last_name = "foo"
128
+
129
+ data = {
130
+ "username" : new_username ,
131
+ "password" : new_password ,
132
+ "password_confirmation" : new_password ,
133
+ "first_name" : new_first_name ,
134
+ "last_name" : new_last_name ,
135
+ "email" : new_email ,
136
+ }
137
+
138
+ response = admin_client .post (
139
+ reverse ("accounts:edit" , kwargs = {"id" : non_administrative_user .id }),
140
+ data ,
141
+ follow = True ,
142
+ )
143
+ assert response .status_code == 200
144
+
145
+ content = response .content .decode ()
146
+ assert "Saved" in content
147
+ assert (
148
+ f'<a href="{ reverse ("accounts:edit" , kwargs = {"id" : non_administrative_user .id })} ">{ new_username } </a>'
149
+ in content
150
+ )
151
+ assert f"<td>{ new_first_name } { new_last_name } </td>" in content
152
+ assert f"<td>{ new_email } </td>" in content
153
+
154
+ non_administrative_user .refresh_from_db ()
155
+ assert non_administrative_user .check_password (new_password )
156
+
157
+
158
+ @pytest .mark .django_db
159
+ def test_edit_user_view_regenerates_api_key (
160
+ dashboard_uuid : None ,
161
+ non_administrative_user : User ,
162
+ non_administrative_user_apikey : ApiKey ,
163
+ admin_client : Client ,
164
+ ) -> None :
165
+ data = {
166
+ "username" : non_administrative_user .username ,
167
+ "first_name" : non_administrative_user .first_name ,
168
+ "last_name" : non_administrative_user .last_name ,
169
+ "email" : non_administrative_user .email ,
170
+ "regenerate_api_key" : True ,
171
+ }
172
+ expected_uuid = uuid .uuid4 ()
173
+ expected_key = hmac .new (expected_uuid .bytes , digestmod = sha1 ).hexdigest ()
174
+
175
+ with mock .patch ("uuid.uuid4" , return_value = expected_uuid ):
176
+ response = admin_client .post (
177
+ reverse ("accounts:edit" , kwargs = {"id" : non_administrative_user .id }),
178
+ data ,
179
+ follow = True ,
180
+ )
181
+ assert response .status_code == 200
182
+
183
+ assert "Saved" in response .content .decode ()
184
+
185
+ non_administrative_user_apikey .refresh_from_db ()
186
+ assert non_administrative_user_apikey .key == expected_key
187
+
188
+
189
+ @pytest .mark .django_db
190
+ def test_user_profile_view_allows_users_to_edit_their_profile_fields (
191
+ dashboard_uuid : None ,
192
+ non_administrative_user : User ,
193
+ non_administrative_user_apikey : ApiKey ,
194
+ client : Client ,
195
+ settings : pytest_django .fixtures .SettingsWrapper ,
196
+ ) -> None :
197
+ settings .ALLOW_USER_EDITS = True
198
+ client .force_login (non_administrative_user )
199
+
200
+ response = client .get (
201
+ reverse ("accounts:profile" ),
202
+ follow = True ,
203
+ )
204
+ assert response .status_code == 200
205
+
206
+ content = response .content .decode ()
207
+ assert f"Edit your profile ({ non_administrative_user .username } )" in content
208
+ assert f'name="username" value="{ non_administrative_user .username } "' in content
209
+ assert f'name="first_name" value="{ non_administrative_user .first_name } "' in content
210
+ assert f'name="last_name" value="{ non_administrative_user .last_name } "' in content
211
+ assert f'name="email" value="{ non_administrative_user .email } "' in content
212
+ assert f"<code>{ non_administrative_user_apikey .key } </code>" in content
213
+
214
+
215
+ @pytest .mark .django_db
216
+ def test_user_profile_view_denies_editing_profile_fields_if_setting_disables_it (
217
+ dashboard_uuid : None ,
218
+ non_administrative_user : User ,
219
+ non_administrative_user_apikey : ApiKey ,
220
+ client : Client ,
221
+ settings : pytest_django .fixtures .SettingsWrapper ,
222
+ ) -> None :
223
+ settings .ALLOW_USER_EDITS = False
224
+ client .force_login (non_administrative_user )
225
+
226
+ response = client .get (
227
+ reverse ("accounts:profile" ),
228
+ follow = True ,
229
+ )
230
+ assert response .status_code == 200
231
+
232
+ content = response .content .decode ()
233
+ assert f"Your profile ({ non_administrative_user .username } )" in content
234
+ assert f"<dd>{ non_administrative_user .username } </dd>" in content
235
+ assert (
236
+ f"<dd>{ non_administrative_user .first_name } { non_administrative_user .last_name } </dd>"
237
+ in content
238
+ )
239
+ assert f"<dd>{ non_administrative_user .email } </dd>" in content
240
+ assert (
241
+ f'<dd>{ "yes" if non_administrative_user .is_superuser else "no" } </dd>' in content
242
+ )
243
+ assert f"<code>{ non_administrative_user_apikey .key } </code>" in content
244
+
245
+
246
+ @pytest .mark .django_db
247
+ def test_user_profile_view_regenerates_api_key_if_setting_disables_editing (
248
+ dashboard_uuid : None ,
249
+ non_administrative_user : User ,
250
+ non_administrative_user_apikey : ApiKey ,
251
+ client : Client ,
252
+ settings : pytest_django .fixtures .SettingsWrapper ,
253
+ ) -> None :
254
+ settings .ALLOW_USER_EDITS = False
255
+ client .force_login (non_administrative_user )
256
+ data = {"regenerate_api_key" : True }
257
+ expected_uuid = uuid .uuid4 ()
258
+ expected_key = hmac .new (expected_uuid .bytes , digestmod = sha1 ).hexdigest ()
259
+
260
+ with mock .patch ("uuid.uuid4" , return_value = expected_uuid ):
261
+ response = client .post (
262
+ reverse ("accounts:profile" ),
263
+ data ,
264
+ follow = True ,
265
+ )
266
+ assert response .status_code == 200
267
+
268
+ content = response .content .decode ()
269
+ assert f"Your profile ({ non_administrative_user .username } )" in content
270
+ assert f"<dd>{ non_administrative_user .username } </dd>" in content
271
+ assert (
272
+ f"<dd>{ non_administrative_user .first_name } { non_administrative_user .last_name } </dd>"
273
+ in content
274
+ )
275
+ assert f"<dd>{ non_administrative_user .email } </dd>" in content
276
+ assert (
277
+ f'<dd>{ "yes" if non_administrative_user .is_superuser else "no" } </dd>' in content
278
+ )
279
+ assert f"<code>{ expected_key } </code>" in content
280
+
281
+
282
+ @pytest .mark .django_db
283
+ def test_user_profile_view_does_not_regenerate_api_key_if_not_requested (
284
+ dashboard_uuid : None ,
285
+ non_administrative_user : User ,
286
+ non_administrative_user_apikey : ApiKey ,
287
+ client : Client ,
288
+ settings : pytest_django .fixtures .SettingsWrapper ,
289
+ ) -> None :
290
+ settings .ALLOW_USER_EDITS = False
291
+ client .force_login (non_administrative_user )
292
+
293
+ response = client .post (reverse ("accounts:profile" ), {}, follow = True )
294
+ assert response .status_code == 200
295
+
296
+ content = response .content .decode ()
297
+ assert f"Your profile ({ non_administrative_user .username } )" in content
298
+ assert f"<dd>{ non_administrative_user .username } </dd>" in content
299
+ assert (
300
+ f"<dd>{ non_administrative_user .first_name } { non_administrative_user .last_name } </dd>"
301
+ in content
302
+ )
303
+ assert f"<dd>{ non_administrative_user .email } </dd>" in content
304
+ assert (
305
+ f'<dd>{ "yes" if non_administrative_user .is_superuser else "no" } </dd>' in content
306
+ )
307
+ assert f"<code>{ non_administrative_user_apikey .key } </code>" in content
0 commit comments