Skip to content

Commit

Permalink
Moving some of the principal classes from Hub to Core.
Browse files Browse the repository at this point in the history
  • Loading branch information
pcc-cahilp committed Nov 14, 2024
1 parent d885769 commit 7dca950
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package gov.cdc.izgateway.security.principal;

import gov.cdc.izgateway.principal.provider.CertificatePrincipalProvider;
import gov.cdc.izgateway.security.CertificatePrincipal;
import gov.cdc.izgateway.security.IzgPrincipal;
import gov.cdc.izgateway.utils.X500Utils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.Globals;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.security.auth.x500.X500Principal;
import java.security.cert.X509Certificate;
import java.util.Map;

@Slf4j
@Component
public class CertificatePrincipalProviderImpl implements CertificatePrincipalProvider {

@Override
public IzgPrincipal createPrincipalFromCertificate(HttpServletRequest request) {
IzgPrincipal principal = new CertificatePrincipal();
X509Certificate[] certs = (X509Certificate[]) request.getAttribute(Globals.CERTIFICATES_ATTR);

if (certs == null || certs.length == 0) {
// Removing the warning log message as it is causing tests to fail
// log.warn("No certificates found in request.");
return null;
}

X509Certificate cert = certs[0];
X500Principal subject = cert.getSubjectX500Principal();

Map<String, String> parts = X500Utils.getParts(subject);
principal.setName(parts.get(X500Utils.COMMON_NAME));
String o = parts.get(X500Utils.ORGANIZATION);
if (StringUtils.isBlank(o)) {
o = parts.get(X500Utils.ORGANIZATION_UNIT);
}
principal.setOrganization(o);

principal.setValidFrom(cert.getNotBefore());
principal.setValidTo(cert.getNotAfter());
principal.setSerialNumber(String.valueOf(cert.getSerialNumber()));
principal.setIssuer(cert.getIssuerX500Principal().getName());

return principal;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package gov.cdc.izgateway.security.principal;

import gov.cdc.izgateway.principal.provider.JwtPrincipalProvider;
import gov.cdc.izgateway.security.IzgPrincipal;
import gov.cdc.izgateway.security.JWTPrincipal;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Objects;

@Slf4j
@Component
public class JwtJwksPrincipalProvider implements JwtPrincipalProvider {
@Value("${jwt.jwk-set-uri:}")
private String jwkSetUri;

@Override
public IzgPrincipal createPrincipalFromJwt(HttpServletRequest request) {
if (StringUtils.isBlank(jwkSetUri)) {
log.warn("No JWT set URI configured. JWT authentication is disabled.");
return null;
}

String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
log.debug("No JWT token found in Authorization header");
return null;
}

JwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
Jwt jwt;

try {
String token = authHeader.substring(7);
jwt = jwtDecoder.decode(token);
} catch (Exception e) {
log.warn("Error parsing JWT token", e);
return null;
}

IzgPrincipal principal = new JWTPrincipal();
principal.setName(jwt.getSubject());
principal.setOrganization(jwt.getClaim("organization"));
principal.setValidFrom(Date.from(Objects.requireNonNull(jwt.getIssuedAt())));
principal.setValidTo(Date.from(Objects.requireNonNull(jwt.getExpiresAt())));
principal.setSerialNumber(jwt.getId());
principal.setIssuer(jwt.getIssuer().toString());
principal.setAudience(jwt.getAudience());

return principal;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package gov.cdc.izgateway.security.principal;

import gov.cdc.izgateway.principal.provider.JwtPrincipalProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JwtPrincipalProviderConfig {
@Value("${jwt.provider:shared-secret}")
private String jwtProvider;

@Bean
public JwtPrincipalProvider jwtPrincipalProvider(
JwtJwksPrincipalProvider clientCredentialsProvider,
JwtSharedSecretPrincipalProvider sharedSecretProvider) {
if ("jwks".equalsIgnoreCase(jwtProvider)) {
return clientCredentialsProvider;
} else if ("shared-secret".equalsIgnoreCase(jwtProvider)) {
return sharedSecretProvider;
} else {
throw new IllegalArgumentException("Invalid JWT provider specified: " + jwtProvider);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package gov.cdc.izgateway.security.principal;

import gov.cdc.izgateway.principal.provider.JwtPrincipalProvider;
import gov.cdc.izgateway.security.IzgPrincipal;
import gov.cdc.izgateway.security.JWTPrincipal;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Collections;
import java.util.TreeSet;

@Slf4j
@Component
public class JwtSharedSecretPrincipalProvider implements JwtPrincipalProvider {
@Value("${jwt.shared-secret:}")
private String sharedSecret;

@Override
public IzgPrincipal createPrincipalFromJwt(HttpServletRequest request) {
if (StringUtils.isBlank(sharedSecret)) {
log.warn("No JWT shared secret was set. JWT authentication is disabled.");
return null;
}

String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
log.debug("No JWT token found in Authorization header");
return null;
}

Claims claims = parseJwt(authHeader);
if (claims == null) {
return null;
}
log.debug("JWT claims for current request: {}", claims);

return buildPrincipal(claims);
}

private Claims parseJwt(String authHeader) {
try {
SecretKey secretKey = Keys.hmacShaKeyFor(sharedSecret.getBytes());
String token = authHeader.substring(7);
return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody();
} catch (Exception e) {
log.warn("Error parsing JWT token", e);
return null;
}
}

private IzgPrincipal buildPrincipal(Claims claims) {
IzgPrincipal principal = new JWTPrincipal();
principal.setName(claims.getSubject());
principal.setOrganization(claims.get("organization", String.class));
principal.setValidFrom(claims.getNotBefore());
principal.setValidTo(claims.getExpiration());
principal.setSerialNumber(claims.getId());
principal.setIssuer(claims.getIssuer());
principal.setAudience(Collections.singletonList(claims.getAudience()));

TreeSet<String> scopes = extractScopes(claims);
principal.setRoles(RoleMapper.mapScopesToRoles(scopes));

return principal;
}

private TreeSet<String> extractScopes(Claims claims) {
TreeSet<String> scopes = new TreeSet<>();
String scopeString = claims.get("scope", String.class);
if (!StringUtils.isEmpty(scopeString)) {
Collections.addAll(scopes, scopeString.split(" "));
}
return scopes;
}
}
12 changes: 12 additions & 0 deletions src/main/java/gov/cdc/izgateway/security/principal/RoleMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package gov.cdc.izgateway.security.principal;

import java.util.Set;
import java.util.TreeSet;

public class RoleMapper {
public static Set<String> mapScopesToRoles(Set<String> scopes) {
// Until we've defined a mapping between scopes and roles, we'll just return the scopes as roles
return new TreeSet<>(scopes);
}

}

0 comments on commit 7dca950

Please sign in to comment.