Skip to content

Commit

Permalink
feat: extend principal token parsing and validation (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrejpetras authored Oct 12, 2024
1 parent fc4c90e commit 7661e47
Show file tree
Hide file tree
Showing 16 changed files with 852 additions and 20 deletions.
2 changes: 1 addition & 1 deletion docs/modules/tkit-quarkus/pages/includes/attributes.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
:project-version: 2.32.0
:project-version: 2.33.0
:quarkus-version: 3.15.1

:examples-dir: ./../examples/
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,23 @@ endif::add-copy-button-to-env-var[]
|string
|`apm-principal-token`

a| [[tkit-quarkus-rest-context_tkit-rs-context-token-parser-error-unauthorized]] [.property-path]##link:#tkit-quarkus-rest-context_tkit-rs-context-token-parser-error-unauthorized[`tkit.rs.context.token.parser-error-unauthorized`]##

[.description]
--
Throw Unauthorized exception for any parser error. Return StatusCode 401.


ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++TKIT_RS_CONTEXT_TOKEN_PARSER_ERROR_UNAUTHORIZED+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++TKIT_RS_CONTEXT_TOKEN_PARSER_ERROR_UNAUTHORIZED+++`
endif::add-copy-button-to-env-var[]
--
|boolean
|`true`

a| [[tkit-quarkus-rest-context_tkit-rs-context-principal-name-enabled]] [.property-path]##link:#tkit-quarkus-rest-context_tkit-rs-context-principal-name-enabled[`tkit.rs.context.principal.name.enabled`]##

[.description]
Expand Down Expand Up @@ -603,6 +620,74 @@ endif::add-copy-button-to-env-var[]
|boolean
|`true`

a| [[tkit-quarkus-rest-context_tkit-rs-context-token-issuers-issuers-enabled]] [.property-path]##link:#tkit-quarkus-rest-context_tkit-rs-context-token-issuers-issuers-enabled[`tkit.rs.context.token.issuers."issuers".enabled`]##

[.description]
--
Enable or disable oidc token config.


ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++TKIT_RS_CONTEXT_TOKEN_ISSUERS__ISSUERS__ENABLED+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++TKIT_RS_CONTEXT_TOKEN_ISSUERS__ISSUERS__ENABLED+++`
endif::add-copy-button-to-env-var[]
--
|boolean
|`true`

a| [[tkit-quarkus-rest-context_tkit-rs-context-token-issuers-issuers-url]] [.property-path]##link:#tkit-quarkus-rest-context_tkit-rs-context-token-issuers-issuers-url[`tkit.rs.context.token.issuers."issuers".url`]##

[.description]
--
Token issuer value


ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++TKIT_RS_CONTEXT_TOKEN_ISSUERS__ISSUERS__URL+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++TKIT_RS_CONTEXT_TOKEN_ISSUERS__ISSUERS__URL+++`
endif::add-copy-button-to-env-var[]
--
|string
|required icon:exclamation-circle[title=Configuration property is required]

a| [[tkit-quarkus-rest-context_tkit-rs-context-token-issuers-issuers-public-key-location-enabled]] [.property-path]##link:#tkit-quarkus-rest-context_tkit-rs-context-token-issuers-issuers-public-key-location-enabled[`tkit.rs.context.token.issuers."issuers".public-key-location.enabled`]##

[.description]
--
Use token realm for the public key.


ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++TKIT_RS_CONTEXT_TOKEN_ISSUERS__ISSUERS__PUBLIC_KEY_LOCATION_ENABLED+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++TKIT_RS_CONTEXT_TOKEN_ISSUERS__ISSUERS__PUBLIC_KEY_LOCATION_ENABLED+++`
endif::add-copy-button-to-env-var[]
--
|boolean
|`true`

a| [[tkit-quarkus-rest-context_tkit-rs-context-token-issuers-issuers-public-key-location-suffix]] [.property-path]##link:#tkit-quarkus-rest-context_tkit-rs-context-token-issuers-issuers-public-key-location-suffix[`tkit.rs.context.token.issuers."issuers".public-key-location.suffix`]##

[.description]
--
Public key server suffix


ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++TKIT_RS_CONTEXT_TOKEN_ISSUERS__ISSUERS__PUBLIC_KEY_LOCATION_SUFFIX+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++TKIT_RS_CONTEXT_TOKEN_ISSUERS__ISSUERS__PUBLIC_KEY_LOCATION_SUFFIX+++`
endif::add-copy-button-to-env-var[]
--
|string
|`/protocol/openid-connect/certs`

|===


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

public class PrincipalTokenRequiredException extends RestContextException {

public PrincipalTokenRequiredException() {
super(ErrorKeys.PRINCIPAL_TOKEN_REQUIRED, "Principal token is required");
public PrincipalTokenRequiredException(ErrorKeys errorKeys, String message) {
super(errorKeys, message);
}

public enum ErrorKeys {

PRINCIPAL_TOKEN_WRONG_ISSUER,

PRINCIPAL_TOKEN_REQUIRED;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.tkit.quarkus.rs.context.token;

import java.util.Map;

import io.quarkus.runtime.annotations.ConfigDocFilename;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
Expand Down Expand Up @@ -69,5 +71,73 @@ interface TokenConfig {
@WithName("header-param")
@WithDefault("apm-principal-token")
String tokenHeaderParam();

/**
* Token oidc configuration.
*/
@WithName("issuers")
Map<String, IssuerConfig> issuers();

/**
* Throw Unauthorized exception for any parser error. Return StatusCode 401.
*/
@WithName("parser-error-unauthorized")
@WithDefault("true")
boolean parserErrorUnauthorized();

/**
* Throw Unauthorized exception for required error. Return StatusCode 401.
*/
@WithName("required-error-unauthorized")
@WithDefault("false")
boolean requiredErrorUnauthorized();

/**
* Throw Unauthorized exception if access token issuer does not equal to principal token issuer.
* Return StatusCode 401.
*/
@WithName("check-tokens-issuer-error-unauthorized")
@WithDefault("true")
boolean checkTokensIssuerErrorUnauthorized();

/**
* Compare access token issuer with principal token issuer.
*/
@WithName("check-tokens-issuer")
@WithDefault("true")
boolean checkTokensIssuer();
}

/**
* Token oidc configuration.
*/
interface IssuerConfig {

/**
* Enable or disable oidc token config.
*/
@WithName("enabled")
@WithDefault("true")
boolean enabled();

/**
* Token issuer value
*/
@WithName("url")
String url();

/**
* Use token realm for the public key.
*/
@WithName("public-key-location.enabled")
@WithDefault("true")
boolean publicKeyLocationEnabled();

/**
* Public key server suffix
*/
@WithName("public-key-location.suffix")
@WithDefault("/protocol/openid-connect/certs")
String publicKeyLocationSuffix();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,94 @@
import jakarta.ws.rs.container.ContainerRequestContext;

import org.eclipse.microprofile.jwt.JsonWebToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.quarkus.security.UnauthorizedException;

@ApplicationScoped
public class TokenContextService {

private static final Logger log = LoggerFactory.getLogger(TokenContextService.class);

@Inject
TokenParserService tokenParserService;

@Inject
TokenContextConfig config;

@Inject
JsonWebToken accessToken;

public JsonWebToken getRestContextPrincipalToken(ContainerRequestContext containerRequestContext) {
if (!config.token().enabled()) {
return null;
}

// parse principal token
var token = getToken(containerRequestContext);
if (token == null && config.token().mandatory()) {
throw new PrincipalTokenRequiredException();

if (token == null) {
// check if principal token is mandatory
if (config.token().mandatory()) {
log.error("Principal token is required for the request.");
if (config.token().requiredErrorUnauthorized()) {
throw new UnauthorizedException("Principal token is required");
}
throw new PrincipalTokenRequiredException(PrincipalTokenRequiredException.ErrorKeys.PRINCIPAL_TOKEN_REQUIRED,
"Principal token is required");
}
} else {
// compare access token and principal token issuer
if (config.token().checkTokensIssuer() && accessToken.getRawToken() != null) {
if (!token.getIssuer().equals(accessToken.getIssuer())) {
log.error("Principal token has undefined issuer compare to access token.");
if (config.token().checkTokensIssuerErrorUnauthorized()) {
throw new UnauthorizedException("Undefined principal token issuer");
}
throw new PrincipalTokenRequiredException(
PrincipalTokenRequiredException.ErrorKeys.PRINCIPAL_TOKEN_WRONG_ISSUER,
"Undefined principal token issuer");
}
}
}

return token;
}

private JsonWebToken getToken(ContainerRequestContext containerRequestContext) {

String rawToken = containerRequestContext.getHeaders().getFirst(config.token().tokenHeaderParam());
var tc = config.token();
String rawToken = containerRequestContext.getHeaders().getFirst(tc.tokenHeaderParam());
if (rawToken == null) {
return null;
}

TokenParserRequest request = new TokenParserRequest(rawToken)
.issuerEnabled(config.token().issuerEnabled())
.type(config.token().type())
.verify(config.token().verify())
.issuerSuffix(config.token().issuerSuffix());
return tokenParserService.parseToken(request);
.issuerEnabled(tc.issuerEnabled())
.type(tc.type())
.verify(tc.verify())
.issuerSuffix(tc.issuerSuffix());

var issuers = tc.issuers();
if (issuers != null && !issuers.isEmpty()) {
issuers.forEach((k, v) -> {
if (v.enabled()) {
request.addIssuerParserRequest(k, new TokenParserRequest.IssuerParserRequest()
.url(v.url())
.publicKeyLocationEnabled(v.publicKeyLocationEnabled())
.publicKeyLocationSuffix(v.publicKeyLocationSuffix()));
}
});
}

try {
return tokenParserService.parseToken(request);
} catch (TokenException ex) {
if (tc.parserErrorUnauthorized()) {
throw new UnauthorizedException(ex);
}
throw ex;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ public class TokenException extends RuntimeException {

private final Enum<?> key;

public TokenException(Enum<?> key, String message) {
super(message);
this.key = key;
}

public TokenException(Enum<?> key, String message, Throwable throwable) {
super(message, throwable);
this.key = key;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.tkit.quarkus.rs.context.token;

import java.util.HashMap;
import java.util.Map;

public class TokenParserRequest {

private final String rawToken;
Expand All @@ -12,6 +15,8 @@ public class TokenParserRequest {

private String type;

private final Map<String, IssuerParserRequest> issuerParserRequests = new HashMap<>();

public TokenParserRequest(String rawToken) {
this.rawToken = rawToken;
}
Expand Down Expand Up @@ -71,4 +76,60 @@ public TokenParserRequest verify(boolean verify) {
setVerify(verify);
return this;
}

public void addIssuerParserRequest(String name, IssuerParserRequest issuerParserRequest) {
issuerParserRequests.put(name, issuerParserRequest);
}

public Map<String, IssuerParserRequest> getIssuerParserRequests() {
return issuerParserRequests;
}

public static class IssuerParserRequest {

private String url;

private String publicKeyLocationSuffix;

private boolean publicKeyLocationEnabled;

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public IssuerParserRequest url(String url) {
setUrl(url);
return this;
}

public void setPublicKeyLocationSuffix(String publicKeyLocationSuffix) {
this.publicKeyLocationSuffix = publicKeyLocationSuffix;
}

public String getPublicKeyLocationSuffix() {
return publicKeyLocationSuffix;
}

public IssuerParserRequest publicKeyLocationSuffix(String publicKeyLocationSuffix) {
setPublicKeyLocationSuffix(publicKeyLocationSuffix);
return this;
}

public boolean getPublicKeyLocationEnabled() {
return publicKeyLocationEnabled;
}

public void setPublicKeyLocationEnabled(boolean publicKeyLocationEnabled) {
this.publicKeyLocationEnabled = publicKeyLocationEnabled;
}

public IssuerParserRequest publicKeyLocationEnabled(boolean publicKeyLocationEnabled) {
setPublicKeyLocationEnabled(publicKeyLocationEnabled);
return this;
}
}
}
Loading

0 comments on commit 7661e47

Please sign in to comment.