Compare commits

...
Author SHA1 Message Date
EthanHealy01 b22a71de0a Scope ADMINS_AND_TEAM_LEADS default access to the owning team
matchesDefault answered 'is this user a leader of ANY team' without looking
at which team owns the resource, so once a real TeamLeadLookup is wired a
leader of one team could use (read) another team's resource shared at that
level. Thread the owning team id through canUseResource and require
leadership of THAT team; resources with no owning team (portal /
server-scoped) keep admitting any lead, which is the intended semantic
there. Dormant today: the only TeamLeadLookup is the always-false no-op.
2026-07-06 15:45:16 +01:00
4 changed files with 64 additions and 14 deletions
@@ -37,6 +37,7 @@ public class OwnershipService {
type,
String.valueOf(resource.getId()),
resource.getOwnerUserId(),
resource.getOwnerTeamId(),
resource.getDefaultAccess(),
user);
}
@@ -37,7 +37,8 @@ public class ResourceAccessService {
/** Whether the user may use the portal / processor. */
public boolean canAccessPortal(User user) {
return canUseResource(ResourceType.PORTAL, "", null, portalDefaultPolicy, user);
// Server-wide resource: no owning team, so the team-lead default admits any lead.
return canUseResource(ResourceType.PORTAL, "", null, null, portalDefaultPolicy, user);
}
/** Whether the user may use a resource, falling back to its default policy. */
@@ -45,6 +46,7 @@ public class ResourceAccessService {
ResourceType type,
String resourceId,
Long ownerUserId,
Long ownerTeamId,
DefaultAccessPolicy defaultPolicy,
User user) {
if (user == null) {
@@ -56,7 +58,7 @@ public class ResourceAccessService {
if (hasGrant(type, normalize(resourceId), user, AccessPermission.USE)) {
return true;
}
return matchesDefault(defaultPolicy, user);
return matchesDefault(defaultPolicy, ownerTeamId, user);
}
/** Whether the user may manage (edit/delete/share) a resource. No default-policy fallback. */
@@ -161,14 +163,19 @@ public class ResourceAccessService {
return held == AccessPermission.MANAGE;
}
private boolean matchesDefault(DefaultAccessPolicy policy, User user) {
private boolean matchesDefault(DefaultAccessPolicy policy, Long ownerTeamId, User user) {
if (policy == null) {
return false;
}
return switch (policy) {
case ORG_ALL -> true;
// Admins already pass above; only team leads here.
case ADMINS_AND_TEAM_LEADS -> teamLeadLookup.isAnyTeamLeader(user);
// Admins already pass above. For a team-owned resource only a lead OF THAT team
// qualifies — "any team leader" would let team A's lead use team B's resource. A
// resource with no owning team (portal / server-scoped) admits any team lead.
case ADMINS_AND_TEAM_LEADS ->
ownerTeamId == null
? teamLeadLookup.isAnyTeamLeader(user)
: teamLeadLookup.isLeaderOfTeam(user, ownerTeamId);
case EXPLICIT_ONLY -> false;
};
}
@@ -120,7 +120,8 @@ class OwnershipServiceTest {
void enabledResourceUseDelegatesToTheAcl() {
TestResource r = new TestResource(1L);
User user = user(7);
when(accessService.canUseResource(any(), any(), any(), any(), any())).thenReturn(true);
when(accessService.canUseResource(any(), any(), any(), any(), any(), any()))
.thenReturn(true);
assertThat(ownership.canUse(TYPE, r, user)).isTrue();
}
@@ -47,7 +47,7 @@ class ResourceAccessServiceTest {
void adminMayUseEvenWithExplicitOnlyAndNoGrants() {
assertThat(
service.canUseResource(
TYPE, RID, null, DefaultAccessPolicy.EXPLICIT_ONLY, admin(1)))
TYPE, RID, null, null, DefaultAccessPolicy.EXPLICIT_ONLY, admin(1)))
.isTrue();
}
@@ -55,13 +55,13 @@ class ResourceAccessServiceTest {
void ownerMayUseEvenWithExplicitOnly() {
assertThat(
service.canUseResource(
TYPE, RID, 5L, DefaultAccessPolicy.EXPLICIT_ONLY, user(5)))
TYPE, RID, 5L, null, DefaultAccessPolicy.EXPLICIT_ONLY, user(5)))
.isTrue();
}
@Test
void nullUserIsAlwaysDenied() {
assertThat(service.canUseResource(TYPE, RID, 5L, DefaultAccessPolicy.ORG_ALL, null))
assertThat(service.canUseResource(TYPE, RID, 5L, null, DefaultAccessPolicy.ORG_ALL, null))
.isFalse();
assertThat(service.canManageResource(TYPE, RID, 5L, null)).isFalse();
}
@@ -73,7 +73,7 @@ class ResourceAccessServiceTest {
stubGrants(grant(PrincipalType.USER, 5L, AccessPermission.USE));
assertThat(
service.canUseResource(
TYPE, RID, null, DefaultAccessPolicy.EXPLICIT_ONLY, user(5)))
TYPE, RID, null, null, DefaultAccessPolicy.EXPLICIT_ONLY, user(5)))
.isTrue();
}
@@ -85,6 +85,7 @@ class ResourceAccessServiceTest {
TYPE,
RID,
null,
null,
DefaultAccessPolicy.EXPLICIT_ONLY,
userInTeam(5, 7)))
.isTrue();
@@ -98,6 +99,7 @@ class ResourceAccessServiceTest {
TYPE,
RID,
null,
null,
DefaultAccessPolicy.EXPLICIT_ONLY,
userInTeam(5, 99)))
.isFalse();
@@ -108,7 +110,7 @@ class ResourceAccessServiceTest {
stubGrants(grant(PrincipalType.USER, 5L, AccessPermission.MANAGE));
assertThat(
service.canUseResource(
TYPE, RID, null, DefaultAccessPolicy.EXPLICIT_ONLY, user(5)))
TYPE, RID, null, null, DefaultAccessPolicy.EXPLICIT_ONLY, user(5)))
.isTrue();
}
@@ -129,7 +131,9 @@ class ResourceAccessServiceTest {
@Test
void orgAllDefaultAllowsAnyUser() {
stubGrants();
assertThat(service.canUseResource(TYPE, RID, null, DefaultAccessPolicy.ORG_ALL, user(5)))
assertThat(
service.canUseResource(
TYPE, RID, null, null, DefaultAccessPolicy.ORG_ALL, user(5)))
.isTrue();
}
@@ -138,7 +142,7 @@ class ResourceAccessServiceTest {
stubGrants();
assertThat(
service.canUseResource(
TYPE, RID, null, DefaultAccessPolicy.EXPLICIT_ONLY, user(5)))
TYPE, RID, null, null, DefaultAccessPolicy.EXPLICIT_ONLY, user(5)))
.isFalse();
}
@@ -149,7 +153,12 @@ class ResourceAccessServiceTest {
when(teamLeadLookup.isAnyTeamLeader(leader)).thenReturn(true);
assertThat(
service.canUseResource(
TYPE, RID, null, DefaultAccessPolicy.ADMINS_AND_TEAM_LEADS, leader))
TYPE,
RID,
null,
null,
DefaultAccessPolicy.ADMINS_AND_TEAM_LEADS,
leader))
.isTrue();
stubGrants();
@@ -158,11 +167,43 @@ class ResourceAccessServiceTest {
TYPE,
RID,
null,
null,
DefaultAccessPolicy.ADMINS_AND_TEAM_LEADS,
user(6)))
.isFalse();
}
@Test
void teamLeadDefaultOnTeamResourceAdmitsOnlyThatTeamsLead() {
// Team-owned resource: only a lead OF THE OWNING TEAM qualifies — a lead of some
// other team must not cross the boundary.
stubGrants();
User owningTeamLead = user(5);
when(teamLeadLookup.isLeaderOfTeam(owningTeamLead, 7L)).thenReturn(true);
assertThat(
service.canUseResource(
TYPE,
RID,
null,
7L,
DefaultAccessPolicy.ADMINS_AND_TEAM_LEADS,
owningTeamLead))
.isTrue();
stubGrants();
User foreignTeamLead = user(6);
when(teamLeadLookup.isLeaderOfTeam(foreignTeamLead, 7L)).thenReturn(false);
assertThat(
service.canUseResource(
TYPE,
RID,
null,
7L,
DefaultAccessPolicy.ADMINS_AND_TEAM_LEADS,
foreignTeamLead))
.isFalse();
}
// ---- portal convenience (default policy ADMINS_AND_TEAM_LEADS) ----
@Test