-
Notifications
You must be signed in to change notification settings - Fork 12
Add new RBAC implementation for backend APIs #217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
5383563
Add authz service layer
tharindu-nw 9c41efa
Add utils and helper functions
tharindu-nw 5a51423
Update login endpoint to return permissions list
tharindu-nw 6fa9079
Add group and role mgt endpoints
tharindu-nw b1744a3
Add permissions and group-user-mapping endpoints
tharindu-nw cdbce63
Add tests for rbac_v2 scenarios
tharindu-nw 7a0329b
Add group_role_mapping related endpoints
tharindu-nw a54685d
Update user mgt endpoints
tharindu-nw 0cdc674
Remove getAdminProjects endpoint
tharindu-nw 6aa1a76
Update access control checks in graphql apis for runtimes
tharindu-nw 5fc1256
Add tests for runtimes gql endpoints
tharindu-nw 3ce44ad
Update rbac for environment gql apis and add tests
tharindu-nw f69cca8
Update access control and add tests for project graphql endpoints
tharindu-nw 598e969
Update access control in Component endpoint and add tests
tharindu-nw 3da6e7d
Remove old deleteComponent endpoint
tharindu-nw 24aff0d
Address code warnings
tharindu-nw 9553b11
Update permission checks in artifact graphql endpoints
tharindu-nw 28eae96
Move auth utils to auth module and refactor code
tharindu-nw d5c3c4d
Refactor and remove old tests
tharindu-nw 574c7df
Update access control checks in the observability service
tharindu-nw a4568c3
Add the new db changes to the h2 db
tharindu-nw f3003fe
Update docker compose files for local server and tests
tharindu-nw cc2fe76
Remove old rbac code and optimize queries
tharindu-nw 099282d
Update token renew tests to use new tokens
tharindu-nw ca61e55
Add new rbac implementation doc
tharindu-nw 3e463d1
Recreate h2 db with init scripts
tharindu-nw 0ac6738
Remove outdated roles and user_roles tables
tharindu-nw 0474c42
Move userContext extraction to method call [review suggestion]
tharindu-nw f70e45e
Update runtime fetch access control logic [review suggestion]
tharindu-nw 72dd19b
Simplify use cases where projectId is queried for a component [review…
tharindu-nw a0ad59a
Make permission names constants [review suggestion]
tharindu-nw 1982c82
Return empty responses when access checks fail [review suggestion]
tharindu-nw b6da564
Use uniform permission checks and move user context extraction to uti…
tharindu-nw d969ae7
Update project creation eligibility call with correct permission check
tharindu-nw 9670510
Add project scope check for update/delete project endpoints and remov…
tharindu-nw 44794bc
Update the user's permission fetch query to use EXISTS
tharindu-nw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,267 @@ | ||
| // Copyright (c) 2025, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. | ||
| // | ||
| // WSO2 Inc. licenses this file to you under the Apache License, | ||
| // Version 2.0 (the "License"); you may not use this file except | ||
| // in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, | ||
| // software distributed under the License is distributed on an | ||
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| // KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations | ||
| // under the License. | ||
|
|
||
| import icp_server.storage; | ||
| import icp_server.types; | ||
|
|
||
| import ballerina/log; | ||
|
|
||
| // ============================================================================ | ||
| // RBAC V2 - Access Resolver | ||
| // ============================================================================ | ||
| // This module provides hierarchical access resolution and batch access checks | ||
| // for efficient resource filtering. | ||
| // ============================================================================ | ||
|
|
||
| // ============================================================================ | ||
| // Hierarchical Access Resolution | ||
| // ============================================================================ | ||
|
|
||
| // Resolve project access through org → project hierarchy | ||
| // Returns access info including whether access is granted and at what level | ||
| public isolated function resolveProjectAccess(string userId, string projectId) returns ProjectAccessInfo|error { | ||
| log:printDebug(string `Resolving project access for user ${userId} on project ${projectId}`); | ||
|
|
||
| boolean hasAccess = check storage:hasAccessToProject(userId, projectId); | ||
|
|
||
| if !hasAccess { | ||
| return { | ||
| hasAccess: false, | ||
| accessLevel: "none", | ||
| projectId: projectId | ||
| }; | ||
| } | ||
|
|
||
| // Check access level by querying the view | ||
| types:UserProjectAccess[] projectAccess = check storage:getUserAccessibleProjects(userId); | ||
|
|
||
| foreach types:UserProjectAccess access in projectAccess { | ||
| if access.projectUuid == projectId { | ||
| return { | ||
| hasAccess: true, | ||
| accessLevel: access.accessLevel, | ||
| projectId: projectId, | ||
| roleId: access.roleId | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| // Has access but not in view results (shouldn't happen) | ||
| return { | ||
| hasAccess: true, | ||
| accessLevel: "unknown", | ||
tharindu-nw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| projectId: projectId | ||
| }; | ||
| } | ||
|
|
||
| // Resolve integration access through org → project → integration hierarchy | ||
| // Returns access info including whether access is granted and at what level | ||
| public isolated function resolveIntegrationAccess(string userId, string integrationId) returns IntegrationAccessInfo|error { | ||
| log:printDebug(string `Resolving integration access for user ${userId} on integration ${integrationId}`); | ||
|
|
||
| boolean hasAccess = check storage:hasAccessToIntegration(userId, integrationId); | ||
|
|
||
| if !hasAccess { | ||
| return { | ||
| hasAccess: false, | ||
| accessLevel: "none", | ||
| integrationId: integrationId | ||
| }; | ||
| } | ||
|
|
||
| // Check access level by querying the view | ||
| types:UserIntegrationAccess[] integrationAccess = check storage:getUserAccessibleIntegrations(userId); | ||
|
|
||
| foreach types:UserIntegrationAccess access in integrationAccess { | ||
| if access.integrationUuid == integrationId { | ||
| return { | ||
| hasAccess: true, | ||
| accessLevel: access.accessLevel, | ||
| integrationId: integrationId, | ||
| projectId: access.projectUuid, | ||
| roleId: access.roleId, | ||
| envRestriction: access.envUuid | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| // Has access but not in view results (shouldn't happen) | ||
| return { | ||
| hasAccess: true, | ||
| accessLevel: "unknown", | ||
| integrationId: integrationId | ||
| }; | ||
| } | ||
|
|
||
| // Resolve environment access restrictions for a user | ||
| // Returns list of allowed environments, or () if all environments are accessible | ||
| public isolated function resolveEnvironmentAccess(string userId, string? projectId = (), string? integrationId = ()) returns EnvironmentAccessInfo|error { | ||
| log:printDebug(string `Resolving environment access for user ${userId}`); | ||
|
|
||
| string[]? allowedEnvs = check storage:getEnvironmentRestriction(userId, projectId, integrationId); | ||
|
|
||
| if allowedEnvs is () { | ||
| return { | ||
| hasRestriction: false, | ||
| allowedEnvironments: (), | ||
| restrictionLevel: "none" | ||
| }; | ||
| } | ||
|
|
||
| if allowedEnvs.length() == 0 { | ||
| return { | ||
| hasRestriction: true, | ||
| allowedEnvironments: [], | ||
| restrictionLevel: "blocked" | ||
| }; | ||
| } | ||
|
|
||
| return { | ||
| hasRestriction: true, | ||
| allowedEnvironments: allowedEnvs, | ||
| restrictionLevel: "filtered" | ||
| }; | ||
| } | ||
|
|
||
| // ============================================================================ | ||
| // Batch Access Checks (for performance) | ||
| // ============================================================================ | ||
|
|
||
| // Filter a list of project IDs to only those the user can access | ||
| // Returns subset of projectIds that user has access to | ||
| public isolated function filterAccessibleProjects(string userId, string[] projectIds) returns string[]|error { | ||
| log:printDebug(string `Filtering ${projectIds.length()} projects for user ${userId}`); | ||
|
|
||
| if projectIds.length() == 0 { | ||
| return []; | ||
| } | ||
|
|
||
| // Get all accessible projects for user | ||
| types:UserProjectAccess[] accessibleProjects = check storage:getUserAccessibleProjects(userId); | ||
|
|
||
| // Build a set of accessible project IDs | ||
| map<boolean> accessibleProjectMap = {}; | ||
| foreach types:UserProjectAccess access in accessibleProjects { | ||
| accessibleProjectMap[access.projectUuid] = true; | ||
| } | ||
|
|
||
| // Filter input list | ||
| string[] filteredProjects = []; | ||
| foreach string projectId in projectIds { | ||
| if accessibleProjectMap.hasKey(projectId) { | ||
| filteredProjects.push(projectId); | ||
| } | ||
| } | ||
|
|
||
| log:printDebug(string `User ${userId} has access to ${filteredProjects.length()} of ${projectIds.length()} projects`); | ||
| return filteredProjects; | ||
| } | ||
|
|
||
| // Filter a list of integration IDs to only those the user can access | ||
| // Returns subset of integrationIds that user has access to | ||
| public isolated function filterAccessibleIntegrations(string userId, string[] integrationIds, string? projectId = (), string? envId = ()) returns string[]|error { | ||
| log:printDebug(string `Filtering ${integrationIds.length()} integrations for user ${userId}`); | ||
|
|
||
| if integrationIds.length() == 0 { | ||
| return []; | ||
| } | ||
|
|
||
| // Get all accessible integrations for user | ||
| types:UserIntegrationAccess[] accessibleIntegrations = check storage:getUserAccessibleIntegrations(userId, projectId, envId); | ||
|
|
||
| // Build a set of accessible integration IDs | ||
| map<boolean> accessibleIntegrationMap = {}; | ||
| foreach types:UserIntegrationAccess access in accessibleIntegrations { | ||
| accessibleIntegrationMap[access.integrationUuid] = true; | ||
| } | ||
|
|
||
| // Filter input list | ||
| string[] filteredIntegrations = []; | ||
| foreach string integrationId in integrationIds { | ||
| if accessibleIntegrationMap.hasKey(integrationId) { | ||
| filteredIntegrations.push(integrationId); | ||
| } | ||
| } | ||
|
|
||
| log:printDebug(string `User ${userId} has access to ${filteredIntegrations.length()} of ${integrationIds.length()} integrations`); | ||
| return filteredIntegrations; | ||
| } | ||
|
|
||
| // Filter a list of runtime IDs to only those the user can access | ||
| // Returns subset of runtimeIds that user has access to | ||
| // Note: This checks both integration access AND environment restrictions | ||
| public isolated function filterAccessibleRuntimes(string userId, string[] runtimeIds) returns string[]|error { | ||
| log:printDebug(string `Filtering ${runtimeIds.length()} runtimes for user ${userId}`); | ||
|
|
||
| if runtimeIds.length() == 0 { | ||
| return []; | ||
| } | ||
|
|
||
| // Check each runtime individually (could be optimized with a batch query in the future) | ||
| string[] filteredRuntimes = []; | ||
| foreach string runtimeId in runtimeIds { | ||
| boolean hasAccess = check storage:hasAccessToRuntime(userId, runtimeId); | ||
| if hasAccess { | ||
| filteredRuntimes.push(runtimeId); | ||
| } | ||
| } | ||
|
|
||
| log:printDebug(string `User ${userId} has access to ${filteredRuntimes.length()} of ${runtimeIds.length()} runtimes`); | ||
| return filteredRuntimes; | ||
| } | ||
|
|
||
| // Get all accessible projects for a user | ||
| // Convenience function that wraps storage layer | ||
| public isolated function getAccessibleProjects(string userId) returns types:UserProjectAccess[]|error { | ||
| log:printDebug(string `Getting accessible projects for user ${userId}`); | ||
| return check storage:getUserAccessibleProjects(userId); | ||
| } | ||
|
|
||
| // Get all accessible integrations for a user | ||
| // Convenience function that wraps storage layer | ||
| public isolated function getAccessibleIntegrations(string userId, string? projectId = (), string? envId = ()) returns types:UserIntegrationAccess[]|error { | ||
| log:printDebug(string `Getting accessible integrations for user ${userId}`); | ||
| return check storage:getUserAccessibleIntegrations(userId, projectId, envId); | ||
| } | ||
|
|
||
| // ============================================================================ | ||
| // Type Definitions | ||
| // ============================================================================ | ||
|
|
||
| // Project access information with hierarchy details | ||
| public type ProjectAccessInfo record { | ||
| boolean hasAccess; | ||
| string accessLevel; // "org", "project", "integration", "none", "unknown" | ||
| string projectId; | ||
| string? roleId?; // Role ID that grants this access | ||
| }; | ||
|
|
||
| // Integration access information with hierarchy details | ||
| public type IntegrationAccessInfo record { | ||
| boolean hasAccess; | ||
| string accessLevel; // "org", "project", "integration", "none", "unknown" | ||
| string integrationId; | ||
| string? projectId?; // Parent project ID | ||
| string? roleId?; // Role ID that grants this access | ||
| string? envRestriction?; // If set, only this environment is accessible | ||
| }; | ||
|
|
||
| // Environment access restrictions | ||
| public type EnvironmentAccessInfo record { | ||
| boolean hasRestriction; | ||
| string[]? allowedEnvironments; // () = all allowed, [] = none allowed, [ids] = specific envs | ||
| string restrictionLevel; // "none", "filtered", "blocked" | ||
| }; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.