django-auth-adfs 1.14.0__py3-none-any.whl → 1.15.1__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.14.0'
7
+ __version__ = '1.15.1'
@@ -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,19 +1,21 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: django-auth-adfs
3
- Version: 1.14.0
3
+ Version: 1.15.1
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
6
+ License-File: LICENSE
7
7
  Keywords: django,authentication,adfs,azure,ad,oauth2
8
8
  Author: Joris Beckers
9
9
  Author-email: joris.beckers@gmail.com
10
10
  Maintainer: Jonas Krüger Svensson
11
11
  Maintainer-email: jonas-ks@hotmail.com
12
- Requires-Python: >=3.8,<4.0
12
+ Requires-Python: >=3.9,<4.0
13
13
  Classifier: Development Status :: 5 - Production/Stable
14
14
  Classifier: Environment :: Web Environment
15
15
  Classifier: Framework :: Django :: 4.2
16
16
  Classifier: Framework :: Django :: 5.0
17
+ Classifier: Framework :: Django :: 5.1
18
+ Classifier: Framework :: Django :: 5.2
17
19
  Classifier: Intended Audience :: Developers
18
20
  Classifier: Intended Audience :: End Users/Desktop
19
21
  Classifier: License :: OSI Approved
@@ -21,11 +23,12 @@ Classifier: License :: OSI Approved :: BSD License
21
23
  Classifier: Operating System :: OS Independent
22
24
  Classifier: Programming Language :: Python
23
25
  Classifier: Programming Language :: Python :: 3
24
- Classifier: Programming Language :: Python :: 3.8
25
26
  Classifier: Programming Language :: Python :: 3.9
26
27
  Classifier: Programming Language :: Python :: 3.10
27
28
  Classifier: Programming Language :: Python :: 3.11
28
29
  Classifier: Programming Language :: Python :: 3.12
30
+ Classifier: Programming Language :: Python :: 3.13
31
+ Classifier: Programming Language :: Python :: 3.14
29
32
  Classifier: Topic :: Internet :: WWW/HTTP
30
33
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
31
34
  Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
@@ -33,11 +36,12 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
33
36
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
34
37
  Requires-Dist: PyJWT
35
38
  Requires-Dist: cryptography
36
- Requires-Dist: django (>=4.2,<5.0) ; python_version >= "3.8" and python_version < "3.10"
39
+ Requires-Dist: django (>=4.2,<5.0) ; python_version == "3.9"
37
40
  Requires-Dist: django (>=4.2,<6) ; python_version >= "3.10"
38
41
  Requires-Dist: requests
39
42
  Requires-Dist: urllib3
40
43
  Project-URL: Documentation, https://django-auth-adfs.readthedocs.io/en/latest
44
+ Project-URL: Homepage, https://github.com/snok/django-auth-adfs
41
45
  Project-URL: Repository, https://github.com/snok/django-auth-adfs
42
46
  Description-Content-Type: text/x-rst
43
47
 
@@ -53,8 +57,8 @@ ADFS Authentication for Django
53
57
  :target: https://pypi.python.org/pypi/django-auth-adfs#downloads
54
58
  .. image:: https://img.shields.io/pypi/djversions/django-auth-adfs.svg
55
59
  :target: https://pypi.python.org/pypi/django-auth-adfs
56
- .. image:: https://codecov.io/github/snok/django-auth-adfs/coverage.svg?branch=master
57
- :target: https://codecov.io/github/snok/django-auth-adfs?branch=master
60
+ .. image:: https://codecov.io/github/snok/django-auth-adfs/coverage.svg?branch=main
61
+ :target: https://codecov.io/github/snok/django-auth-adfs?branch=main
58
62
 
59
63
  A Django authentication backend for Microsoft ADFS and Azure AD
60
64
 
@@ -141,13 +145,36 @@ This will add these paths to Django:
141
145
  * ``/oauth2/callback`` where ADFS redirects back to after login. So make sure you set the redirect URI on ADFS to this.
142
146
  * ``/oauth2/logout`` which logs out the user from both Django and ADFS.
143
147
 
144
- You can use them like this in your django templates:
148
+ Below is sample Django template code to use these paths depending if
149
+ you'd like to use GET or POST requests. Logging out was deprecated in
150
+ `Django 4.1 <https://docs.djangoproject.com/en/5.1/releases/4.1/#features-deprecated-in-4-1>`_.
145
151
 
146
- .. code-block:: html
152
+ - Using GET requests:
147
153
 
148
- <a href="{% url 'django_auth_adfs:logout' %}">Logout</a>
149
- <a href="{% url 'django_auth_adfs:login' %}">Login</a>
150
- <a href="{% url 'django_auth_adfs:login-no-sso' %}">Login (no SSO)</a>
154
+ .. code-block:: html
155
+
156
+ <a href="{% url 'django_auth_adfs:logout' %}">Logout</a>
157
+ <a href="{% url 'django_auth_adfs:login' %}">Login</a>
158
+ <a href="{% url 'django_auth_adfs:login-no-sso' %}">Login (no SSO)</a>
159
+
160
+ - Using POST requests:
161
+
162
+ .. code-block:: html+django
163
+
164
+ <form method="post" action="{% url 'django_auth_adfs:logout' %}">
165
+ {% csrf_token %}
166
+ <button type="submit">Logout</button>
167
+ </form>
168
+ <form method="post" action="{% url 'django_auth_adfs:login' %}">
169
+ {% csrf_token %}
170
+ <input type="hidden" name="next" value="{{ next }}">
171
+ <button type="submit">Login</button>
172
+ </form>
173
+ <form method="post" action="{% url 'django_auth_adfs:login-no-sso' %}">
174
+ {% csrf_token %}
175
+ <input type="hidden" name="next" value="{{ next }}">
176
+ <button type="submit">Login (no SSO)</button>
177
+ </form>
151
178
 
152
179
  Contributing
153
180
  ------------
@@ -0,0 +1,16 @@
1
+ django_auth_adfs/__init__.py,sha256=uO6MFeiHZyNgf305wqfY6o9ZFDRoCN63fEMUR9yVA-o,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.1.dist-info/METADATA,sha256=kB5u7Xad5gPB-F5-s5kwak8EMfv4DLwKLPDWcf9w6ZU,6993
14
+ django_auth_adfs-1.15.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
15
+ django_auth_adfs-1.15.1.dist-info/licenses/LICENSE,sha256=WGMaA474V3mIMiWEkbMc-rCVt-uZvIjF4NqP1AIvNZo,1277
16
+ django_auth_adfs-1.15.1.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.2.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,16 +0,0 @@
1
- django_auth_adfs/__init__.py,sha256=qOOJKTzh0aY8HeYW6CdWkhpAhEvo_bTE6TqRyvW8Jgk,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.14.0.dist-info/LICENSE,sha256=WGMaA474V3mIMiWEkbMc-rCVt-uZvIjF4NqP1AIvNZo,1277
14
- django_auth_adfs-1.14.0.dist-info/METADATA,sha256=Btwm4YoHjzz-bY5YEvpMgKCpqgetVJPJ19DREqhqxtA,5933
15
- django_auth_adfs-1.14.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
16
- django_auth_adfs-1.14.0.dist-info/RECORD,,