From a5ad7059b3de6671a4dfe2529efacc89d7a0d0bc Mon Sep 17 00:00:00 2001 From: Chris Harris Date: Wed, 11 Feb 2026 15:18:26 +0000 Subject: [PATCH 1/3] 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. --- src/apps/oidc_configurations/views.py | 63 ++++++++++++++++++++------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/src/apps/oidc_configurations/views.py b/src/apps/oidc_configurations/views.py index 6b04be8ff..7c8d6a4fc 100644 --- a/src/apps/oidc_configurations/views.py +++ b/src/apps/oidc_configurations/views.py @@ -77,23 +77,32 @@ def oidc_complete(request, auth_organization_id): if user_info_error: context["error"] = user_info_error else: + if not isinstance(user_info, dict): + context["error"] = "Invalid user info payload from OIDC provider." + return render(request, 'oidc/oidc_complete.html', context) - # get email and nickname (username) of the user user_email = user_info.get("email", None) user_nickname = user_info.get("nickname", None) - if user_email: - # get user with this email - user = get_user_by_email(user_email) - # STEP 4: Check if user exists and user is created using oidc and oidc orgnaization matches this one - if user: - login(request, user, backend=BACKEND) - # Redirect the user home page - return redirect('pages:home') - else: - return register_and_authenticate_user(request, user_email, user_nickname, organization) - + if not user_email: + context["error"] = ( + "Unable to extract email from user info." + ) + return render(request, 'oidc/oidc_complete.html', context) + user_email = str(user_email).strip().lower() + + if user_nickname is not None: + user_nickname = str(user_nickname).strip() or None + + # get user with this email + user = get_user_by_email(user_email) + # STEP 4: Check if user exists and user is created using oidc and oidc orgnaization matches this one + if user: + login(request, user, backend=BACKEND) + # Redirect the user home page + return redirect('pages:home') else: - context["error"] = "Unable to extract email from user info! Please contact platform" + return register_and_authenticate_user(request, user_email, user_nickname, organization) + else: context["error"] = "Invalid Organization ID!" except Exception as e: @@ -181,14 +190,21 @@ def register_and_authenticate_user(request, user_email, user_nickname, organizat def create_unique_username(username): + # Normalize the username to remove unsupported characters and ensure it's route-safe. + username = normalize_username(username) + + # Truncate the username to fit within the maximum length allowed by the User model. + max_username_length = User._meta.get_field("username").max_length + username = username[:max_username_length] + # Check if the username already exists if User.objects.filter(username=username).exists(): # If the username already exists, modify it to make it unique suffix = 1 - new_username = f"{username}_{suffix}" + new_username = with_suffix(username, suffix, max_username_length) while User.objects.filter(username=new_username).exists(): suffix += 1 - new_username = f"{username}_{suffix}" + new_username = with_suffix(username, suffix, max_username_length) return new_username else: # If the username doesn't exist, use it as is @@ -201,3 +217,20 @@ def get_user_by_email(email): return user except User.DoesNotExist: return None + +def normalize_username(username): + # Keep OIDC names readable while removing unsupported chars. + # Keep in sync with profile URL regex: [-a-zA-Z0-9_]+ + cleaned = re.sub(r'[^a-zA-Z0-9_-]', '', username.strip()) + if not cleaned: + raise ValueError("OIDC username contains no valid route-safe characters") + + return cleaned + +def with_suffix(base_username, suffix, max_username_length): + suffix = f"_{suffix}" + limit = max_username_length - len(suffix) + if limit < 1: + raise ValueError("Unable to create unique username within max length") + + return f"{base_username[:limit]}{suffix}" From e3b1e43bb6c7c9b18072b8048e49b861b719eb18 Mon Sep 17 00:00:00 2001 From: Ihsan Ullah Date: Fri, 15 May 2026 15:57:50 +0500 Subject: [PATCH 2/3] flake fixes --- src/apps/oidc_configurations/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/apps/oidc_configurations/views.py b/src/apps/oidc_configurations/views.py index 7c8d6a4fc..e88a55fca 100644 --- a/src/apps/oidc_configurations/views.py +++ b/src/apps/oidc_configurations/views.py @@ -218,6 +218,7 @@ def get_user_by_email(email): except User.DoesNotExist: return None + def normalize_username(username): # Keep OIDC names readable while removing unsupported chars. # Keep in sync with profile URL regex: [-a-zA-Z0-9_]+ @@ -227,6 +228,7 @@ def normalize_username(username): return cleaned + def with_suffix(base_username, suffix, max_username_length): suffix = f"_{suffix}" limit = max_username_length - len(suffix) From cdd71064cf771b6d165ec52746761fab18334c8d Mon Sep 17 00:00:00 2001 From: Ihsan Ullah Date: Fri, 15 May 2026 16:51:45 +0500 Subject: [PATCH 3/3] code simplified --- src/apps/oidc_configurations/views.py | 93 +++++++++++++-------------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/src/apps/oidc_configurations/views.py b/src/apps/oidc_configurations/views.py index e88a55fca..9e739b078 100644 --- a/src/apps/oidc_configurations/views.py +++ b/src/apps/oidc_configurations/views.py @@ -53,62 +53,61 @@ def oidc_complete(request, auth_organization_id): if error: context["error"] = error - if error_description: context["error_description"] = error_description - # Token exhange process - if authorization_code: + if error or error_description: + return render(request, 'oidc/oidc_complete.html', context) - try: - # STEP 1: Get auth organization using its id - organization = get_object_or_404(Auth_Organization, pk=auth_organization_id) + if not authorization_code: + context["error"] = "Authorization Code not provided!" + return render(request, 'oidc/oidc_complete.html', context) - if organization: + try: + # STEP 1: Get auth organization using its id + organization = get_object_or_404(Auth_Organization, pk=auth_organization_id) - # STEP 2: Get access token - access_token, token_error = get_access_token(organization, authorization_code) + # STEP 2: Get access token + access_token, token_error = get_access_token(organization, authorization_code) + if token_error: + context["error"] = token_error + return render(request, 'oidc/oidc_complete.html', context) + else: + # STEP 3: Get user info + user_info, user_info_error = get_user_info(organization, access_token) + if user_info_error: + context["error"] = user_info_error + return render(request, 'oidc/oidc_complete.html', context) + else: + if not isinstance(user_info, dict): + context["error"] = "Invalid user info payload from OIDC provider." + return render(request, 'oidc/oidc_complete.html', context) - if token_error: - context["error"] = token_error - else: - # STEP 3: Get user info - user_info, user_info_error = get_user_info(organization, access_token) - if user_info_error: - context["error"] = user_info_error - else: - if not isinstance(user_info, dict): - context["error"] = "Invalid user info payload from OIDC provider." - return render(request, 'oidc/oidc_complete.html', context) - - user_email = user_info.get("email", None) - user_nickname = user_info.get("nickname", None) - if not user_email: - context["error"] = ( - "Unable to extract email from user info." - ) - return render(request, 'oidc/oidc_complete.html', context) - user_email = str(user_email).strip().lower() - - if user_nickname is not None: - user_nickname = str(user_nickname).strip() or None - - # get user with this email - user = get_user_by_email(user_email) - # STEP 4: Check if user exists and user is created using oidc and oidc orgnaization matches this one - if user: - login(request, user, backend=BACKEND) - # Redirect the user home page - return redirect('pages:home') - else: - return register_and_authenticate_user(request, user_email, user_nickname, organization) + user_email = user_info.get("email", None) + user_nickname = user_info.get("nickname", None) + if not user_email: + context["error"] = "Unable to extract email from user info." + return render(request, 'oidc/oidc_complete.html', context) - else: - context["error"] = "Invalid Organization ID!" - except Exception as e: - context["error"] = f"{e}" + user_email = str(user_email).strip().lower() - return render(request, 'oidc/oidc_complete.html', context) + if user_nickname is not None: + user_nickname = str(user_nickname).strip() or None + + # get user with this email + user = get_user_by_email(user_email) + + # STEP 4: Check if user exists and user is created using oidc and oidc orgnaization matches this one + if user: + login(request, user, backend=BACKEND) + # Redirect the user home page + return redirect('pages:home') + else: + return register_and_authenticate_user(request, user_email, user_nickname, organization) + + except Exception as e: + context["error"] = f"{e}" + return render(request, 'oidc/oidc_complete.html', context) def get_access_token(organization, authorization_code):