dbca-utils 2.0.0__tar.gz → 2.0.1__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbca-utils
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: Utilities for Django/Python apps
5
5
  Home-page: https://github.com/dbca-wa/dbca-utils
6
6
  Author: Department of Biodiversity, Conservation and Attractions
@@ -0,0 +1,178 @@
1
+ from django import http, VERSION
2
+ from django.conf import settings
3
+ from django.contrib.auth import login, logout, get_user_model
4
+ from django.utils.deprecation import MiddlewareMixin
5
+ from django.utils.functional import SimpleLazyObject
6
+ from django.contrib.auth.middleware import AuthenticationMiddleware, get_user
7
+
8
+ from dbca_utils.utils import env
9
+
10
+ ENABLE_AUTH2_GROUPS = env("ENABLE_AUTH2_GROUPS", default=False)
11
+ LOCAL_USERGROUPS = env("LOCAL_USERGROUPS", default=[])
12
+ User = get_user_model()
13
+
14
+
15
+ def sync_usergroups(user, groups):
16
+ from django.contrib.auth.models import Group
17
+
18
+ usergroups = (
19
+ [Group.objects.get_or_create(name=name)[0] for name in groups.split(",")] if groups else []
20
+ )
21
+ usergroups.sort(key=lambda o: o.id)
22
+ existing_usergroups = list(user.groups.exclude(name__in=LOCAL_USERGROUPS).order_by("id"))
23
+ index1 = 0
24
+ index2 = 0
25
+ len1 = len(usergroups)
26
+ len2 = len(existing_usergroups)
27
+
28
+ while True:
29
+ group1 = usergroups[index1] if index1 < len1 else None
30
+ group2 = existing_usergroups[index2] if index2 < len2 else None
31
+ if not group1 and not group2:
32
+ break
33
+ if not group1:
34
+ user.groups.remove(group2)
35
+ index2 += 1
36
+ elif not group2:
37
+ user.groups.add(group1)
38
+ index1 += 1
39
+ elif group1.id == group2.id:
40
+ index1 += 1
41
+ index2 += 1
42
+ elif group1.id < group2.id:
43
+ user.groups.add(group1)
44
+ index1 += 1
45
+ else:
46
+ user.groups.remove(group2)
47
+ index2 += 1
48
+
49
+
50
+ class SimpleLazyUser(SimpleLazyObject):
51
+ def __init__(self, func, request, groups):
52
+ super().__init__(func)
53
+ self.request = request
54
+ self.usergroups = groups
55
+
56
+ def __getattr__(self, name):
57
+ if name == "groups":
58
+ sync_usergroups(self._wrapped, self.usergroups)
59
+ self.request.session["usergroups"] = self.usergroups
60
+
61
+ return super().__getattr__(name)
62
+
63
+
64
+ # Monkey patch AuthenticationMiddleware to add logic to process user groups.
65
+ if ENABLE_AUTH2_GROUPS:
66
+ original_process_request = AuthenticationMiddleware.process_request
67
+
68
+ def _process_request(self, request):
69
+ if "HTTP_X_GROUPS" in request.META:
70
+ groups = request.META["HTTP_X_GROUPS"] or None
71
+ existing_groups = request.session.get("usergroups")
72
+ if groups != existing_groups:
73
+ # User group is changed.
74
+ request.user = SimpleLazyUser(
75
+ lambda: get_user(request), request, groups
76
+ )
77
+ return
78
+ original_process_request(self, request)
79
+
80
+ AuthenticationMiddleware.process_request = _process_request
81
+
82
+
83
+ class SSOLoginMiddleware(MiddlewareMixin):
84
+ """Django middleware to process HTTP requests containing headers set by the Auth2
85
+ SSO service, specificially:
86
+ - `HTTP_REMOTE_USER`
87
+ - `HTTP_X_LAST_NAME`
88
+ - `HTTP_X_FIRST_NAME`
89
+ - `HTTP_X_EMAIL`
90
+ The middleware assesses requests containing these headers, and (having deferred user
91
+ authentication to the upstream service), retrieves the local Django User and logs
92
+ the user in automatically.
93
+ If the request path starts with one of the defined logout paths and a `HTTP_X_LOGOUT_URL`
94
+ value is set in the response, log out the user and redirect to that URL instead.
95
+ """
96
+
97
+ def process_request(self, request):
98
+
99
+ # Logout headers included with request.
100
+ if (
101
+ (
102
+ request.path.startswith("/logout")
103
+ or request.path.startswith("/admin/logout")
104
+ or request.path.startswith("/ledger/logout")
105
+ )
106
+ and "HTTP_X_LOGOUT_URL" in request.META
107
+ and request.META["HTTP_X_LOGOUT_URL"]
108
+ ):
109
+ logout(request)
110
+ return http.HttpResponseRedirect(request.META["HTTP_X_LOGOUT_URL"])
111
+
112
+ # Auth2 is not enabled, skip further processing.
113
+ if (
114
+ "HTTP_REMOTE_USER" not in request.META
115
+ or not request.META["HTTP_REMOTE_USER"]
116
+ ):
117
+ # auth2 not enabled
118
+ return
119
+
120
+ if VERSION < (2, 0):
121
+ user_authenticated = request.user.is_authenticated()
122
+ else:
123
+ user_authenticated = request.user.is_authenticated
124
+
125
+ # Auth2 is enabled.
126
+ # Request user is not authenticated.
127
+ if not user_authenticated:
128
+ attributemap = {
129
+ "username": "HTTP_REMOTE_USER",
130
+ "last_name": "HTTP_X_LAST_NAME",
131
+ "first_name": "HTTP_X_FIRST_NAME",
132
+ "email": "HTTP_X_EMAIL",
133
+ }
134
+
135
+ for key, value in attributemap.items():
136
+ if value in request.META:
137
+ attributemap[key] = request.META[value]
138
+
139
+ # Optional setting: projects may define accepted user email domains either as
140
+ # a list of strings, or a single string.
141
+ if (
142
+ hasattr(settings, "ALLOWED_EMAIL_SUFFIXES")
143
+ and settings.ALLOWED_EMAIL_SUFFIXES
144
+ ):
145
+ allowed = settings.ALLOWED_EMAIL_SUFFIXES
146
+ if isinstance(settings.ALLOWED_EMAIL_SUFFIXES, str):
147
+ allowed = [settings.ALLOWED_EMAIL_SUFFIXES]
148
+ if not any(
149
+ [attributemap["email"].lower().endswith(x) for x in allowed]
150
+ ):
151
+ return http.HttpResponseForbidden()
152
+
153
+ if (
154
+ attributemap["email"]
155
+ and User.objects.filter(email__iexact=attributemap["email"]).exists()
156
+ ):
157
+ user = User.objects.filter(email__iexact=attributemap["email"])[0]
158
+ elif (
159
+ User.__name__ != "EmailUser"
160
+ and User.objects.filter(username__iexact=attributemap["username"]).exists()
161
+ ):
162
+ user = User.objects.filter(username__iexact=attributemap["username"])[0]
163
+ else:
164
+ user = User()
165
+
166
+ # Set the user's details from the supplied information.
167
+ user.__dict__.update(attributemap)
168
+ user.save()
169
+ user.backend = "django.contrib.auth.backends.ModelBackend"
170
+
171
+ # Log the user in.
172
+ login(request, user)
173
+
174
+ # Synchronize the user groups
175
+ if ENABLE_AUTH2_GROUPS and "HTTP_X_GROUPS" in request.META:
176
+ groups = request.META["HTTP_X_GROUPS"] or None
177
+ sync_usergroups(user, groups)
178
+ request.session["usergroups"] = groups
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbca-utils
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: Utilities for Django/Python apps
5
5
  Home-page: https://github.com/dbca-wa/dbca-utils
