Skip to content

Add SSO group mapping from IdP claims to ICP groups - #780

Open
tharindu-nw wants to merge 12 commits into
wso2:mainfrom
tharindu-nw:sso-map
Open

Add SSO group mapping from IdP claims to ICP groups#780
tharindu-nw wants to merge 12 commits into
wso2:mainfrom
tharindu-nw:sso-map

Conversation

@tharindu-nw

@tharindu-nw tharindu-nw commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Add SSO group mapping from IdP claims to ICP groups

Summary

ICP can already authenticate users through OIDC SSO, but authorization has always
been resolved purely from ICP database state — users are placed in ICP groups by
hand, groups receive scoped ICP roles, and roles carry permissions.

This PR lets an SSO deployment drive group membership from the identity
provider. An IdP group or role claim value is mapped to an existing ICP group;
on every SSO login the user's federated memberships are reconciled from their
token claims. ICP remains the source of role definitions and scoping.

Two things are deliberately not federated, and reviewers should read the rest
of this description with them in mind:

  • ICP roles and permissions stay local. What comes from the IdP is which
    groups a user belongs to. The group's group_role_mapping rows continue to
    decide what that membership actually grants.
  • Granular access scoping is unchanged. It is still expressed by
    group_role_mapping — a group-role mapping created at project level grants
    project-level access only. Mapping an IdP value to a group does not widen or
    narrow that.

Deployment modes

Three configuration values produce three supported states (plus one rejected
combination):

# ssoEnabled passwordLoginDisabled federatedAccessControlEnabled Meaning
1 true false false Password + SSO login, manual access control (existing default SSO behaviour)
2 true true false SSO-only login, manual access control
3 true true true SSO-only login, federated access control
4 true false true Rejected at startup — federated access control combined with password login is not supported for now

In mode 3, user↔group membership is owned by the IdP: manual membership
additions are blocked, while removals remain allowed (removals only reduce
access and are needed to revoke a stale super admin or clean up rows created
before the flag was enabled). Group, role and permission management stays fully
enabled in every mode.

New configuration

All values go in icp_server/Config.toml alongside the existing SSO block.

Added by this PR

Key Type Default Purpose
passwordLoginDisabled boolean false Disable local username/password login (SSO-only). Rejects /auth/login with 403 and stops advertising local user-store capabilities.
ssoAdminClaim string "" Claim used to identify SSO super admins, e.g. groups. Supports dotted paths (realm_access.roles).
ssoAdminValues string[] [] Claim values that grant Super Admin on login, e.g. ["icp-platform-admins"].
federatedAccessControlEnabled boolean false Manage group membership from IdP claims via SSO mappings. Requires passwordLoginDisabled = true.

Existing SSO keys, for context

ssoEnabled, ssoIssuer, ssoAuthorizationEndpoint, ssoTokenEndpoint,
ssoLogoutEndpoint, ssoJwksUrl, ssoClientId, ssoClientSecret,
ssoRedirectUri, ssoUsernameClaim (default email), ssoScopes (default
["openid","email","profile"]), ssoAllowInsecureTLS.

To receive group claims, the IdP must issue them in the ID token and the scope
must be requested — e.g. ssoScopes = ["openid","email","profile","groups"].

Example: mode 3

ssoEnabled = true
ssoIssuer = "https://idp.example.com/oauth2/token"
ssoAuthorizationEndpoint = "https://idp.example.com/oauth2/authorize"
ssoTokenEndpoint = "https://idp.example.com/oauth2/token"
ssoLogoutEndpoint = "https://idp.example.com/oidc/logout"
ssoJwksUrl = "https://idp.example.com/oauth2/jwks"
ssoClientId = "icp"
ssoClientSecret = "$secret{ssoClientSecret}"
ssoRedirectUri = "https://icp.example.com/auth/callback"
ssoUsernameClaim = "email"
ssoScopes = ["openid", "email", "profile", "groups"]

passwordLoginDisabled = true
ssoAdminClaim = "groups"
ssoAdminValues = ["icp-platform-admins"]
federatedAccessControlEnabled = true

Startup validation

Invalid combinations fail fast with a message naming the offending key:

  • passwordLoginDisabled = true requires ssoEnabled = true, a non-empty
    ssoAdminClaim, and at least one non-empty ssoAdminValues entry.
  • federatedAccessControlEnabled = true requires both ssoEnabled = true
    and passwordLoginDisabled = true.

Derived frontend runtime config

Generated into the served config.json by webserver.bal — not set by hand:
VITE_SSO_ENABLED, VITE_SSO_ISSUER, VITE_PASSWORD_LOGIN_DISABLED,
VITE_FEDERATED_ACCESS_CONTROL_ENABLED.

How it works

Login flow

  1. User completes OIDC login; ICP validates the ID token against the configured
    JWKS and issuer.
  2. The user is created if they do not exist (JIT provisioning), keyed on
    ssoUsernameClaim.
  3. Super admin bootstrap — if any value extracted from ssoAdminClaim
    matches ssoAdminValues, the user is idempotently added to the built-in
    Super Admins group as a manual membership. This grant is intentionally
    sticky: it is not removed when the claim later disappears, only when another
    super admin removes it. It is exempt from the mode-3 guard, since it is the
    bootstrap path.
  4. Federated membership sync — enabled mappings for the token's issuer are
    matched against the token claims, and federated_group_user_mapping is
    reconciled inside a transaction scoped to exactly one (org, issuer, user):
    existing rows get last_seen_at refreshed, missing rows are inserted, stale
    rows are deleted. Manual memberships and rows owned by other issuers are
    never touched. A reconciliation failure fails the login rather than issuing a
    token from stale authorization state.
  5. The ICP JWT is generated after reconciliation, so returned permissions
    reflect the current state.

Claim extraction

The full validated ID token payload is retained, and claim values are read by
dotted path: groups, roles, realm_access.roles,
resource_access.icp.roles. String arrays are the primary format; a single
string is normalized to one value; non-string entries in mixed arrays are
ignored; missing or unsupported shapes yield an empty list and log at debug
level.

Permission resolution

A new view v_effective_group_user_mapping unions manual memberships from
group_user_mapping with SSO-owned memberships from
federated_group_user_mapping. The project, integration and environment access
views are rebuilt on top of it, and getUserEffectivePermissions() /
getAllUserPermissions() read through it — so hasPermission(),
hasAnyPermission(), JWT scope generation and the /runtime-status WebSocket
upgrade path all observe federated memberships without individual changes.

isUserInGroup() deliberately stays manual-only: the super admin bootstrap uses
it to decide whether to write the sticky manual row.

Membership source

User and group membership responses expose manual, federated, or
manual_and_federated, so the UI can badge SSO-managed rows and render them
read-only in local removal controls. The manual user-group update endpoint
compares against manual memberships only, so an effective SSO membership can
never be silently converted into a local assignment.

Scoped mappings

Mappings can be created at organization, project or integration level.
sso_group_mappings carries nullable project_uuid / integration_uuid with a
check constraint that an integration requires a project.

The mapping's scope records where the mapping is administered, letting
project or integration admins manage the mappings relevant to them. It does not
itself scope permissions — login sync applies all mappings for the issuer
regardless of the level they were created at. The access a user ends up with is
determined by the target group's group_role_mapping rows, exactly as for
manually assigned users.

So the worked example — an IdP france_eng role granting the Engineers role
within the France project — is set up by scoping the Engineers group's role to
the France project, then mapping france_eng to that group.

Listing shows mappings from every scope with their scope displayed; the UI offers
create/delete only for mappings matching the current level, while the API
authorizes at the mapping's own scope (so an org-level permission holder can
still manage lower-scoped mappings, since org scope subsumes them).

Mappings are immutable — created and deleted, never updated, mirroring how
group-role mappings behave. Changing a mapping means delete plus create, and
deleting a mapping removes the memberships it created on each affected user's
next login. There is no enable/disable toggle.

Groups and roles remain org-level entities, so the "create a group from the
mapping dialog" shortcut is offered at org level only; the group dropdown always
lists all org groups.

Database changes

Two tables and one view, applied to fresh-install schemas for H2, MySQL,
PostgreSQL, MSSQL and Oracle
:

  • sso_group_mappings — maps a validated IdP claim value to an existing ICP
    group, with optional project/integration administration scope. Unique on
    (org_uuid, issuer, claim_name, claim_value, group_id), deliberately
    excluding the scope columns: the same claim-to-group pair has an identical
    sync effect at any scope, so duplicates across scopes would be redundant. This
    also avoids cross-database nullable-unique inconsistencies, since MSSQL treats
    NULLs as equal in unique indexes while the others do not.
  • federated_group_user_mapping — SSO-owned memberships, unique on
    (org_uuid, issuer, user_uuid, group_id, claim_name, claim_value).
  • v_effective_group_user_mapping — the manual ∪ federated union described
    above.

ICP groups are never auto-created from IdP values.

For existing deployments there is one consolidated migration per dialect,
add_sso_group_mapping_tables_<db>.sql, which creates the tables and then
recreates the access views in the correct order.

API

