django-auth-adfs 1.13.0__py3-none-any.whl → 1.15.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- django_auth_adfs/__init__.py +1 -1
- django_auth_adfs/backend.py +55 -34
- django_auth_adfs/config.py +4 -1
- django_auth_adfs/rest_framework.py +6 -1
- django_auth_adfs/views.py +37 -0
- {django_auth_adfs-1.13.0.dist-info → django_auth_adfs-1.15.0.dist-info}/METADATA +36 -15
- django_auth_adfs-1.15.0.dist-info/RECORD +16 -0
- {django_auth_adfs-1.13.0.dist-info → django_auth_adfs-1.15.0.dist-info}/WHEEL +1 -1
- django_auth_adfs-1.13.0.dist-info/RECORD +0 -16
- {django_auth_adfs-1.13.0.dist-info → django_auth_adfs-1.15.0.dist-info}/LICENSE +0 -0
django_auth_adfs/__init__.py
CHANGED
django_auth_adfs/backend.py
CHANGED
@@ -15,19 +15,22 @@ logger = logging.getLogger("django_auth_adfs")
|
|
15
15
|
|
16
16
|
|
17
17
|
class AdfsBaseBackend(ModelBackend):
|
18
|
-
def exchange_auth_code(self, authorization_code, request):
|
19
|
-
logger.debug("Received authorization code: %s", authorization_code)
|
20
|
-
data = {
|
21
|
-
'grant_type': 'authorization_code',
|
22
|
-
'client_id': settings.CLIENT_ID,
|
23
|
-
'redirect_uri': provider_config.redirect_uri(request),
|
24
|
-
'code': authorization_code,
|
25
|
-
}
|
26
|
-
if settings.CLIENT_SECRET:
|
27
|
-
data['client_secret'] = settings.CLIENT_SECRET
|
28
18
|
|
29
|
-
|
30
|
-
|
19
|
+
def _ms_request(self, action, url, data=None, **kwargs):
|
20
|
+
"""
|
21
|
+
Make a Microsoft Entra/GraphQL request
|
22
|
+
|
23
|
+
|
24
|
+
Args:
|
25
|
+
action (callable): The callable for making a request.
|
26
|
+
url (str): The URL the request should be sent to.
|
27
|
+
data (dict): Optional dictionary of data to be sent in the request.
|
28
|
+
|
29
|
+
Returns:
|
30
|
+
response: The response from the server. If it's not a 200, a
|
31
|
+
PermissionDenied is raised.
|
32
|
+
"""
|
33
|
+
response = action(url, data=data, timeout=settings.TIMEOUT, **kwargs)
|
31
34
|
# 200 = valid token received
|
32
35
|
# 400 = 'something' is wrong in our request
|
33
36
|
if response.status_code == 400:
|
@@ -39,7 +42,21 @@ class AdfsBaseBackend(ModelBackend):
|
|
39
42
|
if response.status_code != 200:
|
40
43
|
logger.error("Unexpected ADFS response: %s", response.content.decode())
|
41
44
|
raise PermissionDenied
|
45
|
+
return response
|
42
46
|
|
47
|
+
def exchange_auth_code(self, authorization_code, request):
|
48
|
+
logger.debug("Received authorization code: %s", authorization_code)
|
49
|
+
data = {
|
50
|
+
'grant_type': 'authorization_code',
|
51
|
+
'client_id': settings.CLIENT_ID,
|
52
|
+
'redirect_uri': provider_config.redirect_uri(request),
|
53
|
+
'code': authorization_code,
|
54
|
+
}
|
55
|
+
if settings.CLIENT_SECRET:
|
56
|
+
data['client_secret'] = settings.CLIENT_SECRET
|
57
|
+
|
58
|
+
logger.debug("Getting access token at: %s", provider_config.token_endpoint)
|
59
|
+
response = self._ms_request(provider_config.session.post, provider_config.token_endpoint, data)
|
43
60
|
adfs_response = response.json()
|
44
61
|
return adfs_response
|
45
62
|
|
@@ -66,21 +83,30 @@ class AdfsBaseBackend(ModelBackend):
|
|
66
83
|
else:
|
67
84
|
data["resource"] = 'https://graph.microsoft.com'
|
68
85
|
|
69
|
-
response = provider_config.session.get
|
70
|
-
# 200 = valid token received
|
71
|
-
# 400 = 'something' is wrong in our request
|
72
|
-
if response.status_code == 400:
|
73
|
-
logger.error("ADFS server returned an error: %s", response.json()["error_description"])
|
74
|
-
raise PermissionDenied
|
75
|
-
|
76
|
-
if response.status_code != 200:
|
77
|
-
logger.error("Unexpected ADFS response: %s", response.content.decode())
|
78
|
-
raise PermissionDenied
|
79
|
-
|
86
|
+
response = self._ms_request(provider_config.session.get, provider_config.token_endpoint, data)
|
80
87
|
obo_access_token = response.json()["access_token"]
|
81
88
|
logger.debug("Received OBO access token: %s", obo_access_token)
|
82
89
|
return obo_access_token
|
83
90
|
|
91
|
+
def get_group_memberships_from_ms_graph_params(self):
|
92
|
+
"""
|
93
|
+
Return the parameters to be used in the querystring
|
94
|
+
when fetching the user's group memberships.
|
95
|
+
|
96
|
+
Possible keys to be used:
|
97
|
+
- $count
|
98
|
+
- $expand
|
99
|
+
- $filter
|
100
|
+
- $orderby
|
101
|
+
- $search
|
102
|
+
- $select
|
103
|
+
- $top
|
104
|
+
|
105
|
+
Docs:
|
106
|
+
https://learn.microsoft.com/en-us/graph/api/group-list-transitivememberof?view=graph-rest-1.0&tabs=python#http-request
|
107
|
+
"""
|
108
|
+
return {}
|
109
|
+
|
84
110
|
def get_group_memberships_from_ms_graph(self, obo_access_token):
|
85
111
|
"""
|
86
112
|
Looks up a users group membership from the MS Graph API
|
@@ -95,17 +121,12 @@ class AdfsBaseBackend(ModelBackend):
|
|
95
121
|
provider_config.msgraph_endpoint
|
96
122
|
)
|
97
123
|
headers = {"Authorization": "Bearer {}".format(obo_access_token)}
|
98
|
-
response =
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
if response.status_code != 200:
|
106
|
-
logger.error("Unexpected MS Graph response: %s", response.content.decode())
|
107
|
-
raise PermissionDenied
|
108
|
-
|
124
|
+
response = self._ms_request(
|
125
|
+
action=provider_config.session.get,
|
126
|
+
url=graph_url,
|
127
|
+
data=self.get_group_memberships_from_ms_graph_params(),
|
128
|
+
headers=headers,
|
129
|
+
)
|
109
130
|
claim_groups = []
|
110
131
|
for group_data in response.json()["value"]:
|
111
132
|
if group_data["displayName"] is None:
|
django_auth_adfs/config.py
CHANGED
@@ -337,7 +337,10 @@ class ProviderConfig(object):
|
|
337
337
|
|
338
338
|
"""
|
339
339
|
self.load_config()
|
340
|
-
|
340
|
+
if request.method == 'POST':
|
341
|
+
redirect_to = request.POST.get(REDIRECT_FIELD_NAME, None)
|
342
|
+
else:
|
343
|
+
redirect_to = request.GET.get(REDIRECT_FIELD_NAME, None)
|
341
344
|
if not redirect_to:
|
342
345
|
redirect_to = django_settings.LOGIN_REDIRECT_URL
|
343
346
|
redirect_to = base64.urlsafe_b64encode(redirect_to.encode()).decode()
|
@@ -6,6 +6,8 @@ from rest_framework.authentication import (
|
|
6
6
|
BaseAuthentication, get_authorization_header
|
7
7
|
)
|
8
8
|
|
9
|
+
from django_auth_adfs.exceptions import MFARequired
|
10
|
+
|
9
11
|
|
10
12
|
class AdfsAccessTokenAuthentication(BaseAuthentication):
|
11
13
|
"""
|
@@ -33,7 +35,10 @@ class AdfsAccessTokenAuthentication(BaseAuthentication):
|
|
33
35
|
# Authenticate the user
|
34
36
|
# The AdfsAuthCodeBackend authentication backend will notice the "access_token" parameter
|
35
37
|
# and skip the request for an access token using the authorization code
|
36
|
-
|
38
|
+
try:
|
39
|
+
user = authenticate(access_token=auth[1])
|
40
|
+
except MFARequired as e:
|
41
|
+
raise exceptions.AuthenticationFailed('MFA auth is required.') from e
|
37
42
|
|
38
43
|
if user is None:
|
39
44
|
raise exceptions.AuthenticationFailed('Invalid access token.')
|
django_auth_adfs/views.py
CHANGED
@@ -84,6 +84,15 @@ class OAuth2LoginView(View):
|
|
84
84
|
"""
|
85
85
|
return redirect(provider_config.build_authorization_endpoint(request))
|
86
86
|
|
87
|
+
def post(self, request):
|
88
|
+
"""
|
89
|
+
Initiates the OAuth2 flow and redirect the user agent to ADFS
|
90
|
+
|
91
|
+
Args:
|
92
|
+
request (django.http.request.HttpRequest): A Django Request object
|
93
|
+
"""
|
94
|
+
return redirect(provider_config.build_authorization_endpoint(request))
|
95
|
+
|
87
96
|
|
88
97
|
class OAuth2LoginNoSSOView(View):
|
89
98
|
def get(self, request):
|
@@ -95,6 +104,15 @@ class OAuth2LoginNoSSOView(View):
|
|
95
104
|
"""
|
96
105
|
return redirect(provider_config.build_authorization_endpoint(request, disable_sso=True))
|
97
106
|
|
107
|
+
def post(self, request):
|
108
|
+
"""
|
109
|
+
Initiates the OAuth2 flow and redirect the user agent to ADFS
|
110
|
+
|
111
|
+
Args:
|
112
|
+
request (django.http.request.HttpRequest): A Django Request object
|
113
|
+
"""
|
114
|
+
return redirect(provider_config.build_authorization_endpoint(request, disable_sso=True))
|
115
|
+
|
98
116
|
|
99
117
|
class OAuth2LoginForceMFA(View):
|
100
118
|
def get(self, request):
|
@@ -106,6 +124,15 @@ class OAuth2LoginForceMFA(View):
|
|
106
124
|
"""
|
107
125
|
return redirect(provider_config.build_authorization_endpoint(request, force_mfa=True))
|
108
126
|
|
127
|
+
def post(self, request):
|
128
|
+
"""
|
129
|
+
Initiates the OAuth2 flow and redirect the user agent to ADFS
|
130
|
+
|
131
|
+
Args:
|
132
|
+
request (django.http.request.HttpRequest): A Django Request object
|
133
|
+
"""
|
134
|
+
return redirect(provider_config.build_authorization_endpoint(request, force_mfa=True))
|
135
|
+
|
109
136
|
|
110
137
|
class OAuth2LogoutView(View):
|
111
138
|
def get(self, request):
|
@@ -117,3 +144,13 @@ class OAuth2LogoutView(View):
|
|
117
144
|
"""
|
118
145
|
logout(request)
|
119
146
|
return redirect(provider_config.build_end_session_endpoint())
|
147
|
+
|
148
|
+
def post(self, request):
|
149
|
+
"""
|
150
|
+
Logs out the user from both Django and ADFS
|
151
|
+
|
152
|
+
Args:
|
153
|
+
request (django.http.request.HttpRequest): A Django Request object
|
154
|
+
"""
|
155
|
+
logout(request)
|
156
|
+
return redirect(provider_config.build_end_session_endpoint())
|
@@ -1,22 +1,19 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.3
|
2
2
|
Name: django-auth-adfs
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.15.0
|
4
4
|
Summary: A Django authentication backend for Microsoft ADFS and AzureAD
|
5
|
-
Home-page: https://github.com/snok/django-auth-adfs
|
6
5
|
License: BSD-1-Clause
|
7
6
|
Keywords: django,authentication,adfs,azure,ad,oauth2
|
8
7
|
Author: Joris Beckers
|
9
8
|
Author-email: joris.beckers@gmail.com
|
10
9
|
Maintainer: Jonas Krüger Svensson
|
11
10
|
Maintainer-email: jonas-ks@hotmail.com
|
12
|
-
Requires-Python: >=3.
|
11
|
+
Requires-Python: >=3.9,<4.0
|
13
12
|
Classifier: Development Status :: 5 - Production/Stable
|
14
13
|
Classifier: Environment :: Web Environment
|
15
|
-
Classifier: Framework :: Django :: 3.2
|
16
|
-
Classifier: Framework :: Django :: 4.0
|
17
|
-
Classifier: Framework :: Django :: 4.1
|
18
14
|
Classifier: Framework :: Django :: 4.2
|
19
15
|
Classifier: Framework :: Django :: 5.0
|
16
|
+
Classifier: Framework :: Django :: 5.1
|
20
17
|
Classifier: Intended Audience :: Developers
|
21
18
|
Classifier: Intended Audience :: End Users/Desktop
|
22
19
|
Classifier: License :: OSI Approved
|
@@ -24,11 +21,11 @@ Classifier: License :: OSI Approved :: BSD License
|
|
24
21
|
Classifier: Operating System :: OS Independent
|
25
22
|
Classifier: Programming Language :: Python
|
26
23
|
Classifier: Programming Language :: Python :: 3
|
27
|
-
Classifier: Programming Language :: Python :: 3.8
|
28
24
|
Classifier: Programming Language :: Python :: 3.9
|
29
25
|
Classifier: Programming Language :: Python :: 3.10
|
30
26
|
Classifier: Programming Language :: Python :: 3.11
|
31
27
|
Classifier: Programming Language :: Python :: 3.12
|
28
|
+
Classifier: Programming Language :: Python :: 3.13
|
32
29
|
Classifier: Topic :: Internet :: WWW/HTTP
|
33
30
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
34
31
|
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
|
@@ -36,11 +33,12 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
36
33
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
37
34
|
Requires-Dist: PyJWT
|
38
35
|
Requires-Dist: cryptography
|
39
|
-
Requires-Dist: django (>=
|
40
|
-
Requires-Dist: django (>=4,<6) ; python_version >= "3.10"
|
36
|
+
Requires-Dist: django (>=4.2,<5.0) ; python_version >= "3.9" and python_version < "3.10"
|
37
|
+
Requires-Dist: django (>=4.2,<6) ; python_version >= "3.10"
|
41
38
|
Requires-Dist: requests
|
42
39
|
Requires-Dist: urllib3
|
43
40
|
Project-URL: Documentation, https://django-auth-adfs.readthedocs.io/en/latest
|
41
|
+
Project-URL: Homepage, https://github.com/snok/django-auth-adfs
|
44
42
|
Project-URL: Repository, https://github.com/snok/django-auth-adfs
|
45
43
|
Description-Content-Type: text/x-rst
|
46
44
|
|
@@ -144,13 +142,36 @@ This will add these paths to Django:
|
|
144
142
|
* ``/oauth2/callback`` where ADFS redirects back to after login. So make sure you set the redirect URI on ADFS to this.
|
145
143
|
* ``/oauth2/logout`` which logs out the user from both Django and ADFS.
|
146
144
|
|
147
|
-
|
145
|
+
Below is sample Django template code to use these paths depending if
|
146
|
+
you'd like to use GET or POST requests. Logging out was deprecated in
|
147
|
+
`Django 4.1 <https://docs.djangoproject.com/en/5.1/releases/4.1/#features-deprecated-in-4-1>`_.
|
148
148
|
|
149
|
-
|
149
|
+
- Using GET requests:
|
150
150
|
|
151
|
-
|
152
|
-
|
153
|
-
|
151
|
+
.. code-block:: html
|
152
|
+
|
153
|
+
<a href="{% url 'django_auth_adfs:logout' %}">Logout</a>
|
154
|
+
<a href="{% url 'django_auth_adfs:login' %}">Login</a>
|
155
|
+
<a href="{% url 'django_auth_adfs:login-no-sso' %}">Login (no SSO)</a>
|
156
|
+
|
157
|
+
- Using POST requests:
|
158
|
+
|
159
|
+
.. code-block:: html+django
|
160
|
+
|
161
|
+
<form method="post" action="{% url 'django_auth_adfs:logout' %}">
|
162
|
+
{% csrf_token %}
|
163
|
+
<button type="submit">Logout</button>
|
164
|
+
</form>
|
165
|
+
<form method="post" action="{% url 'django_auth_adfs:login' %}">
|
166
|
+
{% csrf_token %}
|
167
|
+
<input type="hidden" name="next" value="{{ next }}">
|
168
|
+
<button type="submit">Login</button>
|
169
|
+
</form>
|
170
|
+
<form method="post" action="{% url 'django_auth_adfs:login-no-sso' %}">
|
171
|
+
{% csrf_token %}
|
172
|
+
<input type="hidden" name="next" value="{{ next }}">
|
173
|
+
<button type="submit">Login (no SSO)</button>
|
174
|
+
</form>
|
154
175
|
|
155
176
|
Contributing
|
156
177
|
------------
|
@@ -0,0 +1,16 @@
|
|
1
|
+
django_auth_adfs/__init__.py,sha256=1Bo5CRC7tY7wU69FiC93k65bICYiU4o1J3B8MKONPu0,137
|
2
|
+
django_auth_adfs/backend.py,sha256=c18wj5UvViQE-ZYmocz_bS9Iv-6sFdnzRnemlHNxmBg,18442
|
3
|
+
django_auth_adfs/config.py,sha256=Q6rsJHqVGESEdoLbLRn_H4qeH5c4knh3hKShdpjsSZ8,16376
|
4
|
+
django_auth_adfs/drf-urls.py,sha256=bBnN1WrgbHoiAs5hTGe-ovmVwwJ4D2xXpYtZVfE8l5U,221
|
5
|
+
django_auth_adfs/drf_urls.py,sha256=YKH7he1cCDr_eo0kgPJQiJ3kmo1FBS46JzSiBOOqwE0,430
|
6
|
+
django_auth_adfs/exceptions.py,sha256=yxwKHtDSMnskgBdIQoCWerYVr8sGtIn26GWVVdhA2YU,110
|
7
|
+
django_auth_adfs/middleware.py,sha256=st8Hx5pHWNcYUNWrCQht3QJqjw6RLQhWyxneTWal0Nw,2249
|
8
|
+
django_auth_adfs/rest_framework.py,sha256=ajt2iCb02Yqzbv1TAyV3evAnY8z-YEDdsg7zHtx6120,1841
|
9
|
+
django_auth_adfs/signals.py,sha256=HGD79-dZNEwqVuINoqe6jB_8K1RfuK5Q288RGamTtEU,137
|
10
|
+
django_auth_adfs/templates/django_auth_adfs/login_failed.html,sha256=1NQ5aCjo7iTLSxy2IXJZbzBp5MaxpfsfQwQ_PDFvgr4,652
|
11
|
+
django_auth_adfs/urls.py,sha256=4Cec5XbxrAqPHc_N3yMTC_m9UKZXsZktf3rO2niF4Bw,533
|
12
|
+
django_auth_adfs/views.py,sha256=MHKDCUEvBpNyBCUreX-cCnpp7ThfeoKt_rTt_1CBq9s,5319
|
13
|
+
django_auth_adfs-1.15.0.dist-info/LICENSE,sha256=WGMaA474V3mIMiWEkbMc-rCVt-uZvIjF4NqP1AIvNZo,1277
|
14
|
+
django_auth_adfs-1.15.0.dist-info/METADATA,sha256=t1GQ3og2Nlw5s3M8qGpV2ph_a-iAGk1ebIORTKzwUio,6913
|
15
|
+
django_auth_adfs-1.15.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
16
|
+
django_auth_adfs-1.15.0.dist-info/RECORD,,
|
@@ -1,16 +0,0 @@
|
|
1
|
-
django_auth_adfs/__init__.py,sha256=9vYn5rXOE0Yseenm9R6nNk1venk-5LOByXYVfektj7g,137
|
2
|
-
django_auth_adfs/backend.py,sha256=UxKnlMrQ3IMOnIiBd3itYtFOer-QxLTSHXXt5deew-s,18055
|
3
|
-
django_auth_adfs/config.py,sha256=p9uNhl4C6OIuiUHAdYmW6LiqhvoI_ZnR_W-mh-u3Z6w,16251
|
4
|
-
django_auth_adfs/drf-urls.py,sha256=bBnN1WrgbHoiAs5hTGe-ovmVwwJ4D2xXpYtZVfE8l5U,221
|
5
|
-
django_auth_adfs/drf_urls.py,sha256=YKH7he1cCDr_eo0kgPJQiJ3kmo1FBS46JzSiBOOqwE0,430
|
6
|
-
django_auth_adfs/exceptions.py,sha256=yxwKHtDSMnskgBdIQoCWerYVr8sGtIn26GWVVdhA2YU,110
|
7
|
-
django_auth_adfs/middleware.py,sha256=st8Hx5pHWNcYUNWrCQht3QJqjw6RLQhWyxneTWal0Nw,2249
|
8
|
-
django_auth_adfs/rest_framework.py,sha256=spWIXpb8UtVDvZ0_W_sJB-5u0hwvLNUCqbAhHf9N0W4,1656
|
9
|
-
django_auth_adfs/signals.py,sha256=HGD79-dZNEwqVuINoqe6jB_8K1RfuK5Q288RGamTtEU,137
|
10
|
-
django_auth_adfs/templates/django_auth_adfs/login_failed.html,sha256=1NQ5aCjo7iTLSxy2IXJZbzBp5MaxpfsfQwQ_PDFvgr4,652
|
11
|
-
django_auth_adfs/urls.py,sha256=4Cec5XbxrAqPHc_N3yMTC_m9UKZXsZktf3rO2niF4Bw,533
|
12
|
-
django_auth_adfs/views.py,sha256=kwMqcsDAgcuQRBgxpRVnFZY79ZkVo28LB3KOIbayI3E,4100
|
13
|
-
django_auth_adfs-1.13.0.dist-info/LICENSE,sha256=WGMaA474V3mIMiWEkbMc-rCVt-uZvIjF4NqP1AIvNZo,1277
|
14
|
-
django_auth_adfs-1.13.0.dist-info/METADATA,sha256=8RdnxYJtbsL9dMtSkQRrtXoBtNFV84GQfBiCuJVAaqk,6044
|
15
|
-
django_auth_adfs-1.13.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
16
|
-
django_auth_adfs-1.13.0.dist-info/RECORD,,
|
File without changes
|