Skip to content

Commit e2bf2cd

Browse files
committed
refactor extracting jwt roles for superadmin.
1 parent c729c94 commit e2bf2cd

File tree

6 files changed

+62
-59
lines changed

6 files changed

+62
-59
lines changed

src/main/java/edu/stanford/protege/webprotege/authorization/AccessManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ Set<Capability> getCapabilityClosure(@Nonnull Subject subject,
6969
*/
7070
boolean hasPermission(@Nonnull Subject subject,
7171
@Nonnull Resource resource,
72-
@Nonnull Capability capability);
72+
@Nonnull Capability capability,
73+
String jwt);
7374

7475
Collection<Subject> getSubjectsWithAccessToResource(Resource resource);
7576

src/main/java/edu/stanford/protege/webprotege/authorization/AccessManagerImpl.java

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import edu.stanford.protege.webprotege.common.ProjectId;
66
import edu.stanford.protege.webprotege.ipc.EventDispatcher;
77
import org.bson.Document;
8+
import org.keycloak.common.VerificationException;
89
import org.slf4j.Logger;
910
import org.slf4j.LoggerFactory;
1011
import org.springframework.data.mongodb.core.MongoTemplate;
@@ -49,19 +50,29 @@ public class AccessManagerImpl implements AccessManager {
4950

5051
private final EventDispatcher eventDispatcher;
5152

53+
private final TokenValidator tokenValidator;
54+
55+
private final BuiltInRoleOracle builtInRoleOracle;
56+
5257
/**
5358
* Constructs an {@link AccessManager} that is backed by MongoDb.
5459
*
5560
* @param mongoTemplate A {@link MongoTemplate} that is used to access MongoDb.
5661
*/
5762
public AccessManagerImpl(ObjectMapper objectMapper,
5863
MongoTemplate mongoTemplate,
59-
ProjectRoleDefinitionsManager projectRoleDefinitionsManager, RoleDefinitionsManager roleDefinitionsManager, EventDispatcher eventDispatcher) {
64+
ProjectRoleDefinitionsManager projectRoleDefinitionsManager,
65+
RoleDefinitionsManager roleDefinitionsManager,
66+
EventDispatcher eventDispatcher,
67+
TokenValidator tokenValidator,
68+
BuiltInRoleOracle builtInRoleOracle) {
6069
this.objectMapper = objectMapper;
6170
this.mongoTemplate = mongoTemplate;
6271
this.projectRoleDefinitionsManager = projectRoleDefinitionsManager;
6372
this.roleDefinitionsManager = roleDefinitionsManager;
6473
this.eventDispatcher = eventDispatcher;
74+
this.tokenValidator = tokenValidator;
75+
this.builtInRoleOracle = builtInRoleOracle;
6576
}
6677

6778
/**
@@ -241,12 +252,31 @@ public Set<Capability> getCapabilityClosure(@Nonnull Subject subject, @Nonnull R
241252
}
242253

243254
@Override
244-
public boolean hasPermission(@Nonnull Subject subject, @Nonnull Resource resource, @Nonnull Capability capability) {
255+
public boolean hasPermission(@Nonnull Subject subject, @Nonnull Resource resource, @Nonnull Capability capability, String jwt) {
256+
logger.info("Checking permission for subject {} and resource {} with capability: {}", subject, resource, capability);
257+
245258
lock.readLock().lock();
246259
try {
247-
return find(withUserOrAnyUserAndTarget(subject, resource))
260+
List<Capability> capabilities = new ArrayList<>(find(withUserOrAnyUserAndTarget(subject, resource))
248261
.map(d -> objectMapper.convertValue(d, RoleAssignment.class))
249-
.anyMatch(roleAssignment -> roleAssignment.getCapabilityClosure().contains(capability));
262+
.flatMap(roleAssignment -> roleAssignment.getCapabilityClosure().stream())
263+
.toList());
264+
265+
if (jwt != null && !jwt.isEmpty()) {
266+
try {
267+
List<RoleId> roleIds = tokenValidator.extractClaimsWithoutVerification(jwt).stream()
268+
.map(RoleId::new)
269+
.toList();
270+
capabilities.addAll(builtInRoleOracle.getCapabilitiesAssociatedToRoles(roleIds));
271+
} catch (VerificationException e) {
272+
logger.error("Error getting token claims", e);
273+
throw new RuntimeException(e);
274+
}
275+
}
276+
277+
278+
return capabilities.contains(capability);
279+
250280
} finally {
251281
lock.readLock().unlock();
252282
}
@@ -285,7 +315,8 @@ public Collection<Resource> getResourcesAccessibleToSubject(Subject subject, Cap
285315
lock.readLock().lock();
286316
try {
287317
var userName = toUserName(subject);
288-
var query = query(where(USER_NAME).is(userName).and(CAPABILITY_CLOSURE).is(capability.id()));
318+
logger.info("Trying to fetch resources {} and capability {}", userName, capability.id());
319+
var query = query(where(USER_NAME).is(userName).and(CAPABILITY_CLOSURE + ".id").is(capability.id()));
289320
return find(query)
290321
.map(f -> objectMapper.convertValue(f, RoleAssignment.class))
291322
.map(ra -> {
@@ -331,7 +362,7 @@ public void rebuild() {
331362
public void rebuild(ProjectId projectId) {
332363
lock.writeLock().lock();
333364
try {
334-
logger.info("Rebuilding permissions for project: {}" , projectId);
365+
logger.info("Rebuilding permissions for project: {}", projectId);
335366
var criteria = where(PROJECT_ID).is(projectId.value());
336367
var queryObject = new Query(criteria).getQueryObject();
337368
rebuildMatchingRoleAssignments(queryObject);
@@ -345,7 +376,7 @@ private void rebuildMatchingRoleAssignments(Document queryObject) {
345376
var collection = mongoTemplate.getCollection(COLLECTION_NAME);
346377
collection.find(queryObject)
347378
.forEach(roleAssignmentDoc -> {
348-
final var roleAssignmentId = roleAssignmentDoc.get("_id" );
379+
final var roleAssignmentId = roleAssignmentDoc.get("_id");
349380
var roleAssignment = objectMapper.convertValue(roleAssignmentDoc, RoleAssignment.class);
350381

351382
// For each role assignment we compute the role closure and then
@@ -373,8 +404,8 @@ private void rebuildMatchingRoleAssignments(Document queryObject) {
373404
sortedCapabilityClosure);
374405

375406
var updatedRoleAssignmentDoc = objectMapper.convertValue(updatedRoleAssignment, Document.class);
376-
updatedRoleAssignmentDoc.put("_id" , roleAssignmentId);
377-
collection.replaceOne(new Document("_id" , roleAssignmentId), updatedRoleAssignmentDoc);
407+
updatedRoleAssignmentDoc.put("_id", roleAssignmentId);
408+
collection.replaceOne(new Document("_id", roleAssignmentId), updatedRoleAssignmentDoc);
378409
});
379410
}
380411

src/main/java/edu/stanford/protege/webprotege/authorization/AuthorizationCommandsService.java

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,11 @@ public AuthorizationCommandsService(AccessManager accessManager, TokenValidator
2424
}
2525
// TODO: Update this when Alex has committed the code
2626
public GetAuthorizationStatusResponse handleAuthorizationStatusCommand(GetAuthorizationStatusRequest request, ExecutionContext executionContext) {
27-
var hasPermission = false;
28-
if(request.resource().isApplication()) {
29-
List<RoleId> roleIds;
30-
try {
31-
roleIds = tokenValidator.getTokenClaims(executionContext.jwt()).stream()
32-
.map(RoleId::new)
33-
.toList();
34-
Set<Capability> capabilities = new HashSet<>(builtInRoleOracle.getCapabilitiesAssociatedToRoles(roleIds));
35-
hasPermission = capabilities.contains(request.capability());
36-
37-
} catch (VerificationException e) {
38-
throw new RuntimeException(e);
39-
}
40-
41-
}else {
42-
hasPermission = accessManager.hasPermission(request.subject(),
27+
var hasPermission = accessManager.hasPermission(request.subject(),
4328
request.resource(),
44-
request.capability());
45-
}
29+
request.capability(),
30+
executionContext.jwt());
31+
4632
if(hasPermission) {
4733
return new GetAuthorizationStatusResponse(request.resource(),
4834
request.subject(),

src/main/java/edu/stanford/protege/webprotege/authorization/GetAuthorizationStatusHandler.java

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*/
2121
@WebProtegeHandler
2222
public class GetAuthorizationStatusHandler implements CommandHandler<GetAuthorizationStatusRequest, GetAuthorizationStatusResponse> {
23-
private final static Logger logger = LoggerFactory.getLogger(GetAuthorizedCapabilitiesHandler.class);
23+
private final static Logger logger = LoggerFactory.getLogger(GetAuthorizationStatusHandler.class);
2424

2525
private final AccessManager accessManager;
2626

@@ -47,26 +47,11 @@ public Class<GetAuthorizationStatusRequest> getRequestClass() {
4747

4848
@Override
4949
public Mono<GetAuthorizationStatusResponse> handleRequest(GetAuthorizationStatusRequest request, ExecutionContext executionContext) {
50-
var hasPermission = false;
51-
if(request.resource().isApplication()) {
52-
List<RoleId> roleIds;
53-
try {
54-
roleIds = tokenValidator.getTokenClaims(executionContext.jwt()).stream()
55-
.map(RoleId::new)
56-
.toList();
57-
Set<Capability> capabilities = new HashSet<>(builtInRoleOracle.getCapabilitiesAssociatedToRoles(roleIds));
58-
hasPermission = capabilities.contains(request.capability());
50+
var hasPermission = accessManager.hasPermission(request.subject(),
51+
request.resource(),
52+
request.capability(),
53+
executionContext.jwt());
5954

60-
} catch (VerificationException e) {
61-
logger.error("Error getting token claims", e);
62-
throw new RuntimeException(e);
63-
}
64-
65-
}else {
66-
hasPermission = accessManager.hasPermission(request.subject(),
67-
request.resource(),
68-
request.capability());
69-
}
7055
if(hasPermission) {
7156
return Mono.just(new GetAuthorizationStatusResponse(request.resource(),
7257
request.subject(),

src/main/java/edu/stanford/protege/webprotege/authorization/GetAuthorizedCapabilitiesHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public Mono<GetAuthorizedCapabilitiesResponse> handleRequest(GetAuthorizedCapabi
4848

4949
try {
5050
//extract any SUPER admin capabilities from token
51-
var roleIds = tokenValidator.getTokenClaims(executionContext.jwt()).stream()
51+
var roleIds = tokenValidator.extractClaimsWithoutVerification(executionContext.jwt()).stream()
5252
.map(RoleId::new)
5353
.toList();
5454
capabilities.addAll(new HashSet<>(builtInRoleOracle.getCapabilitiesAssociatedToRoles(roleIds)));

src/main/java/edu/stanford/protege/webprotege/authorization/TokenValidator.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,18 @@ public Map<String,PublicKey> setUpPublicKey(RestTemplate restTemplate) throws IO
4747
return publicKeys;
4848
}
4949

50-
public Set<String> getTokenClaims(String jwt) throws VerificationException {
51-
50+
/**
51+
* Extracts claims from a JWT token without verification.
52+
* Note: This method should only be used when token verification is not critical.
53+
* For security-critical operations, use getTokenClaims() instead.
54+
*
55+
* @param jwt The JWT token to extract claims from
56+
* @return Set of roles from the token's resource access
57+
* @throws VerificationException if the token is malformed
58+
*/
59+
public Set<String> extractClaimsWithoutVerification(String jwt) throws VerificationException {
5260
TokenVerifier<AccessToken> verifier = TokenVerifier.create(jwt, AccessToken.class);
53-
54-
PublicKey publicKey = publicKeys.get(verifier.getHeader().getKeyId());
55-
if(publicKey == null) {
56-
throw new VerificationException();
57-
}
58-
verifier.publicKey(publicKey);
59-
verifier.verify().withDefaultChecks();
6061
AccessToken token = verifier.getToken();
6162
return token.getResourceAccess().get("webprotege").getRoles();
62-
6363
}
6464
}

0 commit comments

Comments
 (0)