-
Notifications
You must be signed in to change notification settings - Fork 61
Expand file tree
/
Copy pathtest_sso.py
More file actions
143 lines (101 loc) · 4.13 KB
/
test_sso.py
File metadata and controls
143 lines (101 loc) · 4.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import base64
import hashlib
import hmac
from random import randint
from urllib import parse
import pytest
from django.conf import settings
from django.urls import reverse
from pythonpro.discourse.facade import _decode_payload
@pytest.fixture
def nonce():
return str(randint(-100000, 100000))
@pytest.fixture
def payload(nonce):
return parse.urlencode({'nonce': nonce})
@pytest.fixture
def response_with_member(client_with_member, payload, sig=None):
return _resp(client_with_member, payload, sig)
@pytest.fixture
def response_with_lead(client_with_lead, payload, sig=None):
return _resp(client_with_lead, payload, sig)
@pytest.fixture
def response_with_client(client_with_client, payload, sig=None):
return _resp(client_with_client, payload, sig)
def _resp(client, payload, sig=None):
encoded_payload = base64.encodebytes(payload.encode('utf-8'))
hmac_obj = hmac.new(settings.DISCOURSE_SSO_SECRET.encode('utf-8'), encoded_payload, digestmod=hashlib.sha256)
sig = hmac_obj.hexdigest() if sig is None else sig
return client.get(reverse('discourse:sso'),
data={'sso': encoded_payload, 'sig': sig})
@pytest.fixture
def response_with_wrong_sig(client_with_member, payload):
return _resp(client_with_member, payload, 'wrong sinature')
@pytest.fixture
def response_without_nonce(client_with_member):
return _resp(client_with_member, '')
def _extract_from_payload(response):
qs = parse.urlparse(response.url).query
parsed_qs = parse.parse_qs(qs)
payload = parsed_qs['sso'][0]
quoted = _decode_payload(payload, parsed_qs['sig'][0])
parsed_payload = parse.parse_qs(parse.unquote(quoted))
return {key: value[0] for key, value in parsed_payload.items()}
def test_status(response_with_member):
assert response_with_member.status_code == 302
def test_redirect_base_url(response_with_member):
assert response_with_member.url.startswith(settings.DISCOURSE_BASE_URL)
def test_redirect_payload_has_nonce(nonce, response_with_member):
dct = _extract_from_payload(response_with_member)
assert nonce == dct['nonce']
def test_redirect_payload_member_data(logged_user, nonce, response_with_member):
dct = _extract_from_payload(response_with_member)
assert {
'nonce': nonce,
'email': logged_user.email,
'external_id': str(logged_user.id),
'require_activation': 'false',
'groups': 'member'
} == dct
def test_redirect_payload_client_data(logged_user, nonce, response_with_client):
dct = _extract_from_payload(response_with_client)
assert {
'nonce': nonce,
'email': logged_user.email,
'external_id': str(logged_user.id),
'require_activation': 'false',
'groups': 'client'
} == dct
def test_redirect_payload_lead_data(logged_user, nonce, response_with_lead):
dct = _extract_from_payload(response_with_lead)
assert {
'nonce': nonce,
'email': logged_user.email,
'external_id': str(logged_user.id),
'require_activation': 'false',
'groups': 'lead'
} == dct
@pytest.mark.parametrize(
'invalid_data',
[
{},
{'sso': 'sso only'},
{'sig': 'sig only'},
{'sig': 'invalid sig', 'sso': 'invalid sso'},
]
)
def test_status_invalid_data(client_with_member, invalid_data):
response = client_with_member.get(reverse('discourse:sso'), data=invalid_data)
assert response.status_code == 400
def test_payload_without_nonce(response_without_nonce):
assert response_without_nonce.status_code == 400
def test_payload_with_mismatch_signature(response_with_wrong_sig):
assert response_with_wrong_sig.status_code == 400
def test_user_not_logged_status_code(client):
response = client.get(reverse('discourse:sso'))
assert response.status_code == 302
def test_user_not_logged(client):
discourse_path = reverse('discourse:sso')
response = client.get(discourse_path)
login_path = reverse('two_factor:login')
assert response.url == f'{login_path}?next={discourse_path}'