Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import edu.stanford.protege.webprotege.common.ProjectId;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -64,13 +65,14 @@ Set<Capability> getCapabilityClosure(@Nonnull Subject subject,
* @param subject The subject.
* @param resource The resource on which the capability should be executed.
* @param capability The required capability.
* @param jwt The Jason Web Token (JWT) from which super admin roles can be determined. This may be blank or null.
* @return {@code true} if the subject has permission to execute the specified capability on the specified resource,
* otherwise {@code false}.
*/
boolean hasPermission(@Nonnull Subject subject,
@Nonnull Resource resource,
@Nonnull Capability capability,
String jwt);
@Nullable String jwt);

Collection<Subject> getSubjectsWithAccessToResource(Resource resource);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import edu.stanford.protege.webprotege.common.ProjectId;
import edu.stanford.protege.webprotege.ipc.EventDispatcher;
import org.bson.Document;
import org.keycloak.common.VerificationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
Expand All @@ -21,7 +20,8 @@
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static edu.stanford.protege.webprotege.authorization.RoleAssignment.*;
import static edu.stanford.protege.webprotege.authorization.RoleAssignment.PROJECT_ID;
import static edu.stanford.protege.webprotege.authorization.RoleAssignment.USER_NAME;
import static java.util.stream.Collectors.toList;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;
Expand Down Expand Up @@ -50,7 +50,7 @@ public class AccessManagerImpl implements AccessManager {

private final EventDispatcher eventDispatcher;

private final TokenValidator tokenValidator;
private final JwtRolesExtractor jwtRolesExtractor;

private final BuiltInRoleOracle builtInRoleOracle;

Expand All @@ -64,14 +64,14 @@ public AccessManagerImpl(ObjectMapper objectMapper,
ProjectRoleDefinitionsManager projectRoleDefinitionsManager,
RoleDefinitionsManager roleDefinitionsManager,
EventDispatcher eventDispatcher,
TokenValidator tokenValidator,
JwtRolesExtractor jwtRolesExtractor,
BuiltInRoleOracle builtInRoleOracle) {
this.objectMapper = objectMapper;
this.mongoTemplate = mongoTemplate;
this.projectRoleDefinitionsManager = projectRoleDefinitionsManager;
this.roleDefinitionsManager = roleDefinitionsManager;
this.eventDispatcher = eventDispatcher;
this.tokenValidator = tokenValidator;
this.jwtRolesExtractor = jwtRolesExtractor;
this.builtInRoleOracle = builtInRoleOracle;
}

Expand Down Expand Up @@ -252,7 +252,7 @@ public Set<Capability> getCapabilityClosure(@Nonnull Subject subject, @Nonnull R
}

@Override
public boolean hasPermission(@Nonnull Subject subject, @Nonnull Resource resource, @Nonnull Capability capability, String jwt) {
public boolean hasPermission(@Nonnull Subject subject, @Nonnull Resource resource, @Nonnull Capability capability, @Nullable String jwt) {
logger.info("Checking permission for subject {} and resource {} with capability: {}", subject, resource, capability);

lock.readLock().lock();
Expand All @@ -262,21 +262,11 @@ public boolean hasPermission(@Nonnull Subject subject, @Nonnull Resource resourc
.flatMap(roleAssignment -> roleAssignment.getCapabilityClosure().stream())
.toList());

if (jwt != null && !jwt.isEmpty()) {
try {
List<RoleId> roleIds = tokenValidator.extractClaimsWithoutVerification(jwt).stream()
List<RoleId> roleIds = jwtRolesExtractor.safeExtractRolesWithoutVerification(jwt).stream()
.map(RoleId::new)
.toList();
capabilities.addAll(builtInRoleOracle.getCapabilitiesAssociatedToRoles(roleIds));
} catch (VerificationException e) {
logger.error("Error getting token claims", e);
throw new RuntimeException(e);
}
}


capabilities.addAll(builtInRoleOracle.getCapabilitiesAssociatedToRoles(roleIds));
return capabilities.contains(capability);

} finally {
lock.readLock().unlock();
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,9 @@ public class AuthorizationCommandsService {

private final AccessManager accessManager;

private final TokenValidator tokenValidator;

private final BuiltInRoleOracle builtInRoleOracle;

public AuthorizationCommandsService(AccessManager accessManager, TokenValidator tokenValidator, BuiltInRoleOracle builtInRoleOracle) {
public AuthorizationCommandsService(AccessManager accessManager) {
this.accessManager = accessManager;
this.tokenValidator = tokenValidator;
this.builtInRoleOracle = builtInRoleOracle;
}
// TODO: Update this when Alex has committed the code
public GetAuthorizationStatusResponse handleAuthorizationStatusCommand(GetAuthorizationStatusRequest request, ExecutionContext executionContext) {
var hasPermission = accessManager.hasPermission(request.subject(),
request.resource(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import edu.stanford.protege.webprotege.ipc.CommandHandler;
import edu.stanford.protege.webprotege.ipc.ExecutionContext;
import edu.stanford.protege.webprotege.ipc.WebProtegeHandler;
import org.keycloak.common.VerificationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
Expand All @@ -21,13 +20,14 @@ public class GetAuthorizedCapabilitiesHandler implements CommandHandler<GetAutho
private final static Logger logger = LoggerFactory.getLogger(GetAuthorizedCapabilitiesHandler.class);

private final AccessManager accessManager;
private final TokenValidator tokenValidator;

private final JwtRolesExtractor jwtRolesExtractor;

private final BuiltInRoleOracle builtInRoleOracle;

public GetAuthorizedCapabilitiesHandler(AccessManager accessManager, TokenValidator tokenValidator, BuiltInRoleOracle builtInRoleOracle) {
public GetAuthorizedCapabilitiesHandler(AccessManager accessManager, JwtRolesExtractor jwtRolesExtractor, BuiltInRoleOracle builtInRoleOracle) {
this.accessManager = accessManager;
this.tokenValidator = tokenValidator;
this.jwtRolesExtractor = jwtRolesExtractor;
this.builtInRoleOracle = builtInRoleOracle;
}

Expand All @@ -46,15 +46,12 @@ public Class<GetAuthorizedCapabilitiesRequest> getRequestClass() {
public Mono<GetAuthorizedCapabilitiesResponse> handleRequest(GetAuthorizedCapabilitiesRequest request, ExecutionContext executionContext) {
var capabilities = new HashSet<Capability>();

try {
//extract any SUPER admin capabilities from token
var roleIds = tokenValidator.extractClaimsWithoutVerification(executionContext.jwt()).stream()
.map(RoleId::new)
.toList();
capabilities.addAll(new HashSet<>(builtInRoleOracle.getCapabilitiesAssociatedToRoles(roleIds)));
} catch (VerificationException e) {
throw new RuntimeException(e);
}
// We should be able to change this to check if the resource is the application.
// extract any SUPER admin capabilities from token
var roleIds = jwtRolesExtractor.safeExtractRolesWithoutVerification(executionContext.jwt()).stream()
.map(RoleId::new)
.toList();
capabilities.addAll(new HashSet<>(builtInRoleOracle.getCapabilitiesAssociatedToRoles(roleIds)));

capabilities.addAll(accessManager.getCapabilityClosure(request.subject(),
request.resource()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package edu.stanford.protege.webprotege.authorization;

import org.keycloak.TokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.representations.AccessToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Set;

@Component
public class JwtRolesExtractor {

private static final Logger logger = LoggerFactory.getLogger(JwtRolesExtractor.class);

/**
* Extracts the roles from a JWT token without verification.
* Note: This method should only be used when token verification is not critical.
* For security-critical operations, use getTokenClaims() instead.
*
* @param jwt The JWT token to extract claims from
* @return Set of roles from the token's resource access
* @throws VerificationException if the token is malformed
*/
public Set<String> extractRolesWithoutVerification(String jwt) throws VerificationException {
TokenVerifier<AccessToken> verifier = TokenVerifier.create(jwt, AccessToken.class);
AccessToken token = verifier.getToken();
return token.getResourceAccess().get("webprotege").getRoles();
}

/**
* Extracts the roles from the specified JWT, which can be null.
* @param jwt The JWT. This may be null and may be blank/empty
* @return The roles extracted from the JWT. If there was a problem extracting the
* roles then the empty set will be returned and no exception will be thrown.
*/
public Set<String> safeExtractRolesWithoutVerification(String jwt) {
if(jwt == null) {
logger.debug("JWT is null. Returning empty set of roles.");
return Collections.emptySet();
}
if(jwt.isBlank()) {
logger.debug("JWT is empty. Returning empty set of roles.");
return Collections.emptySet();
}
try {
return extractRolesWithoutVerification(jwt);
} catch(VerificationException e) {
logger.error("Error extracting roles from JWT. Returning empty set of roles." , e);
return Collections.emptySet();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,50 +1,16 @@
package edu.stanford.protege.webprotege.authorization;

import org.keycloak.TokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKParser;
import org.keycloak.representations.AccessToken;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Configuration
public class TokenValidator {

private Map<String,PublicKey> publicKeys;


@Value("${keycloak-issuer-url}")
private String keycloakUrl;


@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}

/**
* Extracts claims from a JWT token without verification.
* Note: This method should only be used when token verification is not critical.
* For security-critical operations, use getTokenClaims() instead.
*
* @param jwt The JWT token to extract claims from
* @return Set of roles from the token's resource access
* @throws VerificationException if the token is malformed
*/
public Set<String> extractClaimsWithoutVerification(String jwt) throws VerificationException {
TokenVerifier<AccessToken> verifier = TokenVerifier.create(jwt, AccessToken.class);
AccessToken token = verifier.getToken();
return token.getResourceAccess().get("webprotege").getRoles();
}
}

This file was deleted.

4 changes: 1 addition & 3 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,4 @@ spring:
webprotege.rabbitmq:
requestqueue: webprotege-authorization-queue
responsequeue: webprotege-authorization-response-queue
timeout: 60000

keycloak-issuer-url: http://webprotege-local.edu/keycloak-admin/realms/webprotege/protocol/openid-connect/certs
timeout: 60000
Loading