Skip to content

fix(scim source): Make SCIM group and user handling fully SCIM 2.0 compliant#15046

Closed
amritbrartech wants to merge 4 commits intogoauthentik:mainfrom
amritbrartech:feature/scim-source-fix
Closed

fix(scim source): Make SCIM group and user handling fully SCIM 2.0 compliant#15046
amritbrartech wants to merge 4 commits intogoauthentik:mainfrom
amritbrartech:feature/scim-source-fix

Conversation

@amritbrartech
Copy link

Details

This PR enhances SCIM source functionality in Authentik to ensure full compliance with the SCIM 2.0 specification for both users and groups. Key changes include:

SCIM 2.0 Compliance Enhancements:
• Updated PATCH handling for groups and users to support SCIM 2.0 operations (add, replace, remove).
• Corrected schemas usage in SCIM payloads for both users and groups.
• Added support for PUT operations on group and user endpoints.
• Fixed error handling to return proper SCIM error responses with appropriate scimType.

Group Handling Improvements:
• Replaced incorrect schema in group SCIM response.
• Ensured members field is handled correctly during PATCH operations.
• Guarded against invalid UUID inputs for group IDs.

User Handling Improvements:
• Added PATCH endpoint with proper SCIM operation support (e.g., update userName).
• Normalized startIndex parsing and improved pagination response compliance.

General Enhancements:
• Introduced SCIMValidationError exception for standardized error messages.
• Included JSONParser alongside SCIMParser to support broader content types.
• Improved robustness of group_to_scim and user_to_scim by validating existence of internal objects.


Checklist

  • Local tests pass (ak test authentik/)
  • The code has been formatted (make lint-fix)

If an API change has been made

  • The API schema has been updated (make gen-build)

If changes to the frontend have been made

  • The code has been formatted (make web)

If applicable

  • The documentation has been updated
  • The documentation has been formatted (make website)

@amritbrartech amritbrartech requested a review from a team as a code owner June 14, 2025 15:13
@netlify
Copy link

netlify bot commented Jun 14, 2025

Deploy Preview for authentik-docs canceled.

Name Link
🔨 Latest commit e8483b8
🔍 Latest deploy log https://app.netlify.com/projects/authentik-docs/deploys/684d9193c86ddb0008afce92

@netlify
Copy link

netlify bot commented Jun 14, 2025

Deploy Preview for authentik-storybook canceled.

Name Link
🔨 Latest commit e8483b8
🔍 Latest deploy log https://app.netlify.com/projects/authentik-storybook/deploys/684d919360e3770008a010ff

@codecov
Copy link

codecov bot commented Jun 14, 2025

Codecov Report

Attention: Patch coverage is 33.87097% with 82 lines in your changes missing coverage. Please review.

Project coverage is 92.61%. Comparing base (ee47803) to head (e8483b8).
Report is 108 commits behind head on main.

Files with missing lines Patch % Lines
authentik/sources/scim/views/v2/groups.py 29.57% 50 Missing ⚠️
authentik/sources/scim/views/v2/users.py 37.25% 32 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #15046      +/-   ##
==========================================
- Coverage   92.76%   92.61%   -0.15%     
==========================================
  Files         815      815              
  Lines       42117    42231     +114     
==========================================
+ Hits        39068    39111      +43     
- Misses       3049     3120      +71     
Flag Coverage Δ
e2e 47.65% <33.87%> (+0.06%) ⬆️
integration 24.29% <0.00%> (-0.07%) ⬇️
unit 90.46% <20.96%> (-0.20%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

query |= Q(uuid=member.value)
if query:
group.users.set(User.objects.filter(query))
group.users.set(User.objects.filter(query))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now always runs a filter(query) even if query is an empty Q() (which matches everything). It could unintentionally set all users if members are empty. I'd add back if query:

if not isinstance(value, list):
value = [value]
for member in value:
member_id = member.get("value")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This member is always a dict, but malformed input might send strings or nested formats. I'd suggest adding validation such as if not isinstance(member, dict) and raise a SCIMValidationError.

path = op.get("path", "")
value = op.get("value")

if path.lower() == "displayname":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only lowercase path, but SCIM allows json path-like notation (members[value eq "abc"]) or nested fields.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this filter parsing we should really be using something like https://github.com/15five/scim2-filter-parser as it does a correct token -> AST parsing (that library seems to not have been updated in quite a while so we might want to vendor it)

"totalResults": page.paginator.count,
"itemsPerPage": page.paginator.per_page,
"startIndex": page.start_index(),
"startIndex": start_index,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uses user-supplied startIndex, not page.start_index() which reflects actual pagination result. I'd use page.start_index() to indicate the real start.

path = op.get("path", "")
value = op.get("value")

if path.lower() == "username" or path.lower() == "userName":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only userName is handled. SCIM clients may send emails, name.familyName, etc.

data["userName"] = value
elif op_type == "remove":
data.pop("userName", None)
# Add more SCIM attribute handling as needed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants