Skip to content
Merged
Show file tree
Hide file tree
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 Nov 25, 2025
9c41efa
Add utils and helper functions
tharindu-nw Nov 25, 2025
5a51423
Update login endpoint to return permissions list
tharindu-nw Nov 28, 2025
6fa9079
Add group and role mgt endpoints
tharindu-nw Nov 28, 2025
b1744a3
Add permissions and group-user-mapping endpoints
tharindu-nw Nov 28, 2025
cdbce63
Add tests for rbac_v2 scenarios
tharindu-nw Nov 28, 2025
7a0329b
Add group_role_mapping related endpoints
tharindu-nw Nov 28, 2025
a54685d
Update user mgt endpoints
tharindu-nw Nov 28, 2025
0cdc674
Remove getAdminProjects endpoint
tharindu-nw Nov 29, 2025
6aa1a76
Update access control checks in graphql apis for runtimes
tharindu-nw Nov 29, 2025
5fc1256
Add tests for runtimes gql endpoints
tharindu-nw Nov 29, 2025
3ce44ad
Update rbac for environment gql apis and add tests
tharindu-nw Nov 29, 2025
f69cca8
Update access control and add tests for project graphql endpoints
tharindu-nw Dec 1, 2025
598e969
Update access control in Component endpoint and add tests
tharindu-nw Dec 1, 2025
3da6e7d
Remove old deleteComponent endpoint
tharindu-nw Dec 1, 2025
24aff0d
Address code warnings
tharindu-nw Dec 1, 2025
9553b11
Update permission checks in artifact graphql endpoints
tharindu-nw Dec 1, 2025
28eae96
Move auth utils to auth module and refactor code
tharindu-nw Dec 1, 2025
d5c3c4d
Refactor and remove old tests
tharindu-nw Dec 1, 2025
574c7df
Update access control checks in the observability service
tharindu-nw Dec 1, 2025
a4568c3
Add the new db changes to the h2 db
tharindu-nw Dec 1, 2025
f3003fe
Update docker compose files for local server and tests
tharindu-nw Dec 2, 2025
cc2fe76
Remove old rbac code and optimize queries
tharindu-nw Dec 2, 2025
099282d
Update token renew tests to use new tokens
tharindu-nw Dec 2, 2025
ca61e55
Add new rbac implementation doc
tharindu-nw Dec 2, 2025
3e463d1
Recreate h2 db with init scripts
tharindu-nw Dec 2, 2025
0ac6738
Remove outdated roles and user_roles tables
tharindu-nw Dec 2, 2025
0474c42
Move userContext extraction to method call [review suggestion]
tharindu-nw Dec 2, 2025
f70e45e
Update runtime fetch access control logic [review suggestion]
tharindu-nw Dec 2, 2025
72dd19b
Simplify use cases where projectId is queried for a component [review…
tharindu-nw Dec 2, 2025
a0ad59a
Make permission names constants [review suggestion]
tharindu-nw Dec 2, 2025
1982c82
Return empty responses when access checks fail [review suggestion]
tharindu-nw Dec 2, 2025
b6da564
Use uniform permission checks and move user context extraction to uti…
tharindu-nw Dec 2, 2025
d969ae7
Update project creation eligibility call with correct permission check
tharindu-nw Dec 2, 2025
9670510
Add project scope check for update/delete project endpoints and remov…
tharindu-nw Dec 2, 2025
44794bc
Update the user's permission fetch query to use EXISTS
tharindu-nw Dec 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,562 changes: 1,131 additions & 431 deletions icp_server/auth_service.bal

Large diffs are not rendered by default.

Binary file modified icp_server/database/icpdb.mv.db
Binary file not shown.
2 changes: 1 addition & 1 deletion icp_server/docker-compose.local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ services:
mysql-db:
build:
context: ./resources/db
dockerfile: Dockerfile
dockerfile: test.Dockerfile
ports:
- "3307:3306"
container_name: icp-mysql-local
Expand Down
2 changes: 1 addition & 1 deletion icp_server/docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ services:
mysql-db:
build:
context: ./resources/db
dockerfile: Dockerfile
dockerfile: test.Dockerfile
ports:
- "3307:3306"
container_name: icp-mysql-test
Expand Down
1,124 changes: 551 additions & 573 deletions icp_server/graphql_api.bal

Large diffs are not rendered by default.

267 changes: 267 additions & 0 deletions icp_server/modules/auth/access_resolver.bal
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",
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"
};
Loading
Loading