Skip to content

Commit c1920af

Browse files
ihsaan-ullahcjh1
andauthored
OIDC updates from #2183 - Ensure username for OIDC accounts fit into db constraints (#2376)
* Ensure username for OIDC accounts fit into db constraints The username column is constrained to 50 characters. It's possible that a nickname or email derived username could be larger than this ( was in the case of Globus auth ). Truncate username to the 50 character limit. * flake fixes * code simplified --------- Co-authored-by: Chris Harris <cjh@lbl.gov>
1 parent 128acc4 commit c1920af

1 file changed

Lines changed: 74 additions & 40 deletions

File tree

src/apps/oidc_configurations/views.py

Lines changed: 74 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -53,53 +53,61 @@ def oidc_complete(request, auth_organization_id):
5353

5454
if error:
5555
context["error"] = error
56-
5756
if error_description:
5857
context["error_description"] = error_description
5958

60-
# Token exhange process
61-
if authorization_code:
59+
if error or error_description:
60+
return render(request, 'oidc/oidc_complete.html', context)
6261

63-
try:
64-
# STEP 1: Get auth organization using its id
65-
organization = get_object_or_404(Auth_Organization, pk=auth_organization_id)
62+
if not authorization_code:
63+
context["error"] = "Authorization Code not provided!"
64+
return render(request, 'oidc/oidc_complete.html', context)
6665

67-
if organization:
66+
try:
67+
# STEP 1: Get auth organization using its id
68+
organization = get_object_or_404(Auth_Organization, pk=auth_organization_id)
69+
70+
# STEP 2: Get access token
71+
access_token, token_error = get_access_token(organization, authorization_code)
72+
if token_error:
73+
context["error"] = token_error
74+
return render(request, 'oidc/oidc_complete.html', context)
75+
else:
76+
# STEP 3: Get user info
77+
user_info, user_info_error = get_user_info(organization, access_token)
78+
if user_info_error:
79+
context["error"] = user_info_error
80+
return render(request, 'oidc/oidc_complete.html', context)
81+
else:
82+
if not isinstance(user_info, dict):
83+
context["error"] = "Invalid user info payload from OIDC provider."
84+
return render(request, 'oidc/oidc_complete.html', context)
6885

69-
# STEP 2: Get access token
70-
access_token, token_error = get_access_token(organization, authorization_code)
86+
user_email = user_info.get("email", None)
87+
user_nickname = user_info.get("nickname", None)
88+
if not user_email:
89+
context["error"] = "Unable to extract email from user info."
90+
return render(request, 'oidc/oidc_complete.html', context)
7191

72-
if token_error:
73-
context["error"] = token_error
92+
user_email = str(user_email).strip().lower()
93+
94+
if user_nickname is not None:
95+
user_nickname = str(user_nickname).strip() or None
96+
97+
# get user with this email
98+
user = get_user_by_email(user_email)
99+
100+
# STEP 4: Check if user exists and user is created using oidc and oidc orgnaization matches this one
101+
if user:
102+
login(request, user, backend=BACKEND)
103+
# Redirect the user home page
104+
return redirect('pages:home')
74105
else:
75-
# STEP 3: Get user info
76-
user_info, user_info_error = get_user_info(organization, access_token)
77-
if user_info_error:
78-
context["error"] = user_info_error
79-
else:
80-
81-
# get email and nickname (username) of the user
82-
user_email = user_info.get("email", None)
83-
user_nickname = user_info.get("nickname", None)
84-
if user_email:
85-
# get user with this email
86-
user = get_user_by_email(user_email)
87-
# STEP 4: Check if user exists and user is created using oidc and oidc orgnaization matches this one
88-
if user:
89-
login(request, user, backend=BACKEND)
90-
# Redirect the user home page
91-
return redirect('pages:home')
92-
else:
93-
return register_and_authenticate_user(request, user_email, user_nickname, organization)
94-
95-
else:
96-
context["error"] = "Unable to extract email from user info! Please contact platform"
97-
else:
98-
context["error"] = "Invalid Organization ID!"
99-
except Exception as e:
100-
context["error"] = f"{e}"
106+
return register_and_authenticate_user(request, user_email, user_nickname, organization)
101107

102-
return render(request, 'oidc/oidc_complete.html', context)
108+
except Exception as e:
109+
context["error"] = f"{e}"
110+
return render(request, 'oidc/oidc_complete.html', context)
103111

104112

105113
def get_access_token(organization, authorization_code):
@@ -181,14 +189,21 @@ def register_and_authenticate_user(request, user_email, user_nickname, organizat
181189

182190

183191
def create_unique_username(username):
192+
# Normalize the username to remove unsupported characters and ensure it's route-safe.
193+
username = normalize_username(username)
194+
195+
# Truncate the username to fit within the maximum length allowed by the User model.
196+
max_username_length = User._meta.get_field("username").max_length
197+
username = username[:max_username_length]
198+
184199
# Check if the username already exists
185200
if User.objects.filter(username=username).exists():
186201
# If the username already exists, modify it to make it unique
187202
suffix = 1
188-
new_username = f"{username}_{suffix}"
203+
new_username = with_suffix(username, suffix, max_username_length)
189204
while User.objects.filter(username=new_username).exists():
190205
suffix += 1
191-
new_username = f"{username}_{suffix}"
206+
new_username = with_suffix(username, suffix, max_username_length)
192207
return new_username
193208
else:
194209
# If the username doesn't exist, use it as is
@@ -201,3 +216,22 @@ def get_user_by_email(email):
201216
return user
202217
except User.DoesNotExist:
203218
return None
219+
220+
221+
def normalize_username(username):
222+
# Keep OIDC names readable while removing unsupported chars.
223+
# Keep in sync with profile URL regex: [-a-zA-Z0-9_]+
224+
cleaned = re.sub(r'[^a-zA-Z0-9_-]', '', username.strip())
225+
if not cleaned:
226+
raise ValueError("OIDC username contains no valid route-safe characters")
227+
228+
return cleaned
229+
230+
231+
def with_suffix(base_username, suffix, max_username_length):
232+
suffix = f"_{suffix}"
233+
limit = max_username_length - len(suffix)
234+
if limit < 1:
235+
raise ValueError("Unable to create unique username within max length")
236+
237+
return f"{base_username[:limit]}{suffix}"

0 commit comments

Comments
 (0)