6
6
  Author: Department of Biodiversity, Conservation and Attractions
@@ -9,7 +9,7 @@ long_description = (this_directory / "README.md").read_text()
9
9
 
10
10
  setup(
11
11
  name='dbca-utils',
12
- version='2.0.0',
12
+ version='2.0.1',
13
13
  packages=['dbca_utils'],
14
14
  description='Utilities for Django/Python apps',
15
15
  long_description=long_description,
@@ -1,89 +0,0 @@
1
- from django import http
2
- from django.conf import settings
3
- from django.contrib.auth import login, logout, get_user_model
4
- from django.utils.deprecation import MiddlewareMixin
5
-
6
- User = get_user_model()
7
-
8
-
9
- class SSOLoginMiddleware(MiddlewareMixin):
10
- """Django middleware to process HTTP requests containing headers set by the Auth2
11
- SSO service, specificially:
12
- - `HTTP_REMOTE_USER`
13
- - `HTTP_X_LAST_NAME`
14
- - `HTTP_X_FIRST_NAME`
15
- - `HTTP_X_EMAIL`
16
-
17
- The middleware assesses requests containing these headers, and (having deferred user
18
- authentication to the upstream service), retrieves the local Django User and logs
19
- the user in automatically.
20
-
21
- If the request path starts with one of the defined logout paths and a `HTTP_X_LOGOUT_URL`
22
- value is set in the response, log out the user and redirect to that URL instead.
23
- """
24
-
25
- def process_request(self, request):
26
-
27
- # Logout headers included with request.
28
- if (
29
- (
30
- request.path.startswith("/logout")
31
- or request.path.startswith("/admin/logout")
32
- )
33
- and "HTTP_X_LOGOUT_URL" in request.META
34
- and request.META["HTTP_X_LOGOUT_URL"]
35
- ):
36
- logout(request)
37
- return http.HttpResponseRedirect(request.META["HTTP_X_LOGOUT_URL"])
38
-
39
- # Auth2 is not enabled, skip further processing.
40
- if (
41
- "HTTP_REMOTE_USER" not in request.META
42
- or not request.META["HTTP_REMOTE_USER"]
43
- ):
44
- return
45
-
46
- # Auth2 is enabled.
47
- # Request user is not authenticated.
48
- if not request.user.is_authenticated:
49
- attributemap = {
50
- "username": "HTTP_REMOTE_USER",
51
- "last_name": "HTTP_X_LAST_NAME",
52
- "first_name": "HTTP_X_FIRST_NAME",
53
- "email": "HTTP_X_EMAIL",
54
- }
55
-
56
- for key, value in attributemap.items():
57
- if value in request.META:
58
- attributemap[key] = request.META[value]
59
-
60
- # Optional setting: projects may define accepted user email domains either as
61
- # a list of strings, or a single string.
62
- if (
63
- hasattr(settings, "ALLOWED_EMAIL_SUFFIXES")
64
- and settings.ALLOWED_EMAIL_SUFFIXES
65
- ):
66
- allowed = settings.ALLOWED_EMAIL_SUFFIXES
67
- if isinstance(settings.ALLOWED_EMAIL_SUFFIXES, str):
68
- allowed = [settings.ALLOWED_EMAIL_SUFFIXES]
69
- if not any(
70
- [attributemap["email"].lower().endswith(x) for x in allowed]
71
- ):
72
- return http.HttpResponseForbidden()
73
-
74
- # Check if the supplied request user email already exists as a local User.
75
- if (
76
- attributemap["email"]
77
- and User.objects.filter(email__iexact=attributemap["email"]).exists()
78
- ):
79
- user = User.objects.get(email__iexact=attributemap["email"])
80
- else:
81
- user = User()
82
-
83
- # Set the user's details from the supplied information.
84
- user.__dict__.update(attributemap)
85
- user.save()
86
- user.backend = "django.contrib.auth.backends.ModelBackend"
87
-
88
- # Log the user in.
89
- login(request, user)
File without changes
File without changes
File without changes
File without changes