GET    /auth/orgs/{orgHandle}/sso/group-mappings
POST   /auth/orgs/{orgHandle}/sso/group-mappings
DELETE /auth/orgs/{orgHandle}/sso/group-mappings/{mappingId}
  • Guarded by user_mgt:manage_groups or user_mgt:update_group_roles. POST
    and DELETE authorize at the mapping's own scope; GET uses an org-level read
    check, since every level shows the same list.
  • POST accepts optional projectUuid / integrationUuid, validating that the
    project and integration exist and that the integration belongs to the project.
  • GET returns groupName, projectUuid, integrationUuid, projectName and
    integrationName for display.
  • There is no PUT — mappings are immutable and a PUT returns 405.
  • Errors: 409 on duplicates (naming the existing mapping's scope), 404 for
    unknown group/project/integration or mapping, 400 for empty issuer, claim
    name or claim value and for an integration without a project, 403 when the
    caller lacks the permission at the requested scope.

Also added: a server-side 403 on POST /auth/orgs/{orgHandle}/users whenever
passwordLoginDisabled = true (previously only hidden in the UI), and a 403 on
manual membership additions in mode 3.

UI

  • An SSO Mappings tab under Access Control at organization, project and
    integration level, shown when SSO is enabled.
  • Create dialog defaults the issuer from runtime config and the claim name to
    groups, takes the IdP group or role value as free text, and requires
    selecting an existing ICP group. At project/integration level the scope is
    pre-filled and locked.
  • Responsive desktop-table and compact mobile layouts; scope column; delete
    offered only for mappings at the current level.
  • SSO source badges in user lists, user detail and group detail views;
    federated-only memberships are read-only in local removal controls.
  • In mode 3 the "Assign Groups" and "Add Users" buttons are hidden; removal
    controls remain.
  • The login page hides the username/password form when password login is
    disabled.

Verification

  • Unit/integration: full H2 suite green — 124 passing, 0 failing.
  • Schema, per dialect: fresh install and the upgrade migration were applied
    to MySQL, MSSQL and PostgreSQL, and both paths were confirmed to converge
    on the same object set (tables, views, foreign keys, check constraint, unique
    constraints, indexes).
  • Functional, end-to-end against WSO2 IS 7.2.0 on MySQL, MSSQL and
    PostgreSQL using the real authorization-code flow: JIT provisioning, super
    admin grant plus idempotency and stickiness, mapping CRUD across all scopes,
    cross-scope duplicate conflicts, immutability, membership sync, stale-row
    removal on claim removal, manual memberships surviving sync, combined
    membership badges, delete-mapping-removes-membership, mode-3 guards including
    the bootstrap exemption, project-scoped roles granting at project scope but not
    org scope, membershipSource resolution, and the /runtime-status WebSocket
    upgrade succeeding for a user whose permissions come only from a federated
    group.

Not covered: Oracle (scripts added, no instance available to exercise them),
and the UI was verified at the API and static-gating level rather than by
clicking through a browser.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@tharindu-nw, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 506b239c-4f63-40c2-ae4c-df906df171cc

📥 Commits

Reviewing files that changed from the base of the PR and between 03e022d and 5c28563.

📒 Files selected for processing (7)
  • icp_server/modules/types/types.bal
  • icp_server/resources/db/init-scripts/h2_init.sql
  • icp_server/resources/db/init-scripts/mssql_init.sql
  • icp_server/resources/db/init-scripts/mysql_init.sql
  • icp_server/resources/db/init-scripts/oracle_init.sql
  • icp_server/resources/db/init-scripts/postgresql_init.sql
  • icp_server/resources/db/migration-scripts/README.md
📝 Walkthrough

Walkthrough

This change adds SSO-only login controls, configurable SSO administrator claims, federated group mappings, effective membership storage and RBAC views, scoped mapping APIs, frontend mapping management, membership-source indicators, database upgrade scripts, v1-to-v2 migration scripts, and related tests.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Frontend
  participant AuthService
  participant OIDCProvider
  participant Database
  User->>Frontend: Select SSO sign-in
  Frontend->>AuthService: Start OIDC login
  AuthService->>OIDCProvider: Authenticate and receive claims
  AuthService->>Database: Reconcile federated memberships
  Database-->>AuthService: Effective memberships
  AuthService-->>Frontend: Issue authenticated session
Loading

Suggested reviewers: anuruddhal

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the required template and omits several required sections like Purpose, Goals, Documentation, Security checks, and Test environment. Reformat the PR description to the repository template and add the missing sections, especially Purpose, Goals, Approach, Release note, Documentation, Security checks, and Test environment.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding SSO group mapping from IdP claims to ICP groups.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 20

🧹 Nitpick comments (14)
icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mysql.sql (1)

5-48: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider idempotent creates for consistency with the H2 migration script.

add_sso_group_mapping_tables_h2.sql uses CREATE TABLE IF NOT EXISTS / CREATE INDEX IF NOT EXISTS, while this script uses unguarded CREATE TABLE. A re-run or partial-failure retry aborts here. MySQL supports IF NOT EXISTS on CREATE TABLE, so the two migration scripts can behave the same way.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mysql.sql`
around lines 5 - 48, Make the MySQL migration idempotent by adding IF NOT EXISTS
to both CREATE TABLE statements, sso_group_mappings and
federated_group_user_mapping, matching the H2 migration behavior and allowing
safe reruns after partial failures.
frontend/src/components/LoginForm.tsx (1)

153-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use theme tokens instead of hardcoded colors.

#1e1e1e, #333, and #ccc are hardcoded in the sx props of both buttons. As per coding guidelines, "Use theme tokens with the sx prop instead of hardcoded colors and spacing values".

♻️ Proposed change
-              sx={{ mt: 1, bgcolor: '`#1e1e1e`', '&:hover': { bgcolor: '`#333`' }, textTransform: 'none', py: 1.2 }}
+              sx={{ mt: 1, bgcolor: 'common.black', '&:hover': { bgcolor: 'grey.800' }, textTransform: 'none', py: 1.2 }}
-              sx={{ textTransform: 'none', py: 1.2, borderColor: '`#ccc`', color: 'text.primary' }}
+              sx={{ textTransform: 'none', py: 1.2, borderColor: 'divider', color: 'text.primary' }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/LoginForm.tsx` around lines 153 - 176, Update the
`sx` props on the password and SSO buttons in `LoginForm` to replace hardcoded
colors `#1e1e1e`, `#333`, and `#ccc` with appropriate MUI theme palette tokens,
preserving the existing button appearance and hover behavior.

Source: Coding guidelines

frontend/src/pages/AccessControl.tsx (2)

33-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fragile slice(0, N) coupling to array order.

ORG_TABS.slice(0, 3) and PROJECT_TABS.slice(0, 2) assume 'sso-mappings' stays the last element of each tuple; reordering either constant later would silently drop the wrong tab instead of failing to compile.

♻️ Filter by name instead of position
-  const orgTabs: readonly string[] = window.API_CONFIG.ssoEnabled ? ORG_TABS : ORG_TABS.slice(0, 3);
+  const orgTabs: readonly string[] = window.API_CONFIG.ssoEnabled ? ORG_TABS : ORG_TABS.filter((t) => t !== 'sso-mappings');

Apply the equivalent change for PROJECT_TABS.slice(0, 2).

Also applies to: 54-54, 106-106, 167-168, 215-216

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/AccessControl.tsx` around lines 33 - 34, Replace the
positional ORG_TABS.slice(0, 3) and PROJECT_TABS.slice(0, 2) usage with
name-based filtering that excludes 'sso-mappings'. Apply this consistently at
all referenced usages while preserving the existing tab ordering and behavior
for the remaining tabs.

71-71: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inconsistent scrollable-tabs behavior across scopes.

Org-level tabs (lines 71, 123) use variant="scrollable" scrollButtons="auto", but project/component-level tabs (lines 184, 244) don't, despite both now carrying an extra SSO tab that can overflow on narrow viewports.

Also applies to: 123-123, 184-184, 244-244

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/AccessControl.tsx` at line 71, Make the tab configuration
consistent across scopes by adding variant="scrollable" and scrollButtons="auto"
to the project- and component-level Tabs instances near the existing org-level
configurations. Update the Tabs elements at all four referenced locations so the
extra SSO tab remains accessible on narrow viewports while preserving their
current navigation and value behavior.
frontend/src/pages/access-control/SSOMappingsTab.tsx (2)

71-76: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Editable Issuer field risks silent mapping failures.

The Issuer field defaults from window.API_CONFIG.ssoIssuer but remains freely editable; since mapping matching in oidc.bal requires an exact string match against the token's iss claim, a typo here causes the mapping to silently never apply, with no immediate feedback. Please confirm whether multiple issuers are actually supported; if not, consider making this field read-only.

Also applies to: 107-107

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/access-control/SSOMappingsTab.tsx` around lines 71 - 76,
Confirm whether SSO mappings support multiple issuers; if they do not, update
the issuer input in SSOMappingsTab to be read-only while retaining the
window.API_CONFIG.ssoIssuer default and submitted value. If multiple issuers are
supported, preserve editability and add appropriate validation against accepted
issuer values before submission.

178-273: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

No search or pagination, unlike sibling list tabs.

UsersTab, GroupsTab, and RolesTab all provide search plus pagination for their lists; this table renders the full mapping list unbounded, which is inconsistent and may not scale for orgs with many mappings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/access-control/SSOMappingsTab.tsx` around lines 178 - 273,
Update the SSOMappingsTab mapping list flow around the mappings rendering to add
search and pagination, matching the established behavior and controls used by
UsersTab, GroupsTab, and RolesTab. Filter mappings by the relevant claim,
issuer, or group fields, paginate the filtered results, and render the paginated
collection in both mobile cards and the desktop ListingTable while preserving
empty-state and delete-action behavior.
icp_server/modules/storage/user_repository.bal (1)

154-194: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reduce redundant EXISTS subqueries and duplicate invocation cost.

The new query evaluates the same manual/federated membership check twice per group row (once in the CASE, once in WHERE) via correlated EXISTS. A LEFT JOIN computes both flags once and reuses them. This also compounds with the existing pattern elsewhere in this file where getGroupsForUser is called twice per user (once for groups, once for groupCount, e.g. in getAllUsersV2), so the added subquery cost is paid twice per user in that list endpoint.

⚡ Suggested query rewrite using LEFT JOIN
-    stream<record {|string group_id; string group_name; string description?; string membership_source;|}, sql:Error?> groupStream = dbClient->query(
-        `SELECT g.group_id, g.group_name, g.description,
-                CASE
-                    WHEN EXISTS (
-                        SELECT 1 FROM group_user_mapping gum
-                        WHERE gum.user_uuid = ${userId} AND gum.group_id = g.group_id
-                    ) AND EXISTS (
-                        SELECT 1 FROM federated_group_user_mapping fgm
-                        WHERE fgm.user_uuid = ${userId} AND fgm.group_id = g.group_id
-                    ) THEN 'manual_and_federated'
-                    WHEN EXISTS (
-                        SELECT 1 FROM federated_group_user_mapping fgm
-                        WHERE fgm.user_uuid = ${userId} AND fgm.group_id = g.group_id
-                    ) THEN 'federated'
-                    ELSE 'manual'
-                END AS membership_source
-         FROM user_groups g
-         WHERE EXISTS (
-             SELECT 1 FROM group_user_mapping gum
-             WHERE gum.user_uuid = ${userId} AND gum.group_id = g.group_id
-         ) OR EXISTS (
-             SELECT 1 FROM federated_group_user_mapping fgm
-             WHERE fgm.user_uuid = ${userId} AND fgm.group_id = g.group_id
-         )
-         ORDER BY g.group_name ASC`
-    );
+    stream<record {|string group_id; string group_name; string description?; string membership_source;|}, sql:Error?> groupStream = dbClient->query(
+        `SELECT g.group_id, g.group_name, g.description,
+                CASE
+                    WHEN gum.user_uuid IS NOT NULL AND fgm.user_uuid IS NOT NULL THEN 'manual_and_federated'
+                    WHEN fgm.user_uuid IS NOT NULL THEN 'federated'
+                    ELSE 'manual'
+                END AS membership_source
+         FROM user_groups g
+         LEFT JOIN group_user_mapping gum ON gum.group_id = g.group_id AND gum.user_uuid = ${userId}
+         LEFT JOIN federated_group_user_mapping fgm ON fgm.group_id = g.group_id AND fgm.user_uuid = ${userId}
+         WHERE gum.user_uuid IS NOT NULL OR fgm.user_uuid IS NOT NULL
+         ORDER BY g.group_name ASC`
+    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/modules/storage/user_repository.bal` around lines 154 - 194,
Update getGroupsForUser so each group’s manual and federated membership status
is computed once through LEFT JOIN-derived flags, then reuse those flags for
both filtering and membership_source classification instead of repeating
correlated EXISTS subqueries. Preserve the current group results, ordering, and
membership_source values.
icp_server/tests/sso_config_validation_tests.bal (1)

75-83: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider also covering an empty adminValues list.

testSSOOnlyWithoutAdminClaimIsRejected covers an empty adminClaim, but an SSO-only deployment configured with adminClaim set and adminValues = [] would leave no path to an administrator. A parallel test would pin down whether validation rejects that combination.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/tests/sso_config_validation_tests.bal` around lines 75 - 83, Add a
parallel validation test alongside testSSOOnlyWithoutAdminClaimIsRejected that
builds an SSO-only configuration with a valid adminClaim and an empty
adminValues list, then asserts validateSSOConfig returns an error. Keep the test
focused on ensuring SSO-only deployments cannot be configured without any
administrator values.
icp_server/tests/auth_tests_v2.bal (1)

253-292: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider adding an authorization test for the new SSO mapping endpoints.

The new suite covers happy path, validation, immutability, and deletion, but not access control. Since these endpoints govern group membership derived from IdP claims, a case asserting that a caller without the group-management permission is rejected would protect the endpoint contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/tests/auth_tests_v2.bal` around lines 253 - 292, Add an
authorization test alongside testCreateSSOGroupMapping covering the SSO
group-mapping endpoints, using a caller that lacks the group-management
permission and asserting the request is rejected with the expected unauthorized
status. Reuse the existing authV2Client, endpoint path, and test authentication
setup, and ensure the test validates access control rather than mapping
behavior.
icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_postgresql.sql (1)

5-56: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Migration is not re-runnable, unlike the Oracle sibling.

icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_oracle.sql documents itself as idempotent and guards every object creation. This script errors on a second run or after a partially applied migration. PostgreSQL supports IF NOT EXISTS for tables and indexes, and CREATE OR REPLACE TRIGGER (PG 14+); a DROP TRIGGER IF EXISTS before creation works on older versions.

♻️ Suggested guards
-CREATE TABLE sso_group_mappings (
+CREATE TABLE IF NOT EXISTS sso_group_mappings (
@@
-CREATE INDEX idx_sgm_org_uuid ON sso_group_mappings(org_uuid);
-CREATE INDEX idx_sgm_issuer_claim ON sso_group_mappings(issuer, claim_name, claim_value);
-CREATE INDEX idx_sgm_group_id ON sso_group_mappings(group_id);
-CREATE INDEX idx_sgm_project_uuid ON sso_group_mappings(project_uuid);
-CREATE INDEX idx_sgm_integration_uuid ON sso_group_mappings(integration_uuid);
+CREATE INDEX IF NOT EXISTS idx_sgm_org_uuid ON sso_group_mappings(org_uuid);
+CREATE INDEX IF NOT EXISTS idx_sgm_issuer_claim ON sso_group_mappings(issuer, claim_name, claim_value);
+CREATE INDEX IF NOT EXISTS idx_sgm_group_id ON sso_group_mappings(group_id);
+CREATE INDEX IF NOT EXISTS idx_sgm_project_uuid ON sso_group_mappings(project_uuid);
+CREATE INDEX IF NOT EXISTS idx_sgm_integration_uuid ON sso_group_mappings(integration_uuid);
 
+DROP TRIGGER IF EXISTS update_sso_group_mappings_updated_at ON sso_group_mappings;
 CREATE TRIGGER update_sso_group_mappings_updated_at BEFORE UPDATE ON sso_group_mappings
     FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
 
-CREATE TABLE federated_group_user_mapping (
+CREATE TABLE IF NOT EXISTS federated_group_user_mapping (
@@
-CREATE INDEX idx_fgum_user_uuid ON federated_group_user_mapping(user_uuid);
-CREATE INDEX idx_fgum_group_id ON federated_group_user_mapping(group_id);
-CREATE INDEX idx_fgum_issuer_claim ON federated_group_user_mapping(issuer, claim_name, claim_value);
+CREATE INDEX IF NOT EXISTS idx_fgum_user_uuid ON federated_group_user_mapping(user_uuid);
+CREATE INDEX IF NOT EXISTS idx_fgum_group_id ON federated_group_user_mapping(group_id);
+CREATE INDEX IF NOT EXISTS idx_fgum_issuer_claim ON federated_group_user_mapping(issuer, claim_name, claim_value);
 
+DROP TRIGGER IF EXISTS update_federated_group_user_mapping_updated_at ON federated_group_user_mapping;
 CREATE TRIGGER update_federated_group_user_mapping_updated_at BEFORE UPDATE ON federated_group_user_mapping
     FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_postgresql.sql`
around lines 5 - 56, Make the PostgreSQL migration idempotent like the Oracle
sibling: guard both CREATE TABLE statements with IF NOT EXISTS, guard all CREATE
INDEX statements with IF NOT EXISTS, and make each update trigger safely
replaceable using CREATE OR REPLACE TRIGGER or DROP TRIGGER IF EXISTS before
creation, while preserving the existing definitions and constraints.
icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql (2)

148-148: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused variable. @seed_admin_id is set but the literal UUID is used at Lines 153 and 170; either use the variable or drop it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql` at line 148,
Remove the unused `@seed_admin_id` assignment, or replace the repeated literal
UUIDs at the migration statements around lines 153 and 170 with `@seed_admin_id`;
ensure the migration uses one consistent UUID source.

90-133: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

INSERT IGNORE downgrades all errors to warnings.

Beyond duplicate keys, it also suppresses foreign-key failures and truncation, so a partially migrated user set would be reported as success. Since Step 1a already clears conflicting non-admin rows, an explicit NOT EXISTS guard (as used in the SQL Server script) would keep genuine errors visible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql` around lines
90 - 133, The user migration INSERT in the dynamic SQL should not use INSERT
IGNORE, which suppresses non-duplicate errors. Remove INSERT IGNORE and add an
explicit NOT EXISTS guard against conflicting non-admin rows, matching the SQL
Server migration’s behavior while allowing genuine foreign-key and truncation
errors to surface.
frontend/src/pages/EditGroup.tsx (1)

322-332: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Membership-source label logic is duplicated across pages.

The same membershipSource → label mapping is repeated in EditUser.tsx and Profile.tsx. Extracting a small helper (e.g. in pages/access-control/utils) keeps the three views consistent if new source values are added later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/EditGroup.tsx` around lines 322 - 332, Extract the
repeated membershipSource-to-label mapping into a shared helper under
pages/access-control/utils, then update EditGroup, EditUser, and Profile to use
it for their membership-source labels. Preserve the existing labels for
federated, manual_and_federated, and local sources, and ensure all three views
use the same helper.
icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql (1)

9-23: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Quote dynamically composed identifiers.

Database names supplied through the configuration variables are concatenated directly into the generated statements. Using QUOTENAME(@old_db) (and the same for the two target databases) keeps identifier handling robust for names that are not plain identifiers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql` around lines 9
- 23, The dynamically composed database identifiers in the migration SQL should
be safely quoted. Update the generated statements using `@old_db`, `@new_main_db`,
and `@new_creds_db` to wrap each database name with QUOTENAME rather than
concatenating raw variable values, while preserving the existing query behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/RUNTIME_CONFIG.md`:
- Around line 24-27: Update the configuration keys documented in
RUNTIME_CONFIG.md to use VITE_PASSWORD_LOGIN_DISABLED, matching
frontend/public/config.json.example and frontend usage, and add the two SSO
configuration keys documented by the example config. Keep the documented values
and surrounding runtime configuration entries consistent with the example.

In `@frontend/src/pages/AccessControl.tsx`:
- Around line 36-45: Replace the duplicated responsive SSO label JSX in
AccessControl and OrgAccessControl with the existing SSO_TAB_LABEL constant,
matching its use in ProjectAccessControl and ComponentAccessControl.

In `@icp_server/auth_service.bal`:
- Around line 1295-1363: Constrain target-group validation in the mapping
creation flow around targetGroup so a caller authorized at project or
integration scope cannot select groups with role assignments outside that scope.
Either restrict eligible groups to the mapping’s administrative scope or require
org-level permission when the target group has broader-scope assignments, while
preserving the existing organization-membership check.
- Around line 3618-3652: Update grantSuperAdminFromSSOClaims and the surrounding
SSO reconciliation flow so claim-based Super Admin membership is managed as a
federated grant that is re-evaluated on every login, including removing the
membership when the configured admin claim no longer matches. Reuse the existing
federated reconciliation mechanism and preserve the current validation and error
handling.
- Around line 1457-1468: Update the deletion flow around
storage:deleteSSOGroupMapping to also remove matching rows from
federated_group_user_mapping using the mapping’s organization, issuer, claim,
and group identifiers. Perform this cleanup as part of the same operation, while
preserving the existing not-found response and internal-error handling.
- Around line 368-373: Gate the federated group synchronization in the flow
around `syncFederatedGroupsFromSSOClaims` with `federatedAccessControlEnabled`,
so the call and its error handling execute only when the feature is enabled.
Preserve the existing synchronization behavior and internal-server-error
response when the flag is enabled.
- Line 1757: Update the user-groups update flow around
storage:getUserManualGroups so the response distinguishes manual group IDs from
effective membership IDs: rename the current/final group ID fields to
manualGroupIds/addedGroupIds, or derive and return effective IDs including
federated memberships. Ensure federated groups are not reported as removed
solely because they are absent from the manual-group query.

In `@icp_server/Config.toml`:
- Around line 155-157: Update the commented configuration key in the SSO
settings block to use the declared passwordLoginDisabled name instead of
disablePasswordLogin, while preserving its default value and explanatory
comment.

In `@icp_server/custom_auth/OIDC_SETUP_GUIDE.md`:
- Around line 86-88: Replace disablePasswordLogin with passwordLoginDisabled in
the configuration table at icp_server/custom_auth/OIDC_SETUP_GUIDE.md lines
86-88 and in the FAQ instruction at lines 308-308, ensuring both documentation
references match the server configuration key.

In `@icp_server/modules/storage/auth_repository.bal`:
- Around line 758-782: The insert branch in the federated membership
reconciliation loop should treat duplicate-key failures as benign concurrency
outcomes. Update the INSERT execution in the flow around findFederatedMembership
to classify its SQL error with classifySqlError, suppress only the duplicate-key
result, and continue synchronization; propagate all other database errors
unchanged.
- Around line 702-716: Update the federated group membership insertion flow to
handle Oracle identity retrieval, alongside the existing integer and MSSQL
string parsing in the code returning the mapping ID. When Oracle returns a ROWID
instead of the generated ID, read back federated_group_user_mapping.id using the
inserted row’s unique mapping tuple, then log and return that integer ID;
preserve the existing failure path when no valid ID is found.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/README.md`:
- Around line 72-81: The README migration documentation should cover the DDL
performed by both scripts: creation of org_secrets, ensuring
mi_composite_app_artifacts, and adding runtimes.key_id with its foreign key.
Update the prerequisites to require CREATE and ALTER privileges in addition to
DML, and explicitly warn that existing non-admin accounts are deleted and
replaced, advising a backup before migration.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql`:
- Around line 154-166: The Step 2a credential merge must warn when no row is
updated despite the legacy admin user existing. Update the dynamic SQL execution
flow around `@sql` and sp_executesql to capture @@ROWCOUNT, then check for zero
updates alongside the presence of the legacy admin user and print a clear
warning; retain the existing success message for rows that are merged.
- Around line 271-299: Update the org_secrets DDL block to be re-runnable by
guarding CREATE TABLE and runtimes.key_id creation with existence checks
consistent with the later artifact block. Split the ALTER TABLE adding key_id
and the ALTER TABLE adding fk_runtime_key_id into separate sp_executesql
batches, with the foreign-key batch executed only after the column exists.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql`:
- Around line 318-372: Make the Step 5 DDL re-runnable by adding an existence
guard to the org_secrets CREATE TABLE statement and replacing the unconditional
runtimes ALTER TABLE additions with guarded checks for both the key_id column
and fk_runtime_key_id constraint. Preserve the existing schema definitions and
ensure each change can safely be executed after a partial migration.

In `@icp_server/resources/db/init-scripts/mssql_init.sql`:
- Around line 377-388: Update the trigger statements in
icp_server/resources/db/init-scripts/mssql_init.sql: within
trg_sso_group_mappings_updated_at, target the aliased table with UPDATE sgm, and
within the corresponding federated group mapping trigger, use UPDATE fgum. If
the same triggers exist in
icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql,
apply the identical alias-target changes there.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_h2.sql`:
- Around line 33-48: Change the user_uuid column definition in
federated_group_user_mapping from VARCHAR(36) to CHAR(36), keeping it NOT NULL
and preserving the existing foreign key to users(user_id) and all other
constraints.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql`:
- Around line 5-78: The SQL Server SSO migration is not idempotent because both
tables and triggers are created unconditionally. In
icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql:5-78,
wrap sso_group_mappings, federated_group_user_mapping,
trg_sso_group_mappings_updated_at, and
trg_federated_group_user_mapping_updated_at creation in IF OBJECT_ID(...) IS
NULL guards, matching the H2 variant. No direct change is needed in
icp_server/resources/db/migration-scripts/README.md:203-204 because the guards
preserve its idempotency statement.

In `@icp_server/resources/db/migration-scripts/README.md`:
- Around line 203-204: Correct the idempotency statement in the migration README
to exclude engines where add_sso_group_mapping_tables_mssql.sql lacks existence
guards, or update that script’s table and trigger creation to guard against
existing objects. Ensure rerunning the migration after partial failure succeeds
for the supported engines.

In `@icp_server/tests/sso_federated_mapping_tests.bal`:
- Around line 221-229: Ensure teardown always runs after failures in
testSSOGroupMappingStorage, testFederatedMembershipReconciliation, and
testDeletedMappingRemovesMembershipOnNextLogin by moving their cleanup calls
into an on fail block or shared `@test`:AfterEach/@test:AfterGroups hook. In
icp_server/tests/sso_federated_mapping_tests.bal#L221-L229, include
role-mapping, role, project, user, and group deletions; at `#L297-L298` and
`#L361-L362`, include the corresponding user and group deletions.

---

Nitpick comments:
In `@frontend/src/components/LoginForm.tsx`:
- Around line 153-176: Update the `sx` props on the password and SSO buttons in
`LoginForm` to replace hardcoded colors `#1e1e1e`, `#333`, and `#ccc` with
appropriate MUI theme palette tokens, preserving the existing button appearance
and hover behavior.

In `@frontend/src/pages/access-control/SSOMappingsTab.tsx`:
- Around line 71-76: Confirm whether SSO mappings support multiple issuers; if
they do not, update the issuer input in SSOMappingsTab to be read-only while
retaining the window.API_CONFIG.ssoIssuer default and submitted value. If
multiple issuers are supported, preserve editability and add appropriate
validation against accepted issuer values before submission.
- Around line 178-273: Update the SSOMappingsTab mapping list flow around the
mappings rendering to add search and pagination, matching the established
behavior and controls used by UsersTab, GroupsTab, and RolesTab. Filter mappings
by the relevant claim, issuer, or group fields, paginate the filtered results,
and render the paginated collection in both mobile cards and the desktop
ListingTable while preserving empty-state and delete-action behavior.

In `@frontend/src/pages/AccessControl.tsx`:
- Around line 33-34: Replace the positional ORG_TABS.slice(0, 3) and
PROJECT_TABS.slice(0, 2) usage with name-based filtering that excludes
'sso-mappings'. Apply this consistently at all referenced usages while
preserving the existing tab ordering and behavior for the remaining tabs.
- Line 71: Make the tab configuration consistent across scopes by adding
variant="scrollable" and scrollButtons="auto" to the project- and
component-level Tabs instances near the existing org-level configurations.
Update the Tabs elements at all four referenced locations so the extra SSO tab
remains accessible on narrow viewports while preserving their current navigation
and value behavior.

In `@frontend/src/pages/EditGroup.tsx`:
- Around line 322-332: Extract the repeated membershipSource-to-label mapping
into a shared helper under pages/access-control/utils, then update EditGroup,
EditUser, and Profile to use it for their membership-source labels. Preserve the
existing labels for federated, manual_and_federated, and local sources, and
ensure all three views use the same helper.

In `@icp_server/modules/storage/user_repository.bal`:
- Around line 154-194: Update getGroupsForUser so each group’s manual and
federated membership status is computed once through LEFT JOIN-derived flags,
then reuse those flags for both filtering and membership_source classification
instead of repeating correlated EXISTS subqueries. Preserve the current group
results, ordering, and membership_source values.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql`:
- Around line 9-23: The dynamically composed database identifiers in the
migration SQL should be safely quoted. Update the generated statements using
`@old_db`, `@new_main_db`, and `@new_creds_db` to wrap each database name with
QUOTENAME rather than concatenating raw variable values, while preserving the
existing query behavior.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql`:
- Line 148: Remove the unused `@seed_admin_id` assignment, or replace the repeated
literal UUIDs at the migration statements around lines 153 and 170 with
`@seed_admin_id`; ensure the migration uses one consistent UUID source.
- Around line 90-133: The user migration INSERT in the dynamic SQL should not
use INSERT IGNORE, which suppresses non-duplicate errors. Remove INSERT IGNORE
and add an explicit NOT EXISTS guard against conflicting non-admin rows,
matching the SQL Server migration’s behavior while allowing genuine foreign-key
and truncation errors to surface.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mysql.sql`:
- Around line 5-48: Make the MySQL migration idempotent by adding IF NOT EXISTS
to both CREATE TABLE statements, sso_group_mappings and
federated_group_user_mapping, matching the H2 migration behavior and allowing
safe reruns after partial failures.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_postgresql.sql`:
- Around line 5-56: Make the PostgreSQL migration idempotent like the Oracle
sibling: guard both CREATE TABLE statements with IF NOT EXISTS, guard all CREATE
INDEX statements with IF NOT EXISTS, and make each update trigger safely
replaceable using CREATE OR REPLACE TRIGGER or DROP TRIGGER IF EXISTS before
creation, while preserving the existing definitions and constraints.

In `@icp_server/tests/auth_tests_v2.bal`:
- Around line 253-292: Add an authorization test alongside
testCreateSSOGroupMapping covering the SSO group-mapping endpoints, using a
caller that lacks the group-management permission and asserting the request is
rejected with the expected unauthorized status. Reuse the existing authV2Client,
endpoint path, and test authentication setup, and ensure the test validates
access control rather than mapping behavior.

In `@icp_server/tests/sso_config_validation_tests.bal`:
- Around line 75-83: Add a parallel validation test alongside
testSSOOnlyWithoutAdminClaimIsRejected that builds an SSO-only configuration
with a valid adminClaim and an empty adminValues list, then asserts
validateSSOConfig returns an error. Keep the test focused on ensuring SSO-only
deployments cannot be configured without any administrator values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ae7ed87c-57df-4a1c-9166-102f8f119eab

📥 Commits

Reviewing files that changed from the base of the PR and between 87f45fa and c2a5159.

📒 Files selected for processing (47)
  • frontend/RUNTIME_CONFIG.md
  • frontend/public/config.json
  • frontend/public/config.json.example
  • frontend/src/api/auth.ts
  • frontend/src/api/authQueries.ts
  • frontend/src/components/LoginForm.tsx
  • frontend/src/config/api.ts
  • frontend/src/pages/AccessControl.tsx
  • frontend/src/pages/CreateGroup.tsx
  • frontend/src/pages/EditGroup.tsx
  • frontend/src/pages/EditUser.tsx
  • frontend/src/pages/Profile.tsx
  • frontend/src/pages/access-control/SSOMappingsTab.tsx
  • frontend/src/pages/access-control/UsersTab.tsx
  • frontend/src/paths.ts
  • icp_server/Config.toml
  • icp_server/Dependencies.toml
  • icp_server/auth_service.bal
  • icp_server/config.bal
  • icp_server/custom_auth/OIDC_SETUP_GUIDE.md
  • icp_server/init.bal
  • icp_server/modules/auth/oidc.bal
  • icp_server/modules/storage/auth_repository.bal
  • icp_server/modules/storage/user_repository.bal
  • icp_server/modules/types/auth_types.bal
  • icp_server/modules/types/types.bal
  • icp_server/resources/db/icp-1.2.x-to-2.x.x/README.md
  • icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql
  • icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql
  • icp_server/resources/db/init-scripts/h2_init.sql
  • icp_server/resources/db/init-scripts/mssql_init.sql
  • icp_server/resources/db/init-scripts/mysql_init.sql
  • icp_server/resources/db/init-scripts/oracle_init.sql
  • icp_server/resources/db/init-scripts/postgresql_init.sql
  • icp_server/resources/db/migration-scripts/README.md
  • icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_h2.sql
  • icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql
  • icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mysql.sql
  • icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_oracle.sql
  • icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_postgresql.sql
  • icp_server/tests/Config.toml
  • icp_server/tests/auth_tests_v2.bal
  • icp_server/tests/mock_oidc_provider.bal
  • icp_server/tests/oidc_tests.bal
  • icp_server/tests/sso_config_validation_tests.bal
  • icp_server/tests/sso_federated_mapping_tests.bal
  • icp_server/webserver.bal

Comment on lines +24 to +27
"VITE_OBSERVABILITY_URL": "https://localhost:9446/icp/observability",
"VITE_SSO_ENABLED": false,
"VITE_DISABLE_PASSWORD_LOGIN": false,
"VITE_ICP_VERSION": "2.0.0-SNAPSHOT"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Documented key name does not match frontend/public/config.json.example.

The doc uses VITE_DISABLE_PASSWORD_LOGIN while the example config and frontend read VITE_PASSWORD_LOGIN_DISABLED. Operators following this doc would set a key that is never read. The two new SSO keys are also missing here.

📝 Proposed doc fix
   "VITE_OBSERVABILITY_URL": "https://localhost:9446/icp/observability",
   "VITE_SSO_ENABLED": false,
-  "VITE_DISABLE_PASSWORD_LOGIN": false,
+  "VITE_SSO_ISSUER": "",
+  "VITE_PASSWORD_LOGIN_DISABLED": false,
+  "VITE_FEDERATED_ACCESS_CONTROL_ENABLED": false,
   "VITE_ICP_VERSION": "2.0.0-SNAPSHOT"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"VITE_OBSERVABILITY_URL": "https://localhost:9446/icp/observability",
"VITE_SSO_ENABLED": false,
"VITE_DISABLE_PASSWORD_LOGIN": false,
"VITE_ICP_VERSION": "2.0.0-SNAPSHOT"
"VITE_OBSERVABILITY_URL": "https://localhost:9446/icp/observability",
"VITE_SSO_ENABLED": false,
"VITE_SSO_ISSUER": "",
"VITE_PASSWORD_LOGIN_DISABLED": false,
"VITE_FEDERATED_ACCESS_CONTROL_ENABLED": false,
"VITE_ICP_VERSION": "2.0.0-SNAPSHOT"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/RUNTIME_CONFIG.md` around lines 24 - 27, Update the configuration
keys documented in RUNTIME_CONFIG.md to use VITE_PASSWORD_LOGIN_DISABLED,
matching frontend/public/config.json.example and frontend usage, and add the two
SSO configuration keys documented by the example config. Keep the documented
values and surrounding runtime configuration entries consistent with the
example.

Comment on lines +36 to +45
const SSO_TAB_LABEL = (
<>
<Box component="span" sx={{ display: { xs: 'inline', sm: 'none' } }}>
SSO
</Box>
<Box component="span" sx={{ display: { xs: 'none', sm: 'inline' } }}>
SSO Mappings
</Box>
</>
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Reuse SSO_TAB_LABEL instead of duplicating the JSX inline.

SSO_TAB_LABEL was introduced for exactly this purpose and is used correctly in ProjectAccessControl/ComponentAccessControl, but AccessControl and OrgAccessControl inline the identical JSX instead.

♻️ Reuse the shared label constant
-          {window.API_CONFIG.ssoEnabled && (
-            <Tab
-              label={
-                <>
-                  <Box component="span" sx={{ display: { xs: 'inline', sm: 'none' } }}>
-                    SSO
-                  </Box>
-                  <Box component="span" sx={{ display: { xs: 'none', sm: 'inline' } }}>
-                    SSO Mappings
-                  </Box>
-                </>
-              }
-            />
-          )}
+          {window.API_CONFIG.ssoEnabled && <Tab label={SSO_TAB_LABEL} />}

Apply the same change in both AccessControl (lines 75-88) and OrgAccessControl (lines 127-140).

Also applies to: 75-88, 127-140

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/AccessControl.tsx` around lines 36 - 45, Replace the
duplicated responsive SSO label JSX in AccessControl and OrgAccessControl with
the existing SSO_TAB_LABEL constant, matching its use in ProjectAccessControl
and ComponentAccessControl.

Comment on lines +368 to +373
error? federatedSyncResult = syncFederatedGroupsFromSSOClaims(userDetails.userId, userInfo.username, claims);
if federatedSyncResult is error {
log:printError("Error synchronizing SSO group memberships", federatedSyncResult,
username = userInfo.username);
return utils:createInternalServerError("Error synchronizing SSO group access");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Federated group sync is not gated on federatedAccessControlEnabled.

The flag gates manual membership edits (Lines 1518 and 1802) but not this sync. If mappings exist while the flag is off, federated rows are still written and become effective through v_effective_group_user_mapping, so the flag does not consistently control the feature. Consider gating the call, or documenting that mappings apply regardless of the flag.

🔧 Proposed gate
-        error? federatedSyncResult = syncFederatedGroupsFromSSOClaims(userDetails.userId, userInfo.username, claims);
-        if federatedSyncResult is error {
-            log:printError("Error synchronizing SSO group memberships", federatedSyncResult,
-                    username = userInfo.username);
-            return utils:createInternalServerError("Error synchronizing SSO group access");
+        if federatedAccessControlEnabled {
+            error? federatedSyncResult = syncFederatedGroupsFromSSOClaims(userDetails.userId, userInfo.username, claims);
+            if federatedSyncResult is error {
+                log:printError("Error synchronizing SSO group memberships", federatedSyncResult,
+                        username = userInfo.username);
+                return utils:createInternalServerError("Error synchronizing SSO group access");
+            }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
error? federatedSyncResult = syncFederatedGroupsFromSSOClaims(userDetails.userId, userInfo.username, claims);
if federatedSyncResult is error {
log:printError("Error synchronizing SSO group memberships", federatedSyncResult,
username = userInfo.username);
return utils:createInternalServerError("Error synchronizing SSO group access");
}
if federatedAccessControlEnabled {
error? federatedSyncResult = syncFederatedGroupsFromSSOClaims(userDetails.userId, userInfo.username, claims);
if federatedSyncResult is error {
log:printError("Error synchronizing SSO group memberships", federatedSyncResult,
username = userInfo.username);
return utils:createInternalServerError("Error synchronizing SSO group access");
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/auth_service.bal` around lines 368 - 373, Gate the federated group
synchronization in the flow around `syncFederatedGroupsFromSSOClaims` with
`federatedAccessControlEnabled`, so the call and its error handling execute only
when the feature is enabled. Preserve the existing synchronization behavior and
internal-server-error response when the flag is enabled.

Comment on lines +1295 to +1363
// Authorize at the mapping's administrative scope so project/integration
// scoped admins can manage mappings at their level but not broader ones.
types:AccessScope mappingScope = {orgUuid: storage:DEFAULT_ORG_ID};
if projectUuid is string {
mappingScope.projectUuid = projectUuid;
}
if integrationUuid is string {
mappingScope.integrationUuid = integrationUuid;
}
boolean|error hasPermission = auth:hasAnyPermission(userContext.userId,
[auth:PERMISSION_USER_MANAGE_GROUPS, auth:PERMISSION_USER_UPDATE_GROUP_ROLES], mappingScope);
if hasPermission is error {
log:printError("Error checking permissions", hasPermission, userId = userContext.userId);
return utils:createInternalServerError("Error checking permissions");
}
if !hasPermission {
return <http:Forbidden>{
body: {
message: "Insufficient permissions to create SSO group mappings at the requested scope"
}
};
}

string? projectName = ();
string? integrationName = ();
if projectUuid is string {
types:Project|error project = storage:getProjectById(projectUuid);
if project is error || project.orgId != storage:DEFAULT_ORG_ID {
return <http:NotFound>{
body: {
message: "Target project not found"
}
};
}
projectName = project.name;
}
if integrationUuid is string {
types:Component|error component = storage:getComponentById(integrationUuid);
if component is error || component.projectId != projectUuid {
return <http:NotFound>{
body: {
message: "Target integration not found in the specified project"
}
};
}
integrationName = component.displayName;
}

types:SSOGroupMappingInput inputWithOrg = {
issuer: mappingInput.issuer.trim(),
claimName: mappingInput.claimName.trim(),
claimValue: mappingInput.claimValue.trim(),
groupId: mappingInput.groupId.trim(),
orgUuid: storage:DEFAULT_ORG_ID
};
if projectUuid is string {
inputWithOrg.projectUuid = projectUuid;
}
if integrationUuid is string {
inputWithOrg.integrationUuid = integrationUuid;
}
types:Group|error targetGroup = storage:getGroupById(inputWithOrg.groupId);
if targetGroup is error || targetGroup.orgUuid != storage:DEFAULT_ORG_ID {
return <http:NotFound>{
body: {
message: "Target group not found"
}
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Target group is not constrained by the mapping's administrative scope.

Authorization is evaluated at the mapping's project/integration scope, but groupId is only validated for org membership (Line 1356). A caller authorized at a narrow scope can therefore create a mapping to any org group, including groups holding broader role assignments. Consider restricting selectable target groups, or requiring org-level permission when the target group carries role assignments outside the caller's scope.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/auth_service.bal` around lines 1295 - 1363, Constrain target-group
validation in the mapping creation flow around targetGroup so a caller
authorized at project or integration scope cannot select groups with role
assignments outside that scope. Either restrict eligible groups to the mapping’s
administrative scope or require org-level permission when the target group has
broader-scope assignments, while preserving the existing organization-membership
check.

Source: Path instructions

Comment on lines +1457 to +1468
error? deleteResult = storage:deleteSSOGroupMapping(mappingId, storage:DEFAULT_ORG_ID);
if deleteResult is error {
log:printError("Error deleting SSO group mapping", deleteResult, mappingId = mappingId);
if deleteResult.message().includes("not found") {
return <http:NotFound>{
body: {
message: "SSO group mapping not found"
}
};
}
return utils:createInternalServerError("Failed to delete SSO group mapping");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Deleting a mapping leaves derived federated memberships in place.

federated_group_user_mapping rows are reconciled per user at login, so memberships granted by this mapping persist until each affected user signs in again. Consider deleting the matching federated rows (same org/issuer/claim/group) as part of this operation so revocation takes effect promptly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/auth_service.bal` around lines 1457 - 1468, Update the deletion
flow around storage:deleteSSOGroupMapping to also remove matching rows from
federated_group_user_mapping using the mapping’s organization, issuer, claim,
and group identifiers. Perform this cleanup as part of the same operation, while
preserving the existing not-found response and internal-error handling.

Comment on lines +377 to +388
CREATE TRIGGER trg_sso_group_mappings_updated_at
ON sso_group_mappings
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE sso_group_mappings
SET updated_at = GETDATE()
FROM sso_group_mappings sgm
INNER JOIN inserted i ON sgm.mapping_id = i.mapping_id;
END;
GO

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Both new MSSQL triggers use an incorrect UPDATE ... FROM target. Each trigger aliases the target table in FROM but names the base table in UPDATE, which T-SQL rejects for duplicate exposed names. The same construction was applied twice.

  • icp_server/resources/db/init-scripts/mssql_init.sql#L377-L388: change UPDATE sso_group_mappings to UPDATE sgm.
  • icp_server/resources/db/init-scripts/mssql_init.sql#L412-L423: change UPDATE federated_group_user_mapping to UPDATE fgum.

Please also mirror the fix in icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql if it carries the same triggers.

📍 Affects 1 file
  • icp_server/resources/db/init-scripts/mssql_init.sql#L377-L388 (this comment)
  • icp_server/resources/db/init-scripts/mssql_init.sql#L412-L423
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/resources/db/init-scripts/mssql_init.sql` around lines 377 - 388,
Update the trigger statements in
icp_server/resources/db/init-scripts/mssql_init.sql: within
trg_sso_group_mappings_updated_at, target the aliased table with UPDATE sgm, and
within the corresponding federated group mapping trigger, use UPDATE fgum. If
the same triggers exist in
icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql,
apply the identical alias-target changes there.

Comment on lines +33 to +48
CREATE TABLE IF NOT EXISTS federated_group_user_mapping (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
org_uuid INT NOT NULL DEFAULT 1,
issuer VARCHAR(255) NOT NULL,
user_uuid VARCHAR(36) NOT NULL,
group_id VARCHAR(36) NOT NULL,
claim_name VARCHAR(128) NOT NULL,
claim_value VARCHAR(255) NOT NULL,
last_seen_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_fed_group_user_org FOREIGN KEY (org_uuid) REFERENCES organizations (org_id) ON DELETE CASCADE,
CONSTRAINT fk_fed_group_user_user FOREIGN KEY (user_uuid) REFERENCES users (user_id) ON DELETE CASCADE,
CONSTRAINT fk_fed_group_user_group FOREIGN KEY (group_id) REFERENCES user_groups (group_id) ON DELETE CASCADE,
CONSTRAINT unique_fed_group_user_claim UNIQUE (org_uuid, issuer, user_uuid, group_id, claim_name, claim_value)
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f 'h2_init.sql|mssql_init.sql' -x rg -n 'user_id|user_uuid' {}

Repository: wso2/integration-control-plane

Length of output: 6097


🏁 Script executed:

#!/bin/bash
set -euo pipefail

init_files="$(fd -t f 'h2_init.sql|mssql_init.sql|-init.sql' icp_server/resources/db/migration-scripts | sort)"

printf 'Files:\n'
printf '%s\n' "$init_files"

printf '\nH2 relevant user/group columns:\n'
for f in "$init_files"; do
  if [[ "$f" != *h2* ]]; then continue; fi
  sed -n '200,240p;330,380p' "$f"
done

printf '\nMSSQL relevant user/group columns:\n'
for f in "$init_files"; do
  if [[ "$f" != *mssql* ]]; then continue; fi
  sed -n '320,350p;385,410p' "$f"
done

printf '\nMigration script relevant user_uuid columns:\n'
for f in $(fd -t f 'add_sso_group_mapping_tables_(.*).sql' icp_server/resources/db/migration-scripts | sort); do
  echo "--- $f ---"
  sed -n '1,90p' "$f"
done

Repository: wso2/integration-control-plane

Length of output: 21695


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locating init files:"
git ls-files 'icp_server/resources/db/migration-scripts/*init.sql' 'icp_server/resources/db/migration-scripts/*-init.sql'

echo
echo "Search matching files by exact name:"
fd -t f -p 'icp_server/resources/db/migration-scripts/.*init.sql$|icp_server/resources/db/migration-scripts/.*-init.sql$' || true

echo
echo "All relevant user/group mapping occurrences in migration scripts:"
rg -n 'CREATE TABLE (group_user_mapping|federated_group_user_mapping|users)|user_id|user_uuid|group_user_mapping\.|federated_group_user_mapping\.|v_effective_group_user_mapping' icp_server/resources/db/migration-scripts || true

Repository: wso2/integration-control-plane

Length of output: 15223


Align user_uuid with the FK target in H2.

users.user_id is CHAR(36) and the MS SQL migration uses CHAR(36) for federated_group_user_mapping.user_uuid, while this H2 script uses VARCHAR(36). Change the H2 federated_group_user_mapping.user_uuid column to CHAR(36) to keep FK/type behavior consistent across dialects.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_h2.sql`
around lines 33 - 48, Change the user_uuid column definition in
federated_group_user_mapping from VARCHAR(36) to CHAR(36), keeping it NOT NULL
and preserving the existing foreign key to users(user_id) and all other
constraints.

Comment on lines +5 to +78
CREATE TABLE sso_group_mappings (
mapping_id VARCHAR(36) PRIMARY KEY,
org_uuid INT NOT NULL DEFAULT 1,
issuer VARCHAR(255) NOT NULL,
claim_name VARCHAR(128) NOT NULL,
claim_value VARCHAR(255) NOT NULL,
group_id VARCHAR(36) NOT NULL,
project_uuid CHAR(36) NULL,
integration_uuid CHAR(36) NULL,
created_at DATETIME2 DEFAULT GETDATE (),
updated_at DATETIME2 DEFAULT GETDATE (),
CONSTRAINT fk_sso_group_mapping_org FOREIGN KEY (org_uuid) REFERENCES organizations (org_id) ON DELETE NO ACTION,
CONSTRAINT fk_sso_group_mapping_group FOREIGN KEY (group_id) REFERENCES user_groups (group_id) ON DELETE CASCADE,
CONSTRAINT fk_sso_group_mapping_project FOREIGN KEY (project_uuid) REFERENCES projects (project_id) ON DELETE NO ACTION,
CONSTRAINT fk_sso_group_mapping_integration FOREIGN KEY (integration_uuid) REFERENCES components (component_id) ON DELETE NO ACTION,
CONSTRAINT chk_sso_mapping_integration_requires_project CHECK (
integration_uuid IS NULL
OR project_uuid IS NOT NULL
),
CONSTRAINT unique_sso_group_mapping UNIQUE (org_uuid, issuer, claim_name, claim_value, group_id),
INDEX idx_sso_group_mapping_org (org_uuid),
INDEX idx_sso_group_mapping_issuer_claim (issuer, claim_name, claim_value),
INDEX idx_sso_group_mapping_group (group_id),
INDEX idx_sso_group_mapping_project (project_uuid),
INDEX idx_sso_group_mapping_integration (integration_uuid)
);
GO

CREATE TRIGGER trg_sso_group_mappings_updated_at
ON sso_group_mappings
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE sso_group_mappings
SET updated_at = GETDATE()
FROM sso_group_mappings sgm
INNER JOIN inserted i ON sgm.mapping_id = i.mapping_id;
END;
GO

CREATE TABLE federated_group_user_mapping (
id BIGINT IDENTITY (1, 1) PRIMARY KEY,
org_uuid INT NOT NULL DEFAULT 1,
issuer VARCHAR(255) NOT NULL,
user_uuid CHAR(36) NOT NULL,
group_id VARCHAR(36) NOT NULL,
claim_name VARCHAR(128) NOT NULL,
claim_value VARCHAR(255) NOT NULL,
last_seen_at DATETIME2 DEFAULT GETDATE (),
created_at DATETIME2 DEFAULT GETDATE (),
updated_at DATETIME2 DEFAULT GETDATE (),
CONSTRAINT fk_fed_group_user_org FOREIGN KEY (org_uuid) REFERENCES organizations (org_id) ON DELETE NO ACTION,
CONSTRAINT fk_fed_group_user_user FOREIGN KEY (user_uuid) REFERENCES users (user_id) ON DELETE CASCADE,
CONSTRAINT fk_fed_group_user_group FOREIGN KEY (group_id) REFERENCES user_groups (group_id) ON DELETE CASCADE,
CONSTRAINT unique_fed_group_user_claim UNIQUE (org_uuid, issuer, user_uuid, group_id, claim_name, claim_value),
INDEX idx_fed_group_user_user (user_uuid),
INDEX idx_fed_group_user_group (group_id),
INDEX idx_fed_group_user_issuer_claim (issuer, claim_name, claim_value)
);
GO

CREATE TRIGGER trg_federated_group_user_mapping_updated_at
ON federated_group_user_mapping
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE federated_group_user_mapping
SET updated_at = GETDATE()
FROM federated_group_user_mapping fgum
INNER JOIN inserted i ON fgum.id = i.id;
END;
GO

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

SQL Server SSO upgrade script is not idempotent, contradicting the documented behaviour. The tables and triggers are created unconditionally, while the documentation states these upgrade scripts are safe to re-run after a partial failure.

  • icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql#L5-L78: wrap both CREATE TABLE statements and both CREATE TRIGGER statements in IF OBJECT_ID(...) IS NULL guards, matching the H2 variant.
  • icp_server/resources/db/migration-scripts/README.md#L203-L204: once the guards are added no doc change is needed; otherwise qualify the idempotency statement for SQL Server.
📍 Affects 2 files
  • icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql#L5-L78 (this comment)
  • icp_server/resources/db/migration-scripts/README.md#L203-L204
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql`
around lines 5 - 78, The SQL Server SSO migration is not idempotent because both
tables and triggers are created unconditionally. In
icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql:5-78,
wrap sso_group_mappings, federated_group_user_mapping,
trg_sso_group_mappings_updated_at, and
trg_federated_group_user_mapping_updated_at creation in IF OBJECT_ID(...) IS
NULL guards, matching the H2 variant. No direct change is needed in
icp_server/resources/db/migration-scripts/README.md:203-204 because the guards
preserve its idempotency statement.

Comment on lines +203 to +204
The scripts are **idempotent** — safe to re-run, including after a partial failure. No data
backfill is involved: federated memberships are recorded as users log in through the IdP.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Idempotency claim does not hold for all engines.

add_sso_group_mapping_tables_mssql.sql creates the two tables and their triggers without existence guards, so a re-run fails on the first statement. Either add the guards there or qualify this statement. See the related comment on that script.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/resources/db/migration-scripts/README.md` around lines 203 - 204,
Correct the idempotency statement in the migration README to exclude engines
where add_sso_group_mapping_tables_mssql.sql lacks existence guards, or update
that script’s table and trigger creation to guard against existing objects.
Ensure rerunning the migration after partial failure succeeds for the supported
engines.

Comment on lines +221 to +229
check storage:removeRoleFromGroup(scopedRoleMappingId);
check storage:removeRoleFromGroup(roleMappingId);
check storage:removePermissionsFromRole(scopedRoleId, [projectViewPermission.permissionId]);
check storage:removePermissionsFromRole(roleId, [userManageGroupsPermission.permissionId]);
check storage:deleteRoleV2(scopedRoleId);
check storage:deleteRoleV2(roleId);
check storage:deleteProject(projectId);
check storage:deleteUserV2(userId, "test-cleanup-user");
check storage:deleteGroup(groupId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Test teardown is unreachable on assertion failure in all three tests. Each test performs cleanup as a trailing sequence of check calls in the function body, so any failing test:assert* short-circuits before teardown and leaves groups, roles, projects, users, and federated rows behind. Later tests in this module read org-wide state, and repeated local runs accumulate orphaned data. Move teardown into a construct that always runs — an on fail block or an @test:AfterEach/@test:AfterGroups hook.

  • icp_server/tests/sso_federated_mapping_tests.bal#L221-L229: move the role-mapping, role, project, user, and group deletions in testSSOGroupMappingStorage into a guaranteed-teardown construct.
  • icp_server/tests/sso_federated_mapping_tests.bal#L297-L298: move the user and group deletions in testFederatedMembershipReconciliation into the same construct.
  • icp_server/tests/sso_federated_mapping_tests.bal#L361-L362: move the user and group deletions in testDeletedMappingRemovesMembershipOnNextLogin into the same construct.
📍 Affects 1 file
  • icp_server/tests/sso_federated_mapping_tests.bal#L221-L229 (this comment)
  • icp_server/tests/sso_federated_mapping_tests.bal#L297-L298
  • icp_server/tests/sso_federated_mapping_tests.bal#L361-L362
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/tests/sso_federated_mapping_tests.bal` around lines 221 - 229,
Ensure teardown always runs after failures in testSSOGroupMappingStorage,
testFederatedMembershipReconciliation, and
testDeletedMappingRemovesMembershipOnNextLogin by moving their cleanup calls
into an on fail block or shared `@test`:AfterEach/@test:AfterGroups hook. In
icp_server/tests/sso_federated_mapping_tests.bal#L221-L229, include
role-mapping, role, project, user, and group deletions; at `#L297-L298` and
`#L361-L362`, include the corresponding user and group deletions.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 20

🧹 Nitpick comments (14)
icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mysql.sql (1)

5-48: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider idempotent creates for consistency with the H2 migration script.

add_sso_group_mapping_tables_h2.sql uses CREATE TABLE IF NOT EXISTS / CREATE INDEX IF NOT EXISTS, while this script uses unguarded CREATE TABLE. A re-run or partial-failure retry aborts here. MySQL supports IF NOT EXISTS on CREATE TABLE, so the two migration scripts can behave the same way.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mysql.sql`
around lines 5 - 48, Make the MySQL migration idempotent by adding IF NOT EXISTS
to both CREATE TABLE statements, sso_group_mappings and
federated_group_user_mapping, matching the H2 migration behavior and allowing
safe reruns after partial failures.
frontend/src/components/LoginForm.tsx (1)

153-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use theme tokens instead of hardcoded colors.

#1e1e1e, #333, and #ccc are hardcoded in the sx props of both buttons. As per coding guidelines, "Use theme tokens with the sx prop instead of hardcoded colors and spacing values".

♻️ Proposed change
-              sx={{ mt: 1, bgcolor: '`#1e1e1e`', '&:hover': { bgcolor: '`#333`' }, textTransform: 'none', py: 1.2 }}
+              sx={{ mt: 1, bgcolor: 'common.black', '&:hover': { bgcolor: 'grey.800' }, textTransform: 'none', py: 1.2 }}
-              sx={{ textTransform: 'none', py: 1.2, borderColor: '`#ccc`', color: 'text.primary' }}
+              sx={{ textTransform: 'none', py: 1.2, borderColor: 'divider', color: 'text.primary' }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/LoginForm.tsx` around lines 153 - 176, Update the
`sx` props on the password and SSO buttons in `LoginForm` to replace hardcoded
colors `#1e1e1e`, `#333`, and `#ccc` with appropriate MUI theme palette tokens,
preserving the existing button appearance and hover behavior.

Source: Coding guidelines

frontend/src/pages/AccessControl.tsx (2)

33-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fragile slice(0, N) coupling to array order.

ORG_TABS.slice(0, 3) and PROJECT_TABS.slice(0, 2) assume 'sso-mappings' stays the last element of each tuple; reordering either constant later would silently drop the wrong tab instead of failing to compile.

♻️ Filter by name instead of position
-  const orgTabs: readonly string[] = window.API_CONFIG.ssoEnabled ? ORG_TABS : ORG_TABS.slice(0, 3);
+  const orgTabs: readonly string[] = window.API_CONFIG.ssoEnabled ? ORG_TABS : ORG_TABS.filter((t) => t !== 'sso-mappings');

Apply the equivalent change for PROJECT_TABS.slice(0, 2).

Also applies to: 54-54, 106-106, 167-168, 215-216

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/AccessControl.tsx` around lines 33 - 34, Replace the
positional ORG_TABS.slice(0, 3) and PROJECT_TABS.slice(0, 2) usage with
name-based filtering that excludes 'sso-mappings'. Apply this consistently at
all referenced usages while preserving the existing tab ordering and behavior
for the remaining tabs.

71-71: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inconsistent scrollable-tabs behavior across scopes.

Org-level tabs (lines 71, 123) use variant="scrollable" scrollButtons="auto", but project/component-level tabs (lines 184, 244) don't, despite both now carrying an extra SSO tab that can overflow on narrow viewports.

Also applies to: 123-123, 184-184, 244-244

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/AccessControl.tsx` at line 71, Make the tab configuration
consistent across scopes by adding variant="scrollable" and scrollButtons="auto"
to the project- and component-level Tabs instances near the existing org-level
configurations. Update the Tabs elements at all four referenced locations so the
extra SSO tab remains accessible on narrow viewports while preserving their
current navigation and value behavior.
frontend/src/pages/access-control/SSOMappingsTab.tsx (2)

71-76: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Editable Issuer field risks silent mapping failures.

The Issuer field defaults from window.API_CONFIG.ssoIssuer but remains freely editable; since mapping matching in oidc.bal requires an exact string match against the token's iss claim, a typo here causes the mapping to silently never apply, with no immediate feedback. Please confirm whether multiple issuers are actually supported; if not, consider making this field read-only.

Also applies to: 107-107

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/access-control/SSOMappingsTab.tsx` around lines 71 - 76,
Confirm whether SSO mappings support multiple issuers; if they do not, update
the issuer input in SSOMappingsTab to be read-only while retaining the
window.API_CONFIG.ssoIssuer default and submitted value. If multiple issuers are
supported, preserve editability and add appropriate validation against accepted
issuer values before submission.

178-273: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

No search or pagination, unlike sibling list tabs.

UsersTab, GroupsTab, and RolesTab all provide search plus pagination for their lists; this table renders the full mapping list unbounded, which is inconsistent and may not scale for orgs with many mappings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/access-control/SSOMappingsTab.tsx` around lines 178 - 273,
Update the SSOMappingsTab mapping list flow around the mappings rendering to add
search and pagination, matching the established behavior and controls used by
UsersTab, GroupsTab, and RolesTab. Filter mappings by the relevant claim,
issuer, or group fields, paginate the filtered results, and render the paginated
collection in both mobile cards and the desktop ListingTable while preserving
empty-state and delete-action behavior.
icp_server/modules/storage/user_repository.bal (1)

154-194: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reduce redundant EXISTS subqueries and duplicate invocation cost.

The new query evaluates the same manual/federated membership check twice per group row (once in the CASE, once in WHERE) via correlated EXISTS. A LEFT JOIN computes both flags once and reuses them. This also compounds with the existing pattern elsewhere in this file where getGroupsForUser is called twice per user (once for groups, once for groupCount, e.g. in getAllUsersV2), so the added subquery cost is paid twice per user in that list endpoint.

⚡ Suggested query rewrite using LEFT JOIN
-    stream<record {|string group_id; string group_name; string description?; string membership_source;|}, sql:Error?> groupStream = dbClient->query(
-        `SELECT g.group_id, g.group_name, g.description,
-                CASE
-                    WHEN EXISTS (
-                        SELECT 1 FROM group_user_mapping gum
-                        WHERE gum.user_uuid = ${userId} AND gum.group_id = g.group_id
-                    ) AND EXISTS (
-                        SELECT 1 FROM federated_group_user_mapping fgm
-                        WHERE fgm.user_uuid = ${userId} AND fgm.group_id = g.group_id
-                    ) THEN 'manual_and_federated'
-                    WHEN EXISTS (
-                        SELECT 1 FROM federated_group_user_mapping fgm
-                        WHERE fgm.user_uuid = ${userId} AND fgm.group_id = g.group_id
-                    ) THEN 'federated'
-                    ELSE 'manual'
-                END AS membership_source
-         FROM user_groups g
-         WHERE EXISTS (
-             SELECT 1 FROM group_user_mapping gum
-             WHERE gum.user_uuid = ${userId} AND gum.group_id = g.group_id
-         ) OR EXISTS (
-             SELECT 1 FROM federated_group_user_mapping fgm
-             WHERE fgm.user_uuid = ${userId} AND fgm.group_id = g.group_id
-         )
-         ORDER BY g.group_name ASC`
-    );
+    stream<record {|string group_id; string group_name; string description?; string membership_source;|}, sql:Error?> groupStream = dbClient->query(
+        `SELECT g.group_id, g.group_name, g.description,
+                CASE
+                    WHEN gum.user_uuid IS NOT NULL AND fgm.user_uuid IS NOT NULL THEN 'manual_and_federated'
+                    WHEN fgm.user_uuid IS NOT NULL THEN 'federated'
+                    ELSE 'manual'
+                END AS membership_source
+         FROM user_groups g
+         LEFT JOIN group_user_mapping gum ON gum.group_id = g.group_id AND gum.user_uuid = ${userId}
+         LEFT JOIN federated_group_user_mapping fgm ON fgm.group_id = g.group_id AND fgm.user_uuid = ${userId}
+         WHERE gum.user_uuid IS NOT NULL OR fgm.user_uuid IS NOT NULL
+         ORDER BY g.group_name ASC`
+    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/modules/storage/user_repository.bal` around lines 154 - 194,
Update getGroupsForUser so each group’s manual and federated membership status
is computed once through LEFT JOIN-derived flags, then reuse those flags for
both filtering and membership_source classification instead of repeating
correlated EXISTS subqueries. Preserve the current group results, ordering, and
membership_source values.
icp_server/tests/sso_config_validation_tests.bal (1)

75-83: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider also covering an empty adminValues list.

testSSOOnlyWithoutAdminClaimIsRejected covers an empty adminClaim, but an SSO-only deployment configured with adminClaim set and adminValues = [] would leave no path to an administrator. A parallel test would pin down whether validation rejects that combination.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/tests/sso_config_validation_tests.bal` around lines 75 - 83, Add a
parallel validation test alongside testSSOOnlyWithoutAdminClaimIsRejected that
builds an SSO-only configuration with a valid adminClaim and an empty
adminValues list, then asserts validateSSOConfig returns an error. Keep the test
focused on ensuring SSO-only deployments cannot be configured without any
administrator values.
icp_server/tests/auth_tests_v2.bal (1)

253-292: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider adding an authorization test for the new SSO mapping endpoints.

The new suite covers happy path, validation, immutability, and deletion, but not access control. Since these endpoints govern group membership derived from IdP claims, a case asserting that a caller without the group-management permission is rejected would protect the endpoint contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/tests/auth_tests_v2.bal` around lines 253 - 292, Add an
authorization test alongside testCreateSSOGroupMapping covering the SSO
group-mapping endpoints, using a caller that lacks the group-management
permission and asserting the request is rejected with the expected unauthorized
status. Reuse the existing authV2Client, endpoint path, and test authentication
setup, and ensure the test validates access control rather than mapping
behavior.
icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_postgresql.sql (1)

5-56: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Migration is not re-runnable, unlike the Oracle sibling.

icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_oracle.sql documents itself as idempotent and guards every object creation. This script errors on a second run or after a partially applied migration. PostgreSQL supports IF NOT EXISTS for tables and indexes, and CREATE OR REPLACE TRIGGER (PG 14+); a DROP TRIGGER IF EXISTS before creation works on older versions.

♻️ Suggested guards
-CREATE TABLE sso_group_mappings (
+CREATE TABLE IF NOT EXISTS sso_group_mappings (
@@
-CREATE INDEX idx_sgm_org_uuid ON sso_group_mappings(org_uuid);
-CREATE INDEX idx_sgm_issuer_claim ON sso_group_mappings(issuer, claim_name, claim_value);
-CREATE INDEX idx_sgm_group_id ON sso_group_mappings(group_id);
-CREATE INDEX idx_sgm_project_uuid ON sso_group_mappings(project_uuid);
-CREATE INDEX idx_sgm_integration_uuid ON sso_group_mappings(integration_uuid);
+CREATE INDEX IF NOT EXISTS idx_sgm_org_uuid ON sso_group_mappings(org_uuid);
+CREATE INDEX IF NOT EXISTS idx_sgm_issuer_claim ON sso_group_mappings(issuer, claim_name, claim_value);
+CREATE INDEX IF NOT EXISTS idx_sgm_group_id ON sso_group_mappings(group_id);
+CREATE INDEX IF NOT EXISTS idx_sgm_project_uuid ON sso_group_mappings(project_uuid);
+CREATE INDEX IF NOT EXISTS idx_sgm_integration_uuid ON sso_group_mappings(integration_uuid);
 
+DROP TRIGGER IF EXISTS update_sso_group_mappings_updated_at ON sso_group_mappings;
 CREATE TRIGGER update_sso_group_mappings_updated_at BEFORE UPDATE ON sso_group_mappings
     FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
 
-CREATE TABLE federated_group_user_mapping (
+CREATE TABLE IF NOT EXISTS federated_group_user_mapping (
@@
-CREATE INDEX idx_fgum_user_uuid ON federated_group_user_mapping(user_uuid);
-CREATE INDEX idx_fgum_group_id ON federated_group_user_mapping(group_id);
-CREATE INDEX idx_fgum_issuer_claim ON federated_group_user_mapping(issuer, claim_name, claim_value);
+CREATE INDEX IF NOT EXISTS idx_fgum_user_uuid ON federated_group_user_mapping(user_uuid);
+CREATE INDEX IF NOT EXISTS idx_fgum_group_id ON federated_group_user_mapping(group_id);
+CREATE INDEX IF NOT EXISTS idx_fgum_issuer_claim ON federated_group_user_mapping(issuer, claim_name, claim_value);
 
+DROP TRIGGER IF EXISTS update_federated_group_user_mapping_updated_at ON federated_group_user_mapping;
 CREATE TRIGGER update_federated_group_user_mapping_updated_at BEFORE UPDATE ON federated_group_user_mapping
     FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_postgresql.sql`
around lines 5 - 56, Make the PostgreSQL migration idempotent like the Oracle
sibling: guard both CREATE TABLE statements with IF NOT EXISTS, guard all CREATE
INDEX statements with IF NOT EXISTS, and make each update trigger safely
replaceable using CREATE OR REPLACE TRIGGER or DROP TRIGGER IF EXISTS before
creation, while preserving the existing definitions and constraints.
icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql (2)

148-148: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused variable. @seed_admin_id is set but the literal UUID is used at Lines 153 and 170; either use the variable or drop it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql` at line 148,
Remove the unused `@seed_admin_id` assignment, or replace the repeated literal
UUIDs at the migration statements around lines 153 and 170 with `@seed_admin_id`;
ensure the migration uses one consistent UUID source.

90-133: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

INSERT IGNORE downgrades all errors to warnings.

Beyond duplicate keys, it also suppresses foreign-key failures and truncation, so a partially migrated user set would be reported as success. Since Step 1a already clears conflicting non-admin rows, an explicit NOT EXISTS guard (as used in the SQL Server script) would keep genuine errors visible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql` around lines
90 - 133, The user migration INSERT in the dynamic SQL should not use INSERT
IGNORE, which suppresses non-duplicate errors. Remove INSERT IGNORE and add an
explicit NOT EXISTS guard against conflicting non-admin rows, matching the SQL
Server migration’s behavior while allowing genuine foreign-key and truncation
errors to surface.
frontend/src/pages/EditGroup.tsx (1)

322-332: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Membership-source label logic is duplicated across pages.

The same membershipSource → label mapping is repeated in EditUser.tsx and Profile.tsx. Extracting a small helper (e.g. in pages/access-control/utils) keeps the three views consistent if new source values are added later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/EditGroup.tsx` around lines 322 - 332, Extract the
repeated membershipSource-to-label mapping into a shared helper under
pages/access-control/utils, then update EditGroup, EditUser, and Profile to use
it for their membership-source labels. Preserve the existing labels for
federated, manual_and_federated, and local sources, and ensure all three views
use the same helper.
icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql (1)

9-23: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Quote dynamically composed identifiers.

Database names supplied through the configuration variables are concatenated directly into the generated statements. Using QUOTENAME(@old_db) (and the same for the two target databases) keeps identifier handling robust for names that are not plain identifiers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql` around lines 9
- 23, The dynamically composed database identifiers in the migration SQL should
be safely quoted. Update the generated statements using `@old_db`, `@new_main_db`,
and `@new_creds_db` to wrap each database name with QUOTENAME rather than
concatenating raw variable values, while preserving the existing query behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/RUNTIME_CONFIG.md`:
- Around line 24-27: Update the configuration keys documented in
RUNTIME_CONFIG.md to use VITE_PASSWORD_LOGIN_DISABLED, matching
frontend/public/config.json.example and frontend usage, and add the two SSO
configuration keys documented by the example config. Keep the documented values
and surrounding runtime configuration entries consistent with the example.

In `@frontend/src/pages/AccessControl.tsx`:
- Around line 36-45: Replace the duplicated responsive SSO label JSX in
AccessControl and OrgAccessControl with the existing SSO_TAB_LABEL constant,
matching its use in ProjectAccessControl and ComponentAccessControl.

In `@icp_server/auth_service.bal`:
- Around line 1295-1363: Constrain target-group validation in the mapping
creation flow around targetGroup so a caller authorized at project or
integration scope cannot select groups with role assignments outside that scope.
Either restrict eligible groups to the mapping’s administrative scope or require
org-level permission when the target group has broader-scope assignments, while
preserving the existing organization-membership check.
- Around line 3618-3652: Update grantSuperAdminFromSSOClaims and the surrounding
SSO reconciliation flow so claim-based Super Admin membership is managed as a
federated grant that is re-evaluated on every login, including removing the
membership when the configured admin claim no longer matches. Reuse the existing
federated reconciliation mechanism and preserve the current validation and error
handling.
- Around line 1457-1468: Update the deletion flow around
storage:deleteSSOGroupMapping to also remove matching rows from
federated_group_user_mapping using the mapping’s organization, issuer, claim,
and group identifiers. Perform this cleanup as part of the same operation, while
preserving the existing not-found response and internal-error handling.
- Around line 368-373: Gate the federated group synchronization in the flow
around `syncFederatedGroupsFromSSOClaims` with `federatedAccessControlEnabled`,
so the call and its error handling execute only when the feature is enabled.
Preserve the existing synchronization behavior and internal-server-error
response when the flag is enabled.
- Line 1757: Update the user-groups update flow around
storage:getUserManualGroups so the response distinguishes manual group IDs from
effective membership IDs: rename the current/final group ID fields to
manualGroupIds/addedGroupIds, or derive and return effective IDs including
federated memberships. Ensure federated groups are not reported as removed
solely because they are absent from the manual-group query.

In `@icp_server/Config.toml`:
- Around line 155-157: Update the commented configuration key in the SSO
settings block to use the declared passwordLoginDisabled name instead of
disablePasswordLogin, while preserving its default value and explanatory
comment.

In `@icp_server/custom_auth/OIDC_SETUP_GUIDE.md`:
- Around line 86-88: Replace disablePasswordLogin with passwordLoginDisabled in
the configuration table at icp_server/custom_auth/OIDC_SETUP_GUIDE.md lines
86-88 and in the FAQ instruction at lines 308-308, ensuring both documentation
references match the server configuration key.

In `@icp_server/modules/storage/auth_repository.bal`:
- Around line 758-782: The insert branch in the federated membership
reconciliation loop should treat duplicate-key failures as benign concurrency
outcomes. Update the INSERT execution in the flow around findFederatedMembership
to classify its SQL error with classifySqlError, suppress only the duplicate-key
result, and continue synchronization; propagate all other database errors
unchanged.
- Around line 702-716: Update the federated group membership insertion flow to
handle Oracle identity retrieval, alongside the existing integer and MSSQL
string parsing in the code returning the mapping ID. When Oracle returns a ROWID
instead of the generated ID, read back federated_group_user_mapping.id using the
inserted row’s unique mapping tuple, then log and return that integer ID;
preserve the existing failure path when no valid ID is found.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/README.md`:
- Around line 72-81: The README migration documentation should cover the DDL
performed by both scripts: creation of org_secrets, ensuring
mi_composite_app_artifacts, and adding runtimes.key_id with its foreign key.
Update the prerequisites to require CREATE and ALTER privileges in addition to
DML, and explicitly warn that existing non-admin accounts are deleted and
replaced, advising a backup before migration.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql`:
- Around line 154-166: The Step 2a credential merge must warn when no row is
updated despite the legacy admin user existing. Update the dynamic SQL execution
flow around `@sql` and sp_executesql to capture @@ROWCOUNT, then check for zero
updates alongside the presence of the legacy admin user and print a clear
warning; retain the existing success message for rows that are merged.
- Around line 271-299: Update the org_secrets DDL block to be re-runnable by
guarding CREATE TABLE and runtimes.key_id creation with existence checks
consistent with the later artifact block. Split the ALTER TABLE adding key_id
and the ALTER TABLE adding fk_runtime_key_id into separate sp_executesql
batches, with the foreign-key batch executed only after the column exists.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql`:
- Around line 318-372: Make the Step 5 DDL re-runnable by adding an existence
guard to the org_secrets CREATE TABLE statement and replacing the unconditional
runtimes ALTER TABLE additions with guarded checks for both the key_id column
and fk_runtime_key_id constraint. Preserve the existing schema definitions and
ensure each change can safely be executed after a partial migration.

In `@icp_server/resources/db/init-scripts/mssql_init.sql`:
- Around line 377-388: Update the trigger statements in
icp_server/resources/db/init-scripts/mssql_init.sql: within
trg_sso_group_mappings_updated_at, target the aliased table with UPDATE sgm, and
within the corresponding federated group mapping trigger, use UPDATE fgum. If
the same triggers exist in
icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql,
apply the identical alias-target changes there.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_h2.sql`:
- Around line 33-48: Change the user_uuid column definition in
federated_group_user_mapping from VARCHAR(36) to CHAR(36), keeping it NOT NULL
and preserving the existing foreign key to users(user_id) and all other
constraints.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql`:
- Around line 5-78: The SQL Server SSO migration is not idempotent because both
tables and triggers are created unconditionally. In
icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql:5-78,
wrap sso_group_mappings, federated_group_user_mapping,
trg_sso_group_mappings_updated_at, and
trg_federated_group_user_mapping_updated_at creation in IF OBJECT_ID(...) IS
NULL guards, matching the H2 variant. No direct change is needed in
icp_server/resources/db/migration-scripts/README.md:203-204 because the guards
preserve its idempotency statement.

In `@icp_server/resources/db/migration-scripts/README.md`:
- Around line 203-204: Correct the idempotency statement in the migration README
to exclude engines where add_sso_group_mapping_tables_mssql.sql lacks existence
guards, or update that script’s table and trigger creation to guard against
existing objects. Ensure rerunning the migration after partial failure succeeds
for the supported engines.

In `@icp_server/tests/sso_federated_mapping_tests.bal`:
- Around line 221-229: Ensure teardown always runs after failures in
testSSOGroupMappingStorage, testFederatedMembershipReconciliation, and
testDeletedMappingRemovesMembershipOnNextLogin by moving their cleanup calls
into an on fail block or shared `@test`:AfterEach/@test:AfterGroups hook. In
icp_server/tests/sso_federated_mapping_tests.bal#L221-L229, include
role-mapping, role, project, user, and group deletions; at `#L297-L298` and
`#L361-L362`, include the corresponding user and group deletions.

---

Nitpick comments:
In `@frontend/src/components/LoginForm.tsx`:
- Around line 153-176: Update the `sx` props on the password and SSO buttons in
`LoginForm` to replace hardcoded colors `#1e1e1e`, `#333`, and `#ccc` with
appropriate MUI theme palette tokens, preserving the existing button appearance
and hover behavior.

In `@frontend/src/pages/access-control/SSOMappingsTab.tsx`:
- Around line 71-76: Confirm whether SSO mappings support multiple issuers; if
they do not, update the issuer input in SSOMappingsTab to be read-only while
retaining the window.API_CONFIG.ssoIssuer default and submitted value. If
multiple issuers are supported, preserve editability and add appropriate
validation against accepted issuer values before submission.
- Around line 178-273: Update the SSOMappingsTab mapping list flow around the
mappings rendering to add search and pagination, matching the established
behavior and controls used by UsersTab, GroupsTab, and RolesTab. Filter mappings
by the relevant claim, issuer, or group fields, paginate the filtered results,
and render the paginated collection in both mobile cards and the desktop
ListingTable while preserving empty-state and delete-action behavior.

In `@frontend/src/pages/AccessControl.tsx`:
- Around line 33-34: Replace the positional ORG_TABS.slice(0, 3) and
PROJECT_TABS.slice(0, 2) usage with name-based filtering that excludes
'sso-mappings'. Apply this consistently at all referenced usages while
preserving the existing tab ordering and behavior for the remaining tabs.
- Line 71: Make the tab configuration consistent across scopes by adding
variant="scrollable" and scrollButtons="auto" to the project- and
component-level Tabs instances near the existing org-level configurations.
Update the Tabs elements at all four referenced locations so the extra SSO tab
remains accessible on narrow viewports while preserving their current navigation
and value behavior.

In `@frontend/src/pages/EditGroup.tsx`:
- Around line 322-332: Extract the repeated membershipSource-to-label mapping
into a shared helper under pages/access-control/utils, then update EditGroup,
EditUser, and Profile to use it for their membership-source labels. Preserve the
existing labels for federated, manual_and_federated, and local sources, and
ensure all three views use the same helper.

In `@icp_server/modules/storage/user_repository.bal`:
- Around line 154-194: Update getGroupsForUser so each group’s manual and
federated membership status is computed once through LEFT JOIN-derived flags,
then reuse those flags for both filtering and membership_source classification
instead of repeating correlated EXISTS subqueries. Preserve the current group
results, ordering, and membership_source values.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql`:
- Around line 9-23: The dynamically composed database identifiers in the
migration SQL should be safely quoted. Update the generated statements using
`@old_db`, `@new_main_db`, and `@new_creds_db` to wrap each database name with
QUOTENAME rather than concatenating raw variable values, while preserving the
existing query behavior.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql`:
- Line 148: Remove the unused `@seed_admin_id` assignment, or replace the repeated
literal UUIDs at the migration statements around lines 153 and 170 with
`@seed_admin_id`; ensure the migration uses one consistent UUID source.
- Around line 90-133: The user migration INSERT in the dynamic SQL should not
use INSERT IGNORE, which suppresses non-duplicate errors. Remove INSERT IGNORE
and add an explicit NOT EXISTS guard against conflicting non-admin rows,
matching the SQL Server migration’s behavior while allowing genuine foreign-key
and truncation errors to surface.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mysql.sql`:
- Around line 5-48: Make the MySQL migration idempotent by adding IF NOT EXISTS
to both CREATE TABLE statements, sso_group_mappings and
federated_group_user_mapping, matching the H2 migration behavior and allowing
safe reruns after partial failures.

In
`@icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_postgresql.sql`:
- Around line 5-56: Make the PostgreSQL migration idempotent like the Oracle
sibling: guard both CREATE TABLE statements with IF NOT EXISTS, guard all CREATE
INDEX statements with IF NOT EXISTS, and make each update trigger safely
replaceable using CREATE OR REPLACE TRIGGER or DROP TRIGGER IF EXISTS before
creation, while preserving the existing definitions and constraints.

In `@icp_server/tests/auth_tests_v2.bal`:
- Around line 253-292: Add an authorization test alongside
testCreateSSOGroupMapping covering the SSO group-mapping endpoints, using a
caller that lacks the group-management permission and asserting the request is
rejected with the expected unauthorized status. Reuse the existing authV2Client,
endpoint path, and test authentication setup, and ensure the test validates
access control rather than mapping behavior.

In `@icp_server/tests/sso_config_validation_tests.bal`:
- Around line 75-83: Add a parallel validation test alongside
testSSOOnlyWithoutAdminClaimIsRejected that builds an SSO-only configuration
with a valid adminClaim and an empty adminValues list, then asserts
validateSSOConfig returns an error. Keep the test focused on ensuring SSO-only
deployments cannot be configured without any administrator values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ae7ed87c-57df-4a1c-9166-102f8f119eab

📥 Commits

Reviewing files that changed from the base of the PR and between 87f45fa and c2a5159.

📒 Files selected for processing (47)
  • frontend/RUNTIME_CONFIG.md
  • frontend/public/config.json
  • frontend/public/config.json.example
  • frontend/src/api/auth.ts
  • frontend/src/api/authQueries.ts
  • frontend/src/components/LoginForm.tsx
  • frontend/src/config/api.ts
  • frontend/src/pages/AccessControl.tsx
  • frontend/src/pages/CreateGroup.tsx
  • frontend/src/pages/EditGroup.tsx
  • frontend/src/pages/EditUser.tsx
  • frontend/src/pages/Profile.tsx
  • frontend/src/pages/access-control/SSOMappingsTab.tsx
  • frontend/src/pages/access-control/UsersTab.tsx
  • frontend/src/paths.ts
  • icp_server/Config.toml
  • icp_server/Dependencies.toml
  • icp_server/auth_service.bal
  • icp_server/config.bal
  • icp_server/custom_auth/OIDC_SETUP_GUIDE.md
  • icp_server/init.bal
  • icp_server/modules/auth/oidc.bal
  • icp_server/modules/storage/auth_repository.bal
  • icp_server/modules/storage/user_repository.bal
  • icp_server/modules/types/auth_types.bal
  • icp_server/modules/types/types.bal
  • icp_server/resources/db/icp-1.2.x-to-2.x.x/README.md
  • icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql
  • icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql
  • icp_server/resources/db/init-scripts/h2_init.sql
  • icp_server/resources/db/init-scripts/mssql_init.sql
  • icp_server/resources/db/init-scripts/mysql_init.sql
  • icp_server/resources/db/init-scripts/oracle_init.sql
  • icp_server/resources/db/init-scripts/postgresql_init.sql
  • icp_server/resources/db/migration-scripts/README.md
  • icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_h2.sql
  • icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mssql.sql
  • icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_mysql.sql
  • icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_oracle.sql
  • icp_server/resources/db/migration-scripts/add_sso_group_mapping_tables_postgresql.sql
  • icp_server/tests/Config.toml
  • icp_server/tests/auth_tests_v2.bal
  • icp_server/tests/mock_oidc_provider.bal
  • icp_server/tests/oidc_tests.bal
  • icp_server/tests/sso_config_validation_tests.bal
  • icp_server/tests/sso_federated_mapping_tests.bal
  • icp_server/webserver.bal
🛑 Comments failed to post (3)
icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql (2)

154-166: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Seed admin credential merge silently no-ops when the seed row is absent.

If the credentials row for 550e8400-... is missing or the seed admin was recreated with a different user_id, Step 2a updates nothing and Step 2b skips the row because the username already exists, leaving the v2 seed password in place. Consider reporting a warning when @@ROWCOUNT = 0 while the legacy admin user exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql` around lines
154 - 166, The Step 2a credential merge must warn when no row is updated despite
the legacy admin user existing. Update the dynamic SQL execution flow around
`@sql` and sp_executesql to capture @@ROWCOUNT, then check for zero updates
alongside the presence of the legacy admin user and print a clear warning;
retain the existing success message for rows that are merged.

271-299: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Compare guarded vs unguarded DDL across the migration scripts
fd -t f 'v1_to_v2_.*\.sql' -x rg -n 'CREATE TABLE|OBJECT_ID|IF NOT EXISTS|ALTER TABLE' {}

Repository: wso2/integration-control-plane

Length of output: 765


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the relevant migration sections around the mentioned objects and transaction settings.
sed -n '1,430p' icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql

# Show the transaction / XACT_ABORT behavior around the batch/exec blocks if present.
rg -n 'XACT_ABORT|BEGIN TRANSACTION|COMMIT|ROLLBACK|BEGIN|GO|EXEC sp_executesql|`@sql`' icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql

Repository: wso2/integration-control-plane

Length of output: 17845


🌐 Web query:

SQL Server T-SQL dynamic SQL add column then add foreign key same batch sp_executesql column not found

💡 Result:

In SQL Server, T-SQL batches are compiled in their entirety before execution begins [1][2]. Because of this, any reference to a column in a table must be valid at the time of compilation [1][3]. If you add a column using an ALTER TABLE statement and then attempt to reference that column in the same batch, the compiler will identify that the column does not yet exist and throw an "Invalid column name" error, even if the code would technically be valid during execution [1][4]. Deferred name resolution, which allows SQL Server to delay checking for the existence of table objects until execution, does not apply to columns [2][5][6]. Once a table is referenced, its columns must exist at the time of compilation [2][7]. To resolve this issue, you must separate the DDL statements into distinct batches [1][4]. You have two primary approaches: 1. Use separate sp_executesql calls: This is the standard solution for dynamic SQL [8][9]. Execute the ALTER TABLE command in one call, and then perform the subsequent operations (such as adding the foreign key) in a separate sp_executesql call [8][4]. EXEC sp_executesql N'ALTER TABLE YourTable ADD NewColumn INT'; EXEC sp_executesql N'ALTER TABLE YourTable ADD CONSTRAINT FK_New FOREIGN KEY (NewColumn) REFERENCES OtherTable(ID)'; 2. Use the GO batch separator: If you are writing a static script, insert the GO command between the statements [1][10]. This forces SQL Server to treat the code before and after the GO as separate batches, ensuring the first batch (containing the column addition) is fully executed and committed before the second batch is compiled [1][11]. ALTER TABLE YourTable ADD NewColumn INT; GO ALTER TABLE YourTable ADD CONSTRAINT FK_New FOREIGN KEY (NewColumn) REFERENCES OtherTable(ID); By separating the commands, the second batch is compiled only after the column has been successfully added to the table, allowing the reference to be resolved correctly [1].

Citations:


Make the org_secrets block re-runnable and split the DDL batches.

CREATE TABLE org_secrets, ALTER TABLE runtimes ADD key_id, and the fk_runtime_key_id constraint are inside one sp_executesql batch. SQL Server parses the whole batch before executing it, so adding key_id and then referencing it in the same batch can fail with an invalid column-name error even when the transaction is not yet committed. Guard the table DDL with an existence check or CREATE TABLE IF NOT EXISTS consistent with the later artifact block, and split adding runtimes.key_id into one sp_executesql call and creating its foreign key into a later sp_executesql call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mssql.sql` around lines
271 - 299, Update the org_secrets DDL block to be re-runnable by guarding CREATE
TABLE and runtimes.key_id creation with existence checks consistent with the
later artifact block. Split the ALTER TABLE adding key_id and the ALTER TABLE
adding fk_runtime_key_id into separate sp_executesql batches, with the
foreign-key batch executed only after the column exists.
icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql (1)

318-372: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Step 5 DDL is not re-runnable.

CREATE TABLE org_secrets and the runtimes ALTER lack existence guards, unlike mi_composite_app_artifacts at Line 344. Because the DDL runs after COMMIT and cannot be rolled back, a re-run after a partial failure fails on the first already-applied statement. Recommend CREATE TABLE IF NOT EXISTS for org_secrets and a guarded column/constraint add for runtimes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/resources/db/icp-1.2.x-to-2.x.x/v1_to_v2_mysql.sql` around lines
318 - 372, Make the Step 5 DDL re-runnable by adding an existence guard to the
org_secrets CREATE TABLE statement and replacing the unconditional runtimes
ALTER TABLE additions with guarded checks for both the key_id column and
fk_runtime_key_id constraint. Preserve the existing schema definitions and
ensure each change can safely be executed after a partial migration.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/src/pages/NotAuthorized.tsx (1)

72-72: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use sx for the stack spacing.

Move spacing={2} to sx so spacing consistently uses the required styling mechanism.

Proposed fix
-        <Stack direction="row" spacing={2} justifyContent="center">
+        <Stack direction="row" justifyContent="center" sx={{ gap: 2 }}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/NotAuthorized.tsx` at line 72, Update the Stack element in
NotAuthorized to remove the spacing prop and express the equivalent spacing
through its sx styling configuration, preserving the existing spacing value and
layout behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@icp_server/tests/sso_login_gate_tests.bal`:
- Around line 214-247: The test references hasGroup without a visible definition
in sso_login_gate_tests.bal. Define hasGroup within this test module or move it
to a shared helper module and import it, then keep the existing membership
assertion in testMembershipWithoutRolesIsNotAuthorization using that accessible
helper.

---

Nitpick comments:
In `@frontend/src/pages/NotAuthorized.tsx`:
- Line 72: Update the Stack element in NotAuthorized to remove the spacing prop
and express the equivalent spacing through its sx styling configuration,
preserving the existing spacing value and layout behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ee4c90b0-2cf9-4a51-8fe2-58f65b9ec2bb

📥 Commits

Reviewing files that changed from the base of the PR and between c2a5159 and 03e022d.

📒 Files selected for processing (11)
  • frontend/src/auth/AuthContext.tsx
  • frontend/src/auth/tokenManager.ts
  • frontend/src/config/api.ts
  • frontend/src/config/routes.tsx
  • frontend/src/pages/NotAuthorized.tsx
  • frontend/src/pages/OIDCCallback.tsx
  • frontend/src/pages/access-control/SSOMappingsTab.tsx
  • frontend/src/paths.ts
  • icp_server/auth_service.bal
  • icp_server/tests/mock_oidc_provider.bal
  • icp_server/tests/sso_login_gate_tests.bal
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/pages/access-control/SSOMappingsTab.tsx
  • frontend/src/config/api.ts
  • icp_server/auth_service.bal

Comment on lines +214 to +247
function testMembershipWithoutRolesIsNotAuthorization() returns error? {
string uniqueValue = uuid:createType1AsString();
string userId = uuid:createType1AsString();
string username = "sso-gate-roleless-" + uniqueValue;
string claimValue = "roleless-" + uniqueValue;

string groupId = check storage:createGroup({
groupName: "SSO Gate Roleless Group " + uniqueValue,
description: "Temporary group with no role mappings"
});
_ = check storage:createUserV2(userId, username, "SSO Gate Roleless", [], true);
string mappingId = check storage:createSSOGroupMapping({
issuer: GATE_TEST_ISSUER,
claimName: "groups",
claimValue: claimValue,
groupId: groupId
});

types:OIDCIdTokenClaims claims = buildGateClaims(userId, [claimValue]);
string[] permissions = check resolveLoginPermissions(userId, username, claims);

test:assertTrue(hasGroup(check storage:getUserGroups(userId), groupId),
"The mapping must still grant the federated membership");
test:assertEquals(permissions.length(), 0,
"A group with no role mappings must resolve to no permissions");
test:assertFalse(isLoginAuthorized(true, permissions),
"Membership of a group that grants nothing must not count as authorization");

check storage:deleteSSOGroupMapping(mappingId, storage:DEFAULT_ORG_ID);
check storage:reconcileFederatedGroupUserMappings(
storage:DEFAULT_ORG_ID, GATE_TEST_ISSUER, userId, []);
check storage:deleteUserV2(userId, "test-cleanup-user");
check storage:deleteGroup(groupId);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Locate the definition of hasGroup used by sso_login_gate_tests.bal.
rg -n 'function hasGroup' icp_server/tests

Repository: wso2/integration-control-plane

Length of output: 295


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file outline/usages =="
rg -n 'hasGroup|getUserGroups|module "icp_server/tests"|import "icp_server/tests"' icp_server/tests/sso_login_gate_tests.bal icp_server/tests/sso_federated_mapping_tests.bal

echo
echo "== import headers =="
sed -n '1,80p' icp_server/tests/sso_login_gate_tests.bal
echo "---"
sed -n '1,80p' icp_server/tests/sso_federated_mapping_tests.bal

echo
echo "== relevant helper definition =="
sed -n '370,390p' icp_server/tests/sso_federated_mapping_tests.bal

Repository: wso2/integration-control-plane

Length of output: 8000


Move hasGroup to a shared helper module or define it in this test.

icp_server/tests/sso_login_gate_tests.bal imports icp_server.auth, icp_server.storage, and icp_server.types, but the hasGroup helper is defined in a different test file (sso_federated_mapping_tests.bal), so this call is not in the visible module scope.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@icp_server/tests/sso_login_gate_tests.bal` around lines 214 - 247, The test
references hasGroup without a visible definition in sso_login_gate_tests.bal.
Define hasGroup within this test module or move it to a shared helper module and
import it, then keep the existing membership assertion in
testMembershipWithoutRolesIsNotAuthorization using that accessible helper.

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.

1 participant