Skip to content

Commit 01a91f8

Browse files
ivicacclaude
andcommitted
1051 Fail closed for unauthenticated callers in CE PermissionServiceImpl
Gate the permissive workspace/role/scope checks on SecurityUtils.isAuthenticated() so anonymous callers are denied, add an isAuthenticated() guard to hasResourceScope, correct the class Javadoc to describe the actual (fail-closed) behavior, and set up a ROLE_USER SecurityContext in the tests plus anonymous/unauthenticated denial coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 05cf3a1 commit 01a91f8

3 files changed

Lines changed: 103 additions & 14 deletions

File tree

server/libs/automation/automation-configuration/automation-configuration-service/src/main/java/com/bytechef/automation/configuration/service/PermissionServiceImpl.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,13 @@
3434
import org.springframework.stereotype.Service;
3535

3636
/**
37-
* Community Edition no-op implementation of {@link PermissionService}. RBAC (workspace roles, project scopes, custom
38-
* roles) is an Enterprise Edition feature — in CE every authenticated user is treated as having full access at the
39-
* service level, and access control is enforced solely by Spring Security's {@code ROLE_USER}/{@code ROLE_ADMIN}
40-
* authorities plus the client-side UI gating.
37+
* Community Edition implementation of {@link PermissionService}. Fine-grained RBAC (workspace roles, project scopes,
38+
* custom roles) is an Enterprise Edition feature, so in CE the workspace/role/scope checks grant access to any
39+
* authenticated (non-anonymous) caller and deny unauthenticated ones. Resource-level access
40+
* ({@link #hasResourceScope(Serializable, String, String)}) still fails closed: it denies unauthenticated callers and
41+
* unknown resource types, and enforces owner isolation for user-owned resources. Coarse-grained access control is
42+
* otherwise enforced by Spring Security's {@code ROLE_USER}/{@code ROLE_ADMIN} authorities plus the client-side UI
43+
* gating.
4144
*
4245
* @author Ivica Cardic
4346
*/
@@ -72,21 +75,25 @@ public boolean isCurrentUser(long userId) {
7275

7376
@Override
7477
public boolean hasWorkspaceRole(long workspaceId, String minimumRole) {
75-
return true;
78+
return SecurityUtils.isAuthenticated();
7679
}
7780

7881
@Override
7982
public boolean hasWorkspaceScope(long workspaceId, String scope) {
80-
return true;
83+
return SecurityUtils.isAuthenticated();
8184
}
8285

8386
@Override
8487
public boolean hasWorkspaceScopeForProject(long projectId, String scope) {
85-
return true;
88+
return SecurityUtils.isAuthenticated();
8689
}
8790

8891
@Override
8992
public boolean hasResourceScope(Serializable id, String resourceType, String scope) {
93+
if (!SecurityUtils.isAuthenticated()) {
94+
return false;
95+
}
96+
9097
if (isTenantAdmin()) {
9198
return true;
9299
}
@@ -111,17 +118,17 @@ public boolean hasResourceScope(Serializable id, String resourceType, String sco
111118

112119
@Override
113120
public boolean isResourceOwner(String resourceType, long id) {
114-
return true;
121+
return SecurityUtils.isAuthenticated();
115122
}
116123

117124
@Override
118125
public boolean hasResourceRole(long id, String resourceType, String minimumRole) {
119-
return true;
126+
return SecurityUtils.isAuthenticated();
120127
}
121128

122129
@Override
123130
public boolean hasWorkflowScope(String workflowId, String scope) {
124-
return true;
131+
return SecurityUtils.isAuthenticated();
125132
}
126133

127134
@Override

server/libs/automation/automation-configuration/automation-configuration-service/src/test/java/com/bytechef/automation/configuration/service/PermissionServiceImplResourceTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,19 @@
2121

2222
import com.bytechef.automation.configuration.security.ResourceOwnershipResolver;
2323
import com.bytechef.automation.configuration.security.ResourceOwnershipResolver.ResourceOwner;
24+
import com.bytechef.platform.security.constant.AuthorityConstants;
2425
import com.bytechef.platform.user.domain.User;
2526
import com.bytechef.platform.user.service.UserService;
2627
import java.util.List;
2728
import java.util.Optional;
29+
import org.junit.jupiter.api.AfterEach;
30+
import org.junit.jupiter.api.BeforeEach;
2831
import org.junit.jupiter.api.Test;
2932
import org.mockito.Mockito;
33+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
34+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
35+
import org.springframework.security.core.context.SecurityContext;
36+
import org.springframework.security.core.context.SecurityContextHolder;
3037

3138
/**
3239
* Pins the CE behavior of {@code hasResourceScope}: owner-isolation for owner-carrying resources (PRIVATE), permissive
@@ -39,6 +46,22 @@ class PermissionServiceImplResourceTest {
3946

4047
private final UserService userService = Mockito.mock(UserService.class);
4148

49+
@BeforeEach
50+
void setUp() {
51+
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
52+
53+
securityContext.setAuthentication(
54+
new UsernamePasswordAuthenticationToken(
55+
"user", "user", List.of(new SimpleGrantedAuthority(AuthorityConstants.USER))));
56+
57+
SecurityContextHolder.setContext(securityContext);
58+
}
59+
60+
@AfterEach
61+
void tearDown() {
62+
SecurityContextHolder.clearContext();
63+
}
64+
4265
private PermissionService permissionService(ResourceOwnershipResolver... resolvers) {
4366
return new PermissionServiceImpl(userService, List.of(resolvers));
4467
}
@@ -126,4 +149,13 @@ void testHasWorkflowScopePermissiveInCe() {
126149

127150
assertThat(service.hasWorkflowScope("wf-uuid", "WORKFLOW_EDIT")).isTrue();
128151
}
152+
153+
@Test
154+
void testHasResourceScopeDeniesUnauthenticatedCaller() {
155+
SecurityContextHolder.clearContext();
156+
157+
PermissionService service = permissionService(resolver("Connection", ResourceOwner.ofUser(7L)));
158+
159+
assertThat(service.hasResourceScope(1L, "Connection", "CONNECTION_DELETE")).isFalse();
160+
}
129161
}

server/libs/automation/automation-configuration/automation-configuration-service/src/test/java/com/bytechef/automation/configuration/service/PermissionServiceTest.java

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,40 +19,90 @@
1919
import static org.assertj.core.api.Assertions.assertThat;
2020
import static org.mockito.Mockito.mock;
2121

22+
import com.bytechef.platform.security.constant.AuthorityConstants;
2223
import com.bytechef.platform.user.service.UserService;
24+
import java.util.List;
25+
import org.junit.jupiter.api.AfterEach;
26+
import org.junit.jupiter.api.BeforeEach;
2327
import org.junit.jupiter.api.Test;
28+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
29+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
30+
import org.springframework.security.core.context.SecurityContext;
31+
import org.springframework.security.core.context.SecurityContextHolder;
2432

2533
/**
2634
* @author Ivica Cardic
2735
*/
2836
class PermissionServiceTest {
2937

3038
private final PermissionService permissionService =
31-
new PermissionServiceImpl(mock(UserService.class), java.util.List.of());
39+
new PermissionServiceImpl(mock(UserService.class), List.of());
40+
41+
@BeforeEach
42+
void setUp() {
43+
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
44+
45+
securityContext.setAuthentication(
46+
new UsernamePasswordAuthenticationToken(
47+
"user", "user", List.of(new SimpleGrantedAuthority(AuthorityConstants.USER))));
48+
49+
SecurityContextHolder.setContext(securityContext);
50+
}
51+
52+
@AfterEach
53+
void tearDown() {
54+
SecurityContextHolder.clearContext();
55+
}
3256

3357
@Test
34-
void testHasWorkspaceRoleAlwaysTrue() {
58+
void testHasWorkspaceRoleTrueForAuthenticatedUser() {
3559
assertThat(permissionService.hasWorkspaceRole(1L, "ADMIN")).isTrue();
3660
assertThat(permissionService.hasWorkspaceRole(1L, "EDITOR")).isTrue();
3761
assertThat(permissionService.hasWorkspaceRole(1L, "VIEWER")).isTrue();
3862
assertThat(permissionService.hasWorkspaceRole(99L, "ANY_UNRECOGNIZED_ROLE")).isTrue();
3963
}
4064

4165
@Test
42-
void testHasWorkspaceScopeAlwaysTrue() {
66+
void testHasWorkspaceScopeTrueForAuthenticatedUser() {
4367
assertThat(permissionService.hasWorkspaceScope(1L, "WORKFLOW_VIEW")).isTrue();
4468
assertThat(permissionService.hasWorkspaceScope(1L, "WORKFLOW_DELETE")).isTrue();
4569
assertThat(permissionService.hasWorkspaceScope(1L, "PROJECT_DELETE")).isTrue();
4670
assertThat(permissionService.hasWorkspaceScope(1L, "ANY_UNRECOGNIZED_SCOPE")).isTrue();
4771
}
4872

4973
@Test
50-
void testHasWorkspaceScopeForProjectAlwaysTrue() {
74+
void testHasWorkspaceScopeForProjectTrueForAuthenticatedUser() {
5175
assertThat(permissionService.hasWorkspaceScopeForProject(1L, "WORKFLOW_VIEW")).isTrue();
5276
assertThat(permissionService.hasWorkspaceScopeForProject(1L, "PROJECT_DELETE")).isTrue();
5377
assertThat(permissionService.hasWorkspaceScopeForProject(1L, "ANY_UNRECOGNIZED_SCOPE")).isTrue();
5478
}
5579

80+
@Test
81+
void testWorkspaceChecksDenyUnauthenticatedCaller() {
82+
SecurityContextHolder.clearContext();
83+
84+
assertThat(permissionService.hasWorkspaceRole(1L, "ADMIN")).isFalse();
85+
assertThat(permissionService.hasWorkspaceScope(1L, "WORKFLOW_VIEW")).isFalse();
86+
assertThat(permissionService.hasWorkspaceScopeForProject(1L, "WORKFLOW_VIEW")).isFalse();
87+
assertThat(permissionService.hasWorkflowScope("workflow-1", "WORKFLOW_VIEW")).isFalse();
88+
assertThat(permissionService.hasResourceRole(1L, "project", "ADMIN")).isFalse();
89+
assertThat(permissionService.isResourceOwner("project", 1L)).isFalse();
90+
}
91+
92+
@Test
93+
void testWorkspaceChecksDenyAnonymousCaller() {
94+
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
95+
96+
securityContext.setAuthentication(
97+
new UsernamePasswordAuthenticationToken(
98+
"anonymous", "anonymous", List.of(new SimpleGrantedAuthority(AuthorityConstants.ANONYMOUS))));
99+
100+
SecurityContextHolder.setContext(securityContext);
101+
102+
assertThat(permissionService.hasWorkspaceRole(1L, "ADMIN")).isFalse();
103+
assertThat(permissionService.hasWorkspaceScope(1L, "WORKFLOW_VIEW")).isFalse();
104+
}
105+
56106
@Test
57107
void testGetMyWorkspaceScopesReturnsEmpty() {
58108
// CE has no scope mapping; getMyWorkspaceScopes returns an empty set so client code that relies on the list

0 commit comments

Comments
 (0)