Skip to content

Create MemberOidcProvisioner Service#342

Merged
nkutub merged 4 commits intomainfrom
nkutub/OSCER-330-member-oidc-provisioner-service
Mar 16, 2026
Merged

Create MemberOidcProvisioner Service#342
nkutub merged 4 commits intomainfrom
nkutub/OSCER-330-member-oidc-provisioner-service

Conversation

@nkutub
Copy link
Contributor

@nkutub nkutub commented Mar 13, 2026

Overview

Implements Story 2: MemberOidcProvisioner Service from the Member SSO Implementation Stories.

This PR adds a provisioner service that creates or updates member users from citizen IdP OIDC claims, following the same pattern as StaffUserProvisioner but without role mapping logic.

Changes

New Files

File Description
app/services/member_oidc_provisioner.rb Provisioner service for member OIDC users
spec/services/member_oidc_provisioner_spec.rb Comprehensive test coverage

Modified Files

File Description
spec/support/sso_helpers.rb Added member OIDC test helpers

Implementation Details

MemberOidcProvisioner

provisioner = MemberOidcProvisioner.new
user = provisioner.provision!(claims)
# claims: { uid:, email:, name: }

Behavior:

  • Finds existing users by uid (not email) to handle email changes correctly
  • Creates new user with provider: "member_oidc" if not found
  • Updates email and full_name on every login
  • Sets mfa_preference ||= "opt_out" so IdP handles MFA (no app MFA challenge)
  • Does NOT set or change role (members have no staff role)
  • Validates required claims (uid, email) and raises ArgumentError if missing
  • Does NOT raise for "no role" (unlike StaffUserProvisioner with deny mode)

Key difference from StaffUserProvisioner:

Aspect StaffUserProvisioner MemberOidcProvisioner
Provider "sso" "member_oidc"
Role mapping Uses RoleMapper None (no role logic)
Access denied Raises if no matching role Never raises for role
Groups claim Required for role Not used
Region claim Synced if present Not used

Test Helpers Added to sso_helpers.rb

Helper Purpose
mock_member_claims(overrides = {}) Mock claims for provisioner tests
mock_member_omniauth_hash(overrides = {}) Mock OmniAuth auth hash for controller tests
setup_member_omniauth_mock(auth_hash = nil) Configure OmniAuth test mode for member OIDC
setup_member_omniauth_failure(message) Configure OmniAuth failure for member OIDC
reset_member_omniauth Reset member OIDC mock auth
reset_all_omniauth Reset both SSO and member OIDC mocks
mock_member_oidc_config(overrides = {}) Mock Rails.application.config.member_oidc
configure_member_oidc_for_test(config = nil) Stub member OIDC config in tests

Acceptance Criteria

Criteria Status
provision!(claims) accepts { uid:, email:, name: }
Finds user by uid; if not found, creates with provider: "member_oidc"
On every call, updates email and full_name from claims
Does not set or change role
Sets `user.mfa_preference
Returns the user; does not raise for "no role"
Validates required claims (uid, email) and raises ArgumentError if missing

Test Coverage

Scenario Tests
First login creates user with correct provider and attributes
Subsequent login finds by UID and updates email/name
Email change in IdP updates user (match by UID)
New and existing member OIDC users get mfa_preference set to opt_out
Raises when uid or email missing
Preserves existing role if user was previously staff

Preview environment for reporting-app

♻️ Environment destroyed ♻️

@nkutub nkutub requested a review from a team as a code owner March 13, 2026 21:22
@nkutub nkutub requested a review from baonguyenNava March 13, 2026 21:22
Copy link
Contributor

@baonguyenNava baonguyenNava left a comment

Choose a reason for hiding this comment

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

Looks good, as long as UID collisions never happen between different SSO providers. Highly unlikely though.

@nkutub nkutub merged commit 1859c1b into main Mar 16, 2026
11 checks passed
@nkutub nkutub deleted the nkutub/OSCER-330-member-oidc-provisioner-service branch March 16, 2026 03:47
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.

2 participants