dbca-utils 2.1.1__tar.gz → 2.1.2__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.4
2
2
  Name: dbca-utils
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: Utilities for DBCA Django apps
5
5
  Author-Email: Rocky Chen <rocky.chen@dbca.wa.gov.au>, Ashley Felton <ashley.felton@dbca.wa.gov.au>
6
6
  License-Expression: Apache-2.0
@@ -26,6 +26,7 @@ Project-URL: Changelog, https://github.com/dbca-wa/dbca-utils/blob/master/CHANGE
26
26
  Project-URL: GitHub, https://github.com/dbca-wa/dbca-utils
27
27
  Requires-Python: <4.0,>=3.10
28
28
  Requires-Dist: django<6,>=4
29
+ Requires-Dist: markupsafe==3.0.2
29
30
  Description-Content-Type: text/markdown
30
31
 
31
32
  # Overview
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dbca-utils"
3
- version = "2.1.1"
3
+ version = "2.1.2"
4
4
  description = "Utilities for DBCA Django apps"
5
5
  authors = [
6
6
  { name = "Rocky Chen", email = "rocky.chen@dbca.wa.gov.au" },
@@ -29,6 +29,7 @@ classifiers = [
29
29
  requires-python = ">=3.10,<4.0"
30
30
  dependencies = [
31
31
  "django>=4,<6",
32
+ "markupsafe==3.0.2",
32
33
  ]
33
34
 
34
35
  [project.urls]
@@ -58,3 +59,14 @@ package = true
58
59
  DJANGO_SETTINGS_MODULE = "tests.settings"
59
60
  pythonpath = ". src"
60
61
  python_files = "tests.py test_*.py"
62
+
63
+ [tool.ruff]
64
+ line-length = 140
65
+ indent-width = 4
66
+
67
+ [tool.ruff.lint]
68
+ ignore = [
69
+ "E265",
70
+ "E501",
71
+ "E722",
72
+ ]
@@ -5,6 +5,7 @@ from django.contrib.auth.middleware import AuthenticationMiddleware, get_user
5
5
  from django.utils import timezone
6
6
  from django.utils.deprecation import MiddlewareMixin
7
7
  from django.utils.functional import SimpleLazyObject
8
+ from markupsafe import escape
8
9
 
9
10
  from dbca_utils.utils import env
10
11
 
@@ -13,18 +14,16 @@ LOCAL_USERGROUPS = env("LOCAL_USERGROUPS", default=[])
13
14
  User = get_user_model()
14
15
 
15
16
 
16
- def sync_usergroups(user, groups):
17
+ def sync_usergroups(user, groups=None):
17
18
  from django.contrib.auth.models import Group
18
19
 
19
- usergroups = (
20
- [Group.objects.get_or_create(name=name)[0] for name in groups.split(",")]
21
- if groups
22
- else []
23
- )
20
+ if groups:
21
+ usergroups = [Group.objects.get_or_create(name=name)[0] for name in groups.split(",")]
22
+ else:
23
+ usergroups = []
24
+
24
25
  usergroups.sort(key=lambda o: o.id)
25
- existing_usergroups = list(
26
- user.groups.exclude(name__in=LOCAL_USERGROUPS).order_by("id")
27
- )
26
+ existing_usergroups = list(user.groups.exclude(name__in=LOCAL_USERGROUPS).order_by("id"))
28
27
  index1 = 0
29
28
  index2 = 0
30
29
  len1 = len(usergroups)
@@ -76,9 +75,7 @@ if ENABLE_AUTH2_GROUPS:
76
75
  existing_groups = request.session.get("usergroups")
77
76
  if groups != existing_groups:
78
77
  # User group is changed.
79
- request.user = SimpleLazyUser(
80
- lambda: get_user(request), request, groups
81
- )
78
+ request.user = SimpleLazyUser(lambda: get_user(request), request, groups)
82
79
  return
83
80
  original_process_request(self, request)
84
81
 
@@ -102,11 +99,7 @@ class SSOLoginMiddleware(MiddlewareMixin):
102
99
  def process_request(self, request):
103
100
  # Logout headers included with request.
104
101
  if (
105
- (
106
- request.path.startswith("/logout")
107
- or request.path.startswith("/admin/logout")
108
- or request.path.startswith("/ledger/logout")
109
- )
102
+ (request.path.startswith("/logout") or request.path.startswith("/admin/logout") or request.path.startswith("/ledger/logout"))
110
103
  and "HTTP_X_LOGOUT_URL" in request.META
111
104
  and request.META["HTTP_X_LOGOUT_URL"]
112
105
  ):
@@ -114,18 +107,20 @@ class SSOLoginMiddleware(MiddlewareMixin):
114
107
  return http.HttpResponseRedirect(request.META["HTTP_X_LOGOUT_URL"])
115
108
 
116
109
  # Auth2 is not enabled, skip further processing.
117
- if (
118
- "HTTP_REMOTE_USER" not in request.META
119
- or not request.META["HTTP_REMOTE_USER"]
120
- ):
110
+ if "HTTP_REMOTE_USER" not in request.META or not request.META["HTTP_REMOTE_USER"]:
121
111
  # auth2 not enabled
122
112
  return
123
113
 
124
- user_authenticated = request.user.is_authenticated
125
-
126
114
  # Auth2 is enabled.
127
- # Request user is not authenticated.
128
- if not user_authenticated:
115
+ # Security check: if the logged-in request user's email does not match the email
116
+ # returned from Auth2, invalidate the current request session and force a new session
117
+ # using the returned SSO values.
118
+ if request.user.is_authenticated and request.user.email != request.META["HTTP_X_EMAIL"]:
119
+ logout(request)
120
+
121
+ # Request user is not authenticated locally: obtain user attributes from the request.META dict
122
+ # returned by SSO.
123
+ if not request.user.is_authenticated:
129
124
  attributemap = {
130
125
  "username": "HTTP_REMOTE_USER",
131
126
  "last_name": "HTTP_X_LAST_NAME",
@@ -137,31 +132,28 @@ class SSOLoginMiddleware(MiddlewareMixin):
137
132
  if value in request.META:
138
133
  attributemap[key] = request.META[value]
139
134
 
135
+ # Sanitise first_name and last_name values, because end-users have control over these
136
+ # values and could conceivably inject malicious values into them (e.g. a XSS attack).
137
+ if "first_name" in attributemap:
138
+ attributemap["first_name"] = str(escape(attributemap["first_name"]))
139
+ if "last_name" in attributemap:
140
+ attributemap["last_name"] = str(escape(attributemap["last_name"]))
141
+
140
142
  # Optional setting: projects may define accepted user email domains either as
141
143
  # a list of strings, or a single string.
142
- if (
143
- hasattr(settings, "ALLOWED_EMAIL_SUFFIXES")
144
- and settings.ALLOWED_EMAIL_SUFFIXES
145
- ):
146
- allowed = settings.ALLOWED_EMAIL_SUFFIXES
144
+ if hasattr(settings, "ALLOWED_EMAIL_SUFFIXES") and settings.ALLOWED_EMAIL_SUFFIXES:
147
145
  if isinstance(settings.ALLOWED_EMAIL_SUFFIXES, str):
148
- allowed = [settings.ALLOWED_EMAIL_SUFFIXES]
149
- if not any(
150
- [attributemap["email"].lower().endswith(x) for x in allowed]
151
- ):
146
+ allowed_email_suffixes = list(settings.ALLOWED_EMAIL_SUFFIXES)
147
+ else:
148
+ allowed_email_suffixes = settings.ALLOWED_EMAIL_SUFFIXES
149
+ # If the user email suffix is not in the allowed list, return a 404 response.
150
+ if not any([attributemap["email"].lower().endswith(suffix) for suffix in allowed_email_suffixes]):
152
151
  return http.HttpResponseForbidden()
153
152
 
154
- if (
155
- attributemap["email"]
156
- and User.objects.filter(email__iexact=attributemap["email"]).exists()
157
- ):
153
+ # Check for an existing User instance.
154
+ if attributemap["email"] and User.objects.filter(email__iexact=attributemap["email"]).exists():
158
155
  user = User.objects.filter(email__iexact=attributemap["email"])[0]
159
- elif (
160
- User.__name__ != "EmailUser"
161
- and User.objects.filter(
162
- username__iexact=attributemap["username"]
163
- ).exists()
164
- ):
156
+ elif User.__name__ != "EmailUser" and User.objects.filter(username__iexact=attributemap["username"]).exists():
165
157
  user = User.objects.filter(username__iexact=attributemap["username"])[0]
166
158
  else:
167
159
  user = User(last_login=timezone.localtime())
@@ -174,7 +166,7 @@ class SSOLoginMiddleware(MiddlewareMixin):
174
166
  # Log the user in.
175
167
  login(request, user)
176
168
 
177
- # Synchronize the user groups
169
+ # Synchronize the user groups.
178
170
  if ENABLE_AUTH2_GROUPS and "HTTP_X_GROUPS" in request.META:
179
171
  groups = request.META["HTTP_X_GROUPS"] or None
180
172
  sync_usergroups(user, groups)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes