django-auth-adfs 1.13.0__py3-none-any.whl → 1.15.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,4 +4,4 @@ This file is imported by setup.py
4
4
  Adding imports here will break setup.py
5
5
  """
6
6
 
7
- __version__ = '1.13.0'
7
+ __version__ = '1.15.0'
@@ -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
- logger.debug("Getting access token at: %s", provider_config.token_endpoint)
30
- response = provider_config.session.post(provider_config.token_endpoint, data, timeout=settings.TIMEOUT)
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(provider_config.token_endpoint, data=data, timeout=settings.TIMEOUT)
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 = provider_config.session.get(graph_url, headers=headers, timeout=settings.TIMEOUT)
99
- # 200 = valid token received
100
- # 400 = 'something' is wrong in our request
101
- if response.status_code in [400, 401]:
102
- logger.error("MS Graph server returned an error: %s", response.json()["message"])
103
- raise PermissionDenied
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:
@@ -337,7 +337,10 @@ class ProviderConfig(object):
337
337
 
338
338
  """
339
339
  self.load_config()
340
- redirect_to = request.GET.get(REDIRECT_FIELD_NAME, None)
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
- user = authenticate(access_token=auth[1])
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
1
+ Metadata-Version: 2.3
2
2
  Name: django-auth-adfs
3
- Version: 1.13.0
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.8,<4.0
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 (>=3,<5) ; python_version >= "3.8" and python_version < "3.10"
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
- You can use them like this in your django templates:
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
- .. code-block:: html
149
+ - Using GET requests:
150
150
 
151
- <a href="{% url 'django_auth_adfs:logout' %}">Logout</a>
152
- <a href="{% url 'django_auth_adfs:login' %}">Login</a>
153
- <a href="{% url 'django_auth_adfs:login-no-sso' %}">Login (no SSO)</a>
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,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.8.1
2
+ Generator: poetry-core 2.0.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,