Skip to content

Conversation

@pavinduLakshan
Copy link
Member

@pavinduLakshan pavinduLakshan commented Jan 26, 2026

Purpose

$subject

Related Issues

Related PRs

  • N/A

Checklist

  • e2e cypress tests locally verified. (for internal contributers)
  • Manual test round performed and verified.
  • UX/UI review done on the final implementation.
  • Documentation provided. (Add links if there are any)
  • Relevant backend changes deployed and verified
  • Unit tests provided. (Add links if there are any)
  • Integration tests provided. (Add links if there are any)

Security checks

Developer Checklist (Mandatory)

  • Complete the Developer Checklist in the related product-is issue to track any behavioral change or migration impact.

Summary by CodeRabbit

  • New Features
    • Added user sharing management in the admin interface
    • New "Sharing" tab for controlling user access across organizations
    • Multiple sharing modes: unshare, share with all organizations, or share with selected organizations
    • Role assignment capabilities when sharing users
    • View organizations where users have shared access with applied roles and pagination support

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

Walkthrough

This pull request introduces a comprehensive user sharing feature that enables administrators to share users across organizations with configurable policies and role assignments. It adds new APIs, React components, data models, and an OpenAPI specification to support sharing, unsharing, and managing user access across organizations with granular role control.

Changes

Cohort / File(s) Summary
API & Data Fetching
features/admin.users.v1/api/use-get-user-share.ts, features/admin.users.v1/api/users.ts
New hook useGetUserShare fetches user's shared organizations with pagination/filtering support. Five new API functions added: shareUserWithAllOrganizations, unShareUserWithAllOrganizations, shareUserWithSelectedOrganizationsAndRoles, unshareUserWithSelectedOrganizations, editUserRolesOfExistingOrganizations. All follow standard request/error patterns.
Sharing UI Components
features/admin.users.v1/components/share-user-form.tsx, features/admin.users.v1/components/share-user-form.scss, features/admin.users.v1/components/roles-share-with-all.tsx, features/admin.users.v1/components/roles-share-with-all.scss
New ShareUserForm component handles complex sharing logic including sharing modes (UNSHARE, SHARE_ALL, SHARE_SELECTED), role management, and confirmation modals. RolesShareWithAll provides role selection UI with debounced search. Accompanying SCSS files add styling.
Component Integration
features/admin.users.v1/components/edit-user.tsx
Adds new "Sharing" tab pane to user editor rendering ShareUserForm.
Constants & Types
features/admin.users.v1/constants/user-roles.ts, features/admin.users.v1/constants/user-management-constants.ts, features/admin.users.v1/models/user.ts
New enums: ShareType, RoleShareType, ShareTypeSwitchApproach. New constants for sharing/unsharing errors. Nine new interfaces for share data structures and one UserSharingPolicy enum covering policy options.
API Specification
features/admin.users.v1/user-sharing-v2.yml
Comprehensive OpenAPI 3.0 spec defining six endpoints: POST/PATCH /users/share, POST /users/share-with-all, POST /users/unshare, POST /users/unshare-with-all, GET /users/{userId}/share. Includes security schemes, request/response schemas, and detailed field validation.
Dependencies
features/admin.users.v1/package.json
Added workspace dependencies: @wso2is/admin.console-settings.v1 and @wso2is/common.ui.shared-access.v1.
Shared Access Configuration
features/common.ui.shared-access.v1/components/selective-org-share-with-selective-roles.tsx
Made applicationId optional and added new optional userId prop to SelectiveOrgShareWithSelectiveRolesProps.

Sequence Diagram

sequenceDiagram
    participant User as Admin User
    participant UI as ShareUserForm
    participant API as Admin APIs
    participant Backend as Backend Service
    participant Org as Organization Store

    User->>UI: Select sharing mode & organizations
    UI->>UI: Validate form state
    User->>UI: Submit sharing action
    UI->>API: Call appropriate share/unshare function
    API->>API: Build request config with userId & org data
    API->>Backend: POST/PATCH /users/share endpoint
    Backend->>Backend: Process sharing policy & roles
    Backend->>Org: Update organization-user associations
    Org-->>Backend: Confirmation
    Backend-->>API: Return 202 Processing response
    API-->>UI: Resolve with response data
    UI->>UI: Dispatch success alert
    UI->>UI: Reset form state
    UI-->>User: Display confirmation message
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 wiggles nose with delight
Users now leap 'tween orgs with grace,
Sharing and roles in just the right place,
ShareUserForm works its component magic bright,
From forms to APIs, everything's just right! ✨
hops away happily 🐇

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description is partially filled but incomplete. It contains a placeholder '$subject' in the Purpose section and lacks completion of most checklist items and mandatory developer checklist. Replace the '$subject' placeholder with a meaningful description of the feature. Complete or mark all checklist items appropriately, and address the mandatory Developer Checklist requirement.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title '[WIP] Add new sharing tab to user profile' is clear and directly related to the main changes in the PR, which introduce a new sharing tab component to the user profile interface.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@features/admin.users.v1/components/edit-user.tsx`:
- Around line 281-292: Replace the hard-coded menuItem label "Sharing" with a
localized i18n key and add that key to the locale resources; specifically,
update the menuItem property (the object rendering ResourceTab.Pane and
ShareUserForm) to use the translation function (e.g.,
t('users:editUser.tab.menuItems.4')) instead of the literal string, ensuring the
component that contains this code imports/has access to the i18n hook or
translator used elsewhere in this file, and add the corresponding
"users:editUser.tab.menuItems.4" entry to your locale JSON files for all
supported languages.

In `@features/admin.users.v1/components/share-user-form.tsx`:
- Around line 131-140: ShareUserForm currently assumes onUserSharingCompleted
exists and callers invoke it unconditionally; guard this by either giving it a
safe default or checking before calling. In
features/admin.users.v1/components/share-user-form.tsx update the ShareUserForm
props handling to set onUserSharingCompleted = () => {} in the destructuring (or
wrap every invocation of onUserSharingCompleted in an if (typeof
onUserSharingCompleted === "function") check) and ensure any call sites (e.g.,
triggerUserShare / the save/submit handler inside ShareUserForm) use that
guarded reference to avoid runtime crashes when the prop is omitted.
- Around line 827-857: The unshare API call in shareAllorNoRolesWithSelectedOrgs
is not awaited and thus not included in sharedPromises; change the logic to push
the promise returned by unshareUserWithSelectedOrganizations(data) into
sharedPromises (instead of calling it standalone), and chain .then() to remove
entries from tempShareWithFutureChildOrgsMap and .catch() to set
orgSharingSuccess = false and dispatch the addAlert as currently implemented so
that the subsequent await Promise.all(sharedPromises) properly waits for unshare
completion and error handling.
- Around line 145-149: The FEATURE_DICTIONARY in UserManagementConstants is
missing the USER_SHARED_ACCESS_STATUS key, so
UserManagementConstants.FEATURE_DICTIONARY.get("USER_SHARED_ACCESS_STATUS")
returns undefined and isFeatureEnabled falls back to true; add a new entry to
FEATURE_DICTIONARY with the symbol USER_SHARED_ACCESS_STATUS (use the same
constant name) mapping to the appropriate feature flag string/value used across
the app, update UserManagementConstants to export the USER_SHARED_ACCESS_STATUS
constant and include it in FEATURE_DICTIONARY so share-user-form.tsx's lookup
returns the correct config for isFeatureEnabled.

In `@features/admin.users.v1/models/user.ts`:
- Around line 482-581: The interfaces use UserSharingPolicy correctly for policy
but the nested roleSharing.mode fields should use RoleSharingModes (from
`@wso2is/admin.console-settings.v1/models/shared-access`) instead of the
UI-specific RoleShareType or plain string; update the type of mode in
ShareUserWithAllOrganizationsDataInterface.roleSharing,
SharedOrganizationAndRolesInterface.roleSharing,
ShareUserWithSelectedOrganizationsAndRolesDataInterface (and any other interface
with roleSharing.mode) to RoleSharingModes, add the import for RoleSharingModes,
and remove/replace any references to RoleShareType so the mode matches the
OpenAPI values NONE/SELECTED.

In `@features/admin.users.v1/user-sharing-v2.yml`:
- Around line 27-29: Update the OpenAPI security definition for BasicAuth
referenced in the security section so it documents its limitations and required
protections: add a description to the BasicAuth security scheme stating that
credentials are only base64-encoded (not encrypted), that TLS/HTTPS is required
in production, and indicate the intended use-case (e.g., development only or
service-to-service with mTLS). Locate and modify the BasicAuth entry in the
securitySchemes (the BasicAuth scheme referenced under security) and add the
explanatory description text to ensure callers know to use TLS/mTLS and when
BasicAuth is appropriate.
- Around line 472-482: The schemas allow unbounded arrays: add a maxItems
constraint (e.g., maxItems: 100) to UserCriteria.userIds and likewise add
maxItems to UserUnshareSelectedRequestBody.orgIds and
UserShareSelectedRequestBody.organizations to prevent excessively large
payloads; update the YAML entries for the properties named userIds, orgIds, and
organizations respectively to include a sensible maxItems value consistent with
system capacity and validation elsewhere.
🧹 Nitpick comments (7)
features/admin.users.v1/components/roles-share-with-all.tsx (1)

113-117: Return a stable empty array for userRolesList.
This avoids undefined and better matches the declared RolesV2Interface[] type.

♻️ Suggested refactor
-    const userRolesList: RolesV2Interface[] = useMemo(() => {
-        if (originalUserRoles?.Resources?.length > 0) {
-            return originalUserRoles.Resources;
-        }
-    }, [ originalUserRoles ]);
+    const userRolesList: RolesV2Interface[] = useMemo(() => {
+        return originalUserRoles?.Resources?.length > 0
+            ? originalUserRoles.Resources
+            : [];
+    }, [ originalUserRoles ]);
features/admin.users.v1/api/users.ts (1)

521-704: Replace any payloads with the sharing data interfaces.
The models already define the payload shapes; using them here prevents accidental contract drift.

♻️ Suggested refactor (illustrative)
-import {
-    ResendCodeRequestData,
-    UserDetailsInterface,
-    UserListInterface,
-    UserSessionsInterface
-} from "../models/user";
+import {
+    ResendCodeRequestData,
+    ShareOrganizationsAndRolesPatchDataInterface,
+    ShareUserWithAllOrganizationsDataInterface,
+    ShareUserWithSelectedOrganizationsAndRolesDataInterface,
+    UnshareOrganizationsDataInterface,
+    UnshareUserWithAllOrganizationsDataInterface,
+    UserDetailsInterface,
+    UserListInterface,
+    UserSessionsInterface
+} from "../models/user";
@@
-export const shareUserWithAllOrganizations = (data: any): Promise<any> => {
+export const shareUserWithAllOrganizations = (
+    data: ShareUserWithAllOrganizationsDataInterface
+): Promise<any> => {
@@
-export const unShareUserWithAllOrganizations = (data: any): Promise<any> => {
+export const unShareUserWithAllOrganizations = (
+    data: UnshareUserWithAllOrganizationsDataInterface
+): Promise<any> => {
@@
-export const shareUserWithSelectedOrganizationsAndRoles = (data: any): Promise<any> => {
+export const shareUserWithSelectedOrganizationsAndRoles = (
+    data: ShareUserWithSelectedOrganizationsAndRolesDataInterface
+): Promise<any> => {
@@
-export const unshareUserWithSelectedOrganizations = (data: any): Promise<any> => {
+export const unshareUserWithSelectedOrganizations = (
+    data: UnshareOrganizationsDataInterface
+): Promise<any> => {
@@
-export const editUserRolesOfExistingOrganizations = (data: any): Promise<any> => {
+export const editUserRolesOfExistingOrganizations = (
+    data: ShareOrganizationsAndRolesPatchDataInterface
+): Promise<any> => {
features/admin.users.v1/user-sharing-v2.yml (5)

70-91: Missing 401 and 403 response definitions.

All mutation endpoints should document authentication and authorization failure responses. This helps API consumers implement proper error handling.

Proposed fix: Add 401 and 403 responses
         "500":
           description: Internal Server Error
           content:
             application/json:
               schema:
                 $ref: "#/components/schemas/Error"
+        "401":
+          description: Unauthorized - Authentication required
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
+        "403":
+          description: Forbidden - Insufficient permissions (requires internal_user_share scope)
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"

Apply the same pattern to all other endpoints (/users/share-with-all, /users/unshare, /users/unshare-with-all, and the GET endpoint).


391-450: Missing 404 response for non-existent user.

The GET endpoint should document a 404 response for when the specified userId does not exist. This is important for proper error handling by API consumers.

Proposed fix: Add 404 response
         "400":
           description: Bad Request
           content:
             application/json:
               schema:
                 $ref: "#/components/schemas/Error"
+        "404":
+          description: Not Found - User with the specified ID does not exist
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
         "500":
           description: Internal Server Error

510-520: Consider using enum for policy values instead of freeform strings.

The policy field documents valid values in the description, but using an enum would provide schema-level validation and better code generation for API clients.

Proposed fix: Use enum for policy
         policy:
-          type: string
-          description: |-
-            Sharing scope for this organization.
-
-            Possible values:
-            - `SELECTED_ORG_ONLY`
-            - `SELECTED_ORG_WITH_ALL_EXISTING_CHILDREN_ONLY`
-            - `SELECTED_ORG_WITH_ALL_EXISTING_AND_FUTURE_CHILDREN`
-            - `SELECTED_ORG_WITH_EXISTING_IMMEDIATE_CHILDREN_ONLY`
-            - `SELECTED_ORG_WITH_EXISTING_IMMEDIATE_AND_FUTURE_CHILDREN`
+          type: string
+          description: Sharing scope for this organization.
+          enum:
+            - SELECTED_ORG_ONLY
+            - SELECTED_ORG_WITH_ALL_EXISTING_CHILDREN_ONLY
+            - SELECTED_ORG_WITH_ALL_EXISTING_AND_FUTURE_CHILDREN
+            - SELECTED_ORG_WITH_EXISTING_IMMEDIATE_CHILDREN_ONLY
+            - SELECTED_ORG_WITH_EXISTING_IMMEDIATE_AND_FUTURE_CHILDREN

Apply the same pattern to the global policy in UserShareAllRequestBody (lines 537-544) and SharingMode (lines 700-704).


624-648: Conditional requirement for roles not enforced at schema level.

The description states roles is "Required when mode = SELECTED" but the schema doesn't enforce this. While OpenAPI 3.0 lacks native conditional required support, consider:

  1. Adding a minItems: 1 constraint to roles when defined
  2. Documenting validation behavior clearly for implementers
  3. Using oneOf with separate schemas for NONE and SELECTED modes for stricter validation
Alternative: Use discriminated union pattern
    RoleAssignment:
      oneOf:
        - type: object
          properties:
            mode:
              type: string
              enum: [NONE]
          required: [mode]
        - type: object
          properties:
            mode:
              type: string
              enum: [SELECTED]
            roles:
              type: array
              minItems: 1
              items:
                $ref: "#/components/schemas/RoleShareConfig"
          required: [mode, roles]
      discriminator:
        propertyName: mode

12-14: Use HTTPS for contact URL.

Minor: The contact URL uses http:// which should be https:// for consistency with security best practices.

   contact:
     name: WSO2
-    url: 'http://wso2.com/products/identity-server/'
+    url: 'https://wso2.com/products/identity-server/'

Comment on lines +281 to 292
},
{
menuItem: "Sharing",
render: () => (
<ResourceTab.Pane controlledSegmentation attached={ false }>
<ShareUserForm
user={ user }
readOnly={ isReadOnly }
/>
</ResourceTab.Pane>
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Localize the new "Sharing" tab label.

All other tabs use i18n keys. A hard-coded label will stay English in localized UIs. Consider using a new translation key (e.g., users:editUser.tab.menuItems.4) and adding it to the locale resources.

🔧 Suggested fix
-            {
-                menuItem: "Sharing",
+            {
+                menuItem: t("users:editUser.tab.menuItems.4"),
                 render: () => (
                     <ResourceTab.Pane controlledSegmentation attached={ false }>
                         <ShareUserForm
                             user={ user }
                             readOnly={ isReadOnly }
                         />
                     </ResourceTab.Pane>
                 )
             }
📝 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
},
{
menuItem: "Sharing",
render: () => (
<ResourceTab.Pane controlledSegmentation attached={ false }>
<ShareUserForm
user={ user }
readOnly={ isReadOnly }
/>
</ResourceTab.Pane>
)
}
},
{
menuItem: t("users:editUser.tab.menuItems.4"),
render: () => (
<ResourceTab.Pane controlledSegmentation attached={ false }>
<ShareUserForm
user={ user }
readOnly={ isReadOnly }
/>
</ResourceTab.Pane>
)
}
🤖 Prompt for AI Agents
In `@features/admin.users.v1/components/edit-user.tsx` around lines 281 - 292,
Replace the hard-coded menuItem label "Sharing" with a localized i18n key and
add that key to the locale resources; specifically, update the menuItem property
(the object rendering ResourceTab.Pane and ShareUserForm) to use the translation
function (e.g., t('users:editUser.tab.menuItems.4')) instead of the literal
string, ensuring the component that contains this code imports/has access to the
i18n hook or translator used elsewhere in this file, and add the corresponding
"users:editUser.tab.menuItems.4" entry to your locale JSON files for all
supported languages.

Comment on lines +131 to +140
const {
user,
triggerUserShare,
onUserSharingCompleted,
readOnly,
isSharingInProgress,
operationStatus,
onOperationStarted,
["data-componentid"]: componentId = "user-share-form"
} = props;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard onUserSharingCompleted to avoid a runtime crash.
ShareUserForm is rendered without this prop in EditUser, but several call sites invoke it unconditionally. This will throw when users click save.

🔧 Suggested fix
-        onUserSharingCompleted,
+        onUserSharingCompleted = () => undefined,
📝 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
const {
user,
triggerUserShare,
onUserSharingCompleted,
readOnly,
isSharingInProgress,
operationStatus,
onOperationStarted,
["data-componentid"]: componentId = "user-share-form"
} = props;
const {
user,
triggerUserShare,
onUserSharingCompleted = () => undefined,
readOnly,
isSharingInProgress,
operationStatus,
onOperationStarted,
["data-componentid"]: componentId = "user-share-form"
} = props;
🤖 Prompt for AI Agents
In `@features/admin.users.v1/components/share-user-form.tsx` around lines 131 -
140, ShareUserForm currently assumes onUserSharingCompleted exists and callers
invoke it unconditionally; guard this by either giving it a safe default or
checking before calling. In
features/admin.users.v1/components/share-user-form.tsx update the ShareUserForm
props handling to set onUserSharingCompleted = () => {} in the destructuring (or
wrap every invocation of onUserSharingCompleted in an if (typeof
onUserSharingCompleted === "function") check) and ensure any call sites (e.g.,
triggerUserShare / the save/submit handler inside ShareUserForm) use that
guarded reference to avoid runtime crashes when the prop is omitted.

Comment on lines +145 to +149
const userFeatureConfig: FeatureAccessConfigInterface = useSelector((state: AppState) =>
state?.config?.ui?.features?.users);
const isUserShareOperationStatusEnabled: boolean = isFeatureEnabled(
userFeatureConfig,
UserManagementConstants.FEATURE_DICTIONARY.get("USER_SHARED_ACCESS_STATUS"));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "USER_SHARED_ACCESS_STATUS" -g '*.{ts,tsx}'

Repository: wso2/identity-apps

Length of output: 206


🏁 Script executed:

fd user-management-constants -t f

Repository: wso2/identity-apps

Length of output: 182


🏁 Script executed:

rg -n "FEATURE_DICTIONARY" -A 20 -g '*.{ts,tsx}' features/admin.users.v1/

Repository: wso2/identity-apps

Length of output: 32452


🏁 Script executed:

cat -n features/admin.users.v1/constants/user-management-constants.ts | sed -n '75,105p'

Repository: wso2/identity-apps

Length of output: 2006


🏁 Script executed:

fd -e ts -e tsx | xargs rg -l "isFeatureEnabled" | head -5

Repository: wso2/identity-apps

Length of output: 399


🏁 Script executed:

rg -n "function isFeatureEnabled|const isFeatureEnabled|export.*isFeatureEnabled" -A 8

Repository: wso2/identity-apps

Length of output: 2557


🏁 Script executed:

cat -n modules/access-control/src/helpers/access-control.ts | sed -n '41,80p'

Repository: wso2/identity-apps

Length of output: 1591


Add USER_SHARED_ACCESS_STATUS to FEATURE_DICTIONARY in UserManagementConstants.

The key is not registered in the feature dictionary. When get("USER_SHARED_ACCESS_STATUS") is called at line 149, it returns undefined, causing isFeatureEnabled to fall through to its default return value of true (line 58 of access-control.ts). This means the async notification path will always trigger regardless of the actual feature flag configuration.

🤖 Prompt for AI Agents
In `@features/admin.users.v1/components/share-user-form.tsx` around lines 145 -
149, The FEATURE_DICTIONARY in UserManagementConstants is missing the
USER_SHARED_ACCESS_STATUS key, so
UserManagementConstants.FEATURE_DICTIONARY.get("USER_SHARED_ACCESS_STATUS")
returns undefined and isFeatureEnabled falls back to true; add a new entry to
FEATURE_DICTIONARY with the symbol USER_SHARED_ACCESS_STATUS (use the same
constant name) mapping to the appropriate feature flag string/value used across
the app, update UserManagementConstants to export the USER_SHARED_ACCESS_STATUS
constant and include it in FEATURE_DICTIONARY so share-user-form.tsx's lookup
returns the correct config for isFeatureEnabled.

Comment on lines +827 to +857
// Call unshare API for removed organizations
if (removedOrgIds.length > 0) {
const data: UnshareOrganizationsDataInterface = {
orgIds: removedOrgIds,
userId: user.id
};

unshareUserWithSelectedOrganizations(data)
.then(() => {
// Upon sucessful unsharing, Remove the orgs from tempShareWithFutureChildOrgsMap
// as they are already removed along with the org unsharing and no need of patch operation
removedOrgIds.forEach((orgId: string) => {
delete tempShareWithFutureChildOrgsMap[orgId];
});
})
.catch((error: Error) => {
orgSharingSuccess = false;

dispatch(addAlert({
description: t("users:edit.sections.sharedAccess.notifications.unshare." +
"error.description",
{ error: error.message }),
level: AlertLevels.ERROR,
message: t("users:edit.sections.sharedAccess.notifications.unshare.error.message")
}));
});
}

// Wait for sharing/unsharing to complete
await Promise.all(sharedPromises);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Await unshare operations in shareAllorNoRolesWithSelectedOrgs.
The unshare request isn’t included in Promise.all, so success notifications can fire before the unshare completes (and errors can be missed).

🔧 Suggested fix
-            unshareUserWithSelectedOrganizations(data)
-                .then(() => {
+            sharedPromises.push(
+                unshareUserWithSelectedOrganizations(data)
+                    .then(() => {
                         // Upon sucessful unsharing, Remove the orgs from tempShareWithFutureChildOrgsMap
                         // as they are already removed along with the org unsharing and no need of patch operation
                         removedOrgIds.forEach((orgId: string) => {
                             delete tempShareWithFutureChildOrgsMap[orgId];
                         });
-                })
-                .catch((error: Error) => {
+                    })
+                    .catch((error: Error) => {
                         orgSharingSuccess = false;

                         dispatch(addAlert({
                             description: t("users:edit.sections.sharedAccess.notifications.unshare." +
                                 "error.description",
                             { error: error.message }),
                             level: AlertLevels.ERROR,
                             message: t("users:edit.sections.sharedAccess.notifications.unshare.error.message")
                         }));
-                });
+                    })
+            );
📝 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
// Call unshare API for removed organizations
if (removedOrgIds.length > 0) {
const data: UnshareOrganizationsDataInterface = {
orgIds: removedOrgIds,
userId: user.id
};
unshareUserWithSelectedOrganizations(data)
.then(() => {
// Upon sucessful unsharing, Remove the orgs from tempShareWithFutureChildOrgsMap
// as they are already removed along with the org unsharing and no need of patch operation
removedOrgIds.forEach((orgId: string) => {
delete tempShareWithFutureChildOrgsMap[orgId];
});
})
.catch((error: Error) => {
orgSharingSuccess = false;
dispatch(addAlert({
description: t("users:edit.sections.sharedAccess.notifications.unshare." +
"error.description",
{ error: error.message }),
level: AlertLevels.ERROR,
message: t("users:edit.sections.sharedAccess.notifications.unshare.error.message")
}));
});
}
// Wait for sharing/unsharing to complete
await Promise.all(sharedPromises);
// Call unshare API for removed organizations
if (removedOrgIds.length > 0) {
const data: UnshareOrganizationsDataInterface = {
orgIds: removedOrgIds,
userId: user.id
};
sharedPromises.push(
unshareUserWithSelectedOrganizations(data)
.then(() => {
// Upon sucessful unsharing, Remove the orgs from tempShareWithFutureChildOrgsMap
// as they are already removed along with the org unsharing and no need of patch operation
removedOrgIds.forEach((orgId: string) => {
delete tempShareWithFutureChildOrgsMap[orgId];
});
})
.catch((error: Error) => {
orgSharingSuccess = false;
dispatch(addAlert({
description: t("users:edit.sections.sharedAccess.notifications.unshare." +
"error.description",
{ error: error.message }),
level: AlertLevels.ERROR,
message: t("users:edit.sections.sharedAccess.notifications.unshare.error.message")
}));
})
);
}
// Wait for sharing/unsharing to complete
await Promise.all(sharedPromises);
🤖 Prompt for AI Agents
In `@features/admin.users.v1/components/share-user-form.tsx` around lines 827 -
857, The unshare API call in shareAllorNoRolesWithSelectedOrgs is not awaited
and thus not included in sharedPromises; change the logic to push the promise
returned by unshareUserWithSelectedOrganizations(data) into sharedPromises
(instead of calling it standalone), and chain .then() to remove entries from
tempShareWithFutureChildOrgsMap and .catch() to set orgSharingSuccess = false
and dispatch the addAlert as currently implemented so that the subsequent await
Promise.all(sharedPromises) properly waits for unshare completion and error
handling.

Comment on lines +482 to +581

/**
* Interface for role audiences in user sharing.
*/
export interface RoleAudiencesInterface {
display: string;
type: string;
}

/**
* Interface for role sharing in user sharing.
*/
export interface RoleSharingInterface {
displayName: string;
audience: RoleAudiencesInterface;
}

/**
* Interface for sharing the user with all organizations.
*/
export interface ShareUserWithAllOrganizationsDataInterface {
userId: string;
policy: string;
roleSharing: {
mode: string;
roles?: RoleSharingInterface[];
};
}

/**
* Interface for unsharing the user with all organizations.
*/
export interface UnshareUserWithAllOrganizationsDataInterface {
userId: string;
}

/**
* Interface for shared organization and roles for users.
*/
export interface SharedOrganizationAndRolesInterface {
orgId: string;
policy: string;
roleSharing: {
mode: string;
roles: RoleSharingInterface[];
};
}

/**
* Interface for sharing the user with selected organization and roles.
*/
export interface ShareUserWithSelectedOrganizationsAndRolesDataInterface {
userId: string;
organizations: SharedOrganizationAndRolesInterface[];
}

/**
* Interface for the patch operation to share the user with selected organizations and roles.
*/
export interface ShareOrganizationsAndRolesPatchOperationInterface {
op: string;
path: string;
value: RoleSharingInterface[];
}

/**
* Interface for the patch data to share the user with selected organizations and roles.
*/
export interface ShareOrganizationsAndRolesPatchDataInterface {
userId: string;
Operations: ShareOrganizationsAndRolesPatchOperationInterface[];
}

/**
* Interface for unsharing the user from organizations.
*/
export interface UnshareOrganizationsDataInterface {
userId: string;
orgIds: string[];
}

/**
* Interface for User.
*/
export interface UserInterface extends UserBasicInterface {
id: string;
associatedRoles?: {
allowedAudience?: string;
};
}

/**
* User sharing policy enum.
*/
export enum UserSharingPolicy {
ALL_EXISTING_ORGS_ONLY = "ALL_EXISTING_ORGS_ONLY",
ALL_EXISTING_AND_FUTURE_ORGS = "ALL_EXISTING_AND_FUTURE_ORGS",
SELECTED_ORG_ONLY = "SELECTED_ORG_ONLY",
SELECTED_ORG_WITH_ALL_EXISTING_AND_FUTURE_CHILDREN = "SELECTED_ORG_WITH_ALL_EXISTING_AND_FUTURE_CHILDREN"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, locate the user.ts file and verify the current state
fd 'user\.ts' --type f | grep -E 'admin\.users\.v1.*models'

Repository: wso2/identity-apps

Length of output: 100


🏁 Script executed:

#!/bin/bash
# Search for RoleShareType definition in constants
fd 'user-roles' --type f -o -name '*constants*' | xargs grep -l 'RoleShareType' 2>/dev/null || echo "RoleShareType not found"

Repository: wso2/identity-apps

Length of output: 333


🏁 Script executed:

#!/bin/bash
# Check what's imported or defined in constants
fd '\.ts$' --type f | xargs grep -l 'RoleShareType\|RoleShare' | head -20

Repository: wso2/identity-apps

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Search for actual enum/constant definitions related to sharing modes
rg 'export\s+(enum|const)\s+.*Share|export\s+(enum|const).*Mode' --type ts | head -30

Repository: wso2/identity-apps

Length of output: 3416


🏁 Script executed:

#!/bin/bash
# Look for OpenAPI/spec files related to user sharing
fd '\.(yml|yaml|json)$' --type f | xargs grep -l 'shareWithAll\|UserSharing\|roleSharing' 2>/dev/null | head -10

Repository: wso2/identity-apps

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Read user-roles.ts to see RoleShareType definition
cat -n features/admin.users.v1/constants/user-roles.ts | head -100

Repository: wso2/identity-apps

Length of output: 1525


🏁 Script executed:

#!/bin/bash
# Check user-sharing-v2.yml for the API contract
cat -n features/admin.users.v1/user-sharing-v2.yml | head -200

Repository: wso2/identity-apps

Length of output: 8703


🏁 Script executed:

#!/bin/bash
# Search for usage of ShareUserWithAllOrganizationsDataInterface to see if strings are actually used
rg 'ShareUserWithAllOrganizationsDataInterface|policy:|roleSharing' features/admin.users.v1 -A 2 -B 2 | head -100

Repository: wso2/identity-apps

Length of output: 7532


🏁 Script executed:

#!/bin/bash
# Find RoleSharingModes definition
rg 'RoleSharingModes|enum.*RoleSha' features/admin.users.v1 -A 5 | head -50

Repository: wso2/identity-apps

Length of output: 4368


🏁 Script executed:

#!/bin/bash
# Check how mode values are actually sent in API calls
cat -n features/admin.users.v1/components/share-user-form.tsx | sed -n '1,50p;200,300p;400,500p;600,700p' | head -150

Repository: wso2/identity-apps

Length of output: 7036


🏁 Script executed:

#!/bin/bash
# Check the schema definitions in the OpenAPI spec for policy and mode enums
cat -n features/admin.users.v1/user-sharing-v2.yml | tail -500 | head -200

Repository: wso2/identity-apps

Length of output: 8343


🏁 Script executed:

#!/bin/bash
# Find RoleSharingModes definition in console-settings
fd 'shared-access' --type f | xargs grep -A 10 'enum RoleSharingModes\|export.*RoleSharingModes'

Repository: wso2/identity-apps

Length of output: 451


🏁 Script executed:

#!/bin/bash
# Check the schema definitions at the end of the OpenAPI spec for enum values
tail -300 features/admin.users.v1/user-sharing-v2.yml | grep -A 30 'properties:' | head -80

Repository: wso2/identity-apps

Length of output: 2832


🏁 Script executed:

#!/bin/bash
# Find the RoleAssignment schema definition in the OpenAPI spec
rg 'RoleAssignment:' features/admin.users.v1/user-sharing-v2.yml -A 30

Repository: wso2/identity-apps

Length of output: 1024


🏁 Script executed:

#!/bin/bash
# Check if RoleSharingModes is actually used in the data interfaces or just the component
rg 'RoleSharingModes|roleSharing.*mode' features/admin.users.v1/models/user.ts -B 2 -A 2

Repository: wso2/identity-apps

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Let me check the current interface definitions more carefully to see the mismatch
cat -n features/admin.users.v1/models/user.ts | sed -n '482,581p'

Repository: wso2/identity-apps

Length of output: 3145


🏁 Script executed:

#!/bin/bash
# Check what values are actually being used in API calls
rg 'roleSharing|policy:' features/admin.users.v1/api/users.ts -B 3 -A 3 | head -100

Repository: wso2/identity-apps

Length of output: 367


Use enums for sharing policy, but verify the correct mode enum.

Using UserSharingPolicy for the policy field is correct and aligns with the API contract. However, the mode field should use RoleSharingModes (from @wso2is/admin.console-settings.v1/models/shared-access) instead of RoleShareType, as the OpenAPI spec defines mode values as NONE and SELECTED (matching RoleSharingModes), not the camelCase values in RoleShareType which is a UI-specific enum.

♻️ Corrected typing upgrade
 import { UserRoleInterface } from "@wso2is/admin.core.v1/models/users";
 import { SCIMConfigs } from "@wso2is/admin.extensions.v1/configs/scim";
 import { GroupsInterface, GroupsMemberInterface } from "@wso2is/admin.groups.v1/models/groups";
 import { LinkInterface, MultiValueAttributeInterface, NameInterface, RolesInterface } from "@wso2is/core/models";
+import { RoleSharingModes } from "@wso2is/admin.console-settings.v1/models/shared-access";
 import React, { ReactElement } from "react";
 import { BulkImportResponseOperationTypes, BulkUserImportStatus } from "../constants";
@@
 export interface ShareUserWithAllOrganizationsDataInterface {
     userId: string;
-    policy: string;
+    policy: UserSharingPolicy;
     roleSharing: {
-        mode: string;
+        mode: RoleSharingModes;
         roles?: RoleSharingInterface[];
     };
 }
@@
 export interface SharedOrganizationAndRolesInterface {
     orgId: string;
-    policy: string;
+    policy: UserSharingPolicy;
     roleSharing: {
-        mode: string;
+        mode: RoleSharingModes;
         roles: RoleSharingInterface[];
     };
 }
🤖 Prompt for AI Agents
In `@features/admin.users.v1/models/user.ts` around lines 482 - 581, The
interfaces use UserSharingPolicy correctly for policy but the nested
roleSharing.mode fields should use RoleSharingModes (from
`@wso2is/admin.console-settings.v1/models/shared-access`) instead of the
UI-specific RoleShareType or plain string; update the type of mode in
ShareUserWithAllOrganizationsDataInterface.roleSharing,
SharedOrganizationAndRolesInterface.roleSharing,
ShareUserWithSelectedOrganizationsAndRolesDataInterface (and any other interface
with roleSharing.mode) to RoleSharingModes, add the import for RoleSharingModes,
and remove/replace any references to RoleShareType so the mode matches the
OpenAPI values NONE/SELECTED.

Comment on lines +27 to +29
security:
- OAuth2: []
- BasicAuth: []
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

BasicAuth security scheme allows cleartext credentials.

The static analysis correctly identifies that BasicAuth transmits credentials as base64-encoded (essentially cleartext). While this may be acceptable for development or when TLS is enforced at infrastructure level, consider:

  1. Documenting that HTTPS/TLS is required when using BasicAuth
  2. Adding a description to the BasicAuth scheme clarifying its intended use case (e.g., service-to-service calls with mutual TLS)
Proposed fix: Add description to BasicAuth scheme
     BasicAuth:
       type: http
       scheme: basic
+      description: |-
+        Basic authentication. **Important:** Only use over HTTPS/TLS connections.
+        Recommended for service-to-service calls or development environments only.

Also applies to: 454-456

🤖 Prompt for AI Agents
In `@features/admin.users.v1/user-sharing-v2.yml` around lines 27 - 29, Update the
OpenAPI security definition for BasicAuth referenced in the security section so
it documents its limitations and required protections: add a description to the
BasicAuth security scheme stating that credentials are only base64-encoded (not
encrypted), that TLS/HTTPS is required in production, and indicate the intended
use-case (e.g., development only or service-to-service with mTLS). Locate and
modify the BasicAuth entry in the securitySchemes (the BasicAuth scheme
referenced under security) and add the explanatory description text to ensure
callers know to use TLS/mTLS and when BasicAuth is appropriate.

Comment on lines +472 to +482
UserCriteria:
type: object
description: Criteria for selecting users to share/unshare.
properties:
userIds:
type: array
description: List of user IDs.
items:
type: string
required:
- userIds
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add maxItems constraint to prevent unbounded requests.

The userIds array lacks a maximum item limit. Unbounded arrays can lead to performance degradation or denial-of-service if clients submit excessively large payloads. Consider adding a reasonable limit based on your processing capacity.

Proposed fix: Add maxItems constraint
     UserCriteria:
       type: object
       description: Criteria for selecting users to share/unshare.
       properties:
         userIds:
           type: array
           description: List of user IDs.
+          minItems: 1
+          maxItems: 100
           items:
             type: string
       required:
         - userIds

Similarly, consider adding maxItems to orgIds in UserUnshareSelectedRequestBody (line 561-565) and organizations in UserShareSelectedRequestBody (line 494-498).

📝 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
UserCriteria:
type: object
description: Criteria for selecting users to share/unshare.
properties:
userIds:
type: array
description: List of user IDs.
items:
type: string
required:
- userIds
UserCriteria:
type: object
description: Criteria for selecting users to share/unshare.
properties:
userIds:
type: array
description: List of user IDs.
minItems: 1
maxItems: 100
items:
type: string
required:
- userIds
🧰 Tools
🪛 Checkov (3.2.334)

[medium] 477-482: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

🤖 Prompt for AI Agents
In `@features/admin.users.v1/user-sharing-v2.yml` around lines 472 - 482, The
schemas allow unbounded arrays: add a maxItems constraint (e.g., maxItems: 100)
to UserCriteria.userIds and likewise add maxItems to
UserUnshareSelectedRequestBody.orgIds and
UserShareSelectedRequestBody.organizations to prevent excessively large
payloads; update the YAML entries for the properties named userIds, orgIds, and
organizations respectively to include a sensible maxItems value consistent with
system capacity and validation elsewhere.

@codecov
Copy link

codecov bot commented Jan 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 55.88%. Comparing base (a3fcf72) to head (3e2effd).
⚠️ Report is 23 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #9568   +/-   ##
=======================================
  Coverage   55.88%   55.88%           
=======================================
  Files          42       42           
  Lines        1020     1020           
  Branches      231      247   +16     
=======================================
  Hits          570      570           
+ Misses        450      416   -34     
- Partials        0       34   +34     
Flag Coverage Δ
@wso2is/core 55.88% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.
see 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pavinduLakshan pavinduLakshan marked this pull request as draft January 27, 2026 00:41
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