Skip to content

Commit

Permalink
feat(jans-auth-server): split grant validation logic into TokenRestWe…
Browse files Browse the repository at this point in the history
…bServiceValidator #1591

docs: no docs required
  • Loading branch information
yuriyz committed Jul 20, 2022
1 parent f9f6f49 commit 812e605
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,16 @@
import io.jans.as.common.service.AttributeService;
import io.jans.as.model.configuration.AppConfiguration;
import io.jans.as.model.token.JsonWebResponse;
import io.jans.as.server.model.audit.OAuth2AuditLog;
import io.jans.as.server.model.ldap.TokenEntity;
import io.jans.model.custom.script.conf.CustomScriptConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.core.Response;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.function.Function;

/**
Expand Down Expand Up @@ -60,6 +57,7 @@ public class ExecutionContext {
private Set<String> scopes;
private String claimsAsString;
private List<SessionId> userSessions;
private OAuth2AuditLog auditLog;

@NotNull
private final Map<String, String> attributes = new HashMap<>();
Expand Down Expand Up @@ -90,6 +88,14 @@ public HttpServletResponse getHttpResponse() {
return httpResponse;
}

public OAuth2AuditLog getAuditLog() {
return auditLog;
}

public void setAuditLog(OAuth2AuditLog auditLog) {
this.auditLog = auditLog;
}

public Client getClient() {
return client;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,79 +181,17 @@ public Response requestAccessToken(String grantType, String code,
executionContext.setCertAsPem(request.getHeader(X_CLIENTCERT));
executionContext.setDpop(dpopStr);
executionContext.setClient(client);
executionContext.setDpop(dpopStr);
executionContext.setAppConfiguration(appConfiguration);
executionContext.setAttributeService(attributeService);
executionContext.setAuditLog(auditLog);

if (gt == GrantType.AUTHORIZATION_CODE) {
log.debug("Attempting to find authorizationCodeGrant by clientId: '{}', code: '{}'", client.getClientId(), code);
final AuthorizationCodeGrant authorizationCodeGrant = authorizationGrantList.getAuthorizationCodeGrant(code);
log.trace("AuthorizationCodeGrant : '{}'", authorizationCodeGrant);

if (authorizationCodeGrant == null) {
log.debug("AuthorizationCodeGrant is empty by clientId: '{}', code: '{}'", client.getClientId(), code);
// if authorization code is not found then code was already used or wrong client provided = remove all grants with this auth code
grantService.removeAllByAuthorizationCode(code);
return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Unable to find grant object for given code."), auditLog);
}

if (!client.getClientId().equals(authorizationCodeGrant.getClientId())) {
log.debug("AuthorizationCodeGrant is found but belongs to another client. Grant's clientId: '{}', code: '{}'", authorizationCodeGrant.getClientId(), code);
// if authorization code is not found then code was already used or wrong client provided = remove all grants with this auth code
grantService.removeAllByAuthorizationCode(code);
return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Client mismatch."), auditLog);
}

validatePKCE(authorizationCodeGrant, codeVerifier, auditLog);

authorizationCodeGrant.setIsCachedWithNoPersistence(false);
authorizationCodeGrant.save();

RefreshToken reToken = createRefreshToken(request, client, scope, authorizationCodeGrant, dpopStr);

scope = authorizationCodeGrant.checkScopesPolicy(scope);

executionContext.setGrant(authorizationCodeGrant);
AccessToken accToken = authorizationCodeGrant.createAccessToken(executionContext); // create token after scopes are checked

IdToken idToken = null;
if (authorizationCodeGrant.getScopes().contains(OPENID)) {
String nonce = authorizationCodeGrant.getNonce();
boolean includeIdTokenClaims = Boolean.TRUE.equals(
appConfiguration.getLegacyIdTokenClaims());
final String idTokenTokenBindingCnf = client.getIdTokenTokenBindingCnf();
Function<JsonWebResponse, Void> authorizationCodePreProcessing = jsonWebResponse -> {
if (StringUtils.isNotBlank(idTokenTokenBindingCnf) && StringUtils.isNotBlank(authorizationCodeGrant.getTokenBindingHash())) {
TokenBindingMessage.setCnfClaim(jsonWebResponse, authorizationCodeGrant.getTokenBindingHash(), idTokenTokenBindingCnf);
}
return null;
};

ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(request, authorizationCodeGrant, client, appConfiguration, attributeService);

executionContext.setIncludeIdTokenClaims(includeIdTokenClaims);
executionContext.setPreProcessing(JwrService.wrapWithSidFunction(authorizationCodePreProcessing, sessionIdObj != null ? sessionIdObj.getOutsideSid() : null));
executionContext.setPostProcessor(externalUpdateTokenService.buildModifyIdTokenProcessor(context));

idToken = authorizationCodeGrant.createIdToken(
nonce, authorizationCodeGrant.getAuthorizationCode(), accToken, null, null, executionContext);
}

auditLog.updateOAuth2AuditLog(authorizationCodeGrant, true);

grantService.removeAuthorizationCode(authorizationCodeGrant.getAuthorizationCode().getCode());

final String entity = getJSonResponse(accToken, accToken.getTokenType(), accToken.getExpiresIn(), reToken, scope, idToken);
return response(Response.ok().entity(entity), auditLog);
return processAuthorizationCode(code, scope, codeVerifier, sessionIdObj, executionContext);
}

if (gt == GrantType.REFRESH_TOKEN) {
AuthorizationGrant authorizationGrant = authorizationGrantList.getAuthorizationGrantByRefreshToken(client.getClientId(), refreshToken);

if (authorizationGrant == null) {
log.trace("Grant object is not found by refresh token.");
return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Unable to find grant object by refresh token or otherwise token type or client does not match."), auditLog);
}
tokenRestWebServiceValidator.validateGrant(authorizationGrant, client, refreshToken, auditLog);

final RefreshToken refreshTokenObject = authorizationGrant.getRefreshToken(refreshToken);
if (refreshTokenObject == null || !refreshTokenObject.isValid()) {
Expand Down Expand Up @@ -510,6 +448,58 @@ public Response requestAccessToken(String grantType, String code,
return response(builder, auditLog);
}

private Response processAuthorizationCode(String code, String scope, String codeVerifier, SessionId sessionIdObj, ExecutionContext executionContext) {
Client client = executionContext.getClient();

log.debug("Attempting to find authorizationCodeGrant by clientId: '{}', code: '{}'", client.getClientId(), code);
final AuthorizationCodeGrant authorizationCodeGrant = authorizationGrantList.getAuthorizationCodeGrant(code);
log.trace("AuthorizationCodeGrant : '{}'", authorizationCodeGrant);

// if authorization code is not found then code was already used or wrong client provided = remove all grants with this auth code
tokenRestWebServiceValidator.validateGrant(authorizationCodeGrant, client, code, executionContext.getAuditLog(), grant -> grantService.removeAllByAuthorizationCode(code));
validatePKCE(authorizationCodeGrant, codeVerifier, executionContext.getAuditLog());

authorizationCodeGrant.setIsCachedWithNoPersistence(false);
authorizationCodeGrant.save();

RefreshToken reToken = createRefreshToken(executionContext.getHttpRequest(), client, scope, authorizationCodeGrant, executionContext.getDpop());

scope = authorizationCodeGrant.checkScopesPolicy(scope);

executionContext.setGrant(authorizationCodeGrant);
AccessToken accToken = authorizationCodeGrant.createAccessToken(executionContext); // create token after scopes are checked

IdToken idToken = null;
if (authorizationCodeGrant.getScopes().contains(OPENID)) {
String nonce = authorizationCodeGrant.getNonce();
boolean includeIdTokenClaims = Boolean.TRUE.equals(
appConfiguration.getLegacyIdTokenClaims());
final String idTokenTokenBindingCnf = client.getIdTokenTokenBindingCnf();
Function<JsonWebResponse, Void> authorizationCodePreProcessing = jsonWebResponse -> {
if (StringUtils.isNotBlank(idTokenTokenBindingCnf) && StringUtils.isNotBlank(authorizationCodeGrant.getTokenBindingHash())) {
TokenBindingMessage.setCnfClaim(jsonWebResponse, authorizationCodeGrant.getTokenBindingHash(), idTokenTokenBindingCnf);
}
return null;
};

ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(executionContext.getHttpRequest(), authorizationCodeGrant, client, appConfiguration, attributeService);

executionContext.setIncludeIdTokenClaims(includeIdTokenClaims);
executionContext.setPreProcessing(JwrService.wrapWithSidFunction(authorizationCodePreProcessing, sessionIdObj != null ? sessionIdObj.getOutsideSid() : null));
executionContext.setPostProcessor(externalUpdateTokenService.buildModifyIdTokenProcessor(context));

idToken = authorizationCodeGrant.createIdToken(
nonce, authorizationCodeGrant.getAuthorizationCode(), accToken, null, null, executionContext);
}

executionContext.getAuditLog().updateOAuth2AuditLog(authorizationCodeGrant, true);

grantService.removeAuthorizationCode(authorizationCodeGrant.getAuthorizationCode().getCode());

final String entity = getJSonResponse(accToken, accToken.getTokenType(), accToken.getExpiresIn(), reToken, scope, idToken);
return response(Response.ok().entity(entity), executionContext.getAuditLog());
}

@Nullable
private Client getClient() {
SessionClient sessionClient = identity.getSessionClient();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.jans.as.model.token.TokenErrorResponseType;
import io.jans.as.server.audit.ApplicationAuditLogger;
import io.jans.as.server.model.audit.OAuth2AuditLog;
import io.jans.as.server.model.common.AuthorizationGrant;
import io.jans.as.server.model.common.DeviceAuthorizationCacheControl;
import io.jans.as.server.util.ServerUtil;
import jakarta.ejb.Stateless;
Expand All @@ -21,6 +22,7 @@

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

import static io.jans.as.model.config.Constants.REASON_CLIENT_NOT_AUTHORIZED;

Expand Down Expand Up @@ -130,4 +132,27 @@ public void validateDeviceAuthorization(Client client, String deviceCode, Device
throw new WebApplicationException(response(error(400, TokenErrorResponseType.INVALID_GRANT, REASON_CLIENT_NOT_AUTHORIZED), oAuth2AuditLog));
}
}

public void validateGrant(AuthorizationGrant grant, Client client, Object identifier, OAuth2AuditLog auditLog) {
validateGrant(grant, client, identifier, auditLog, null);
}


public void validateGrant(AuthorizationGrant grant, Client client, Object identifier, OAuth2AuditLog auditLog, Consumer<AuthorizationGrant> onFailure) {
if (grant == null) {
log.debug("AuthorizationGrant not found by clientId: '{}', identifier: '{}'", client.getClientId(), identifier);
if (onFailure != null) {
onFailure.accept(grant);
}
throw new WebApplicationException(response(error(400, TokenErrorResponseType.INVALID_GRANT, "Unable to find grant object for given code."), auditLog));
}

if (!client.getClientId().equals(grant.getClientId())) {
log.debug("AuthorizationCodeGrant is found but belongs to another client. Grant's clientId: '{}', identifier: '{}'", grant.getClientId(), identifier);
if (onFailure != null) {
onFailure.accept(grant);
}
throw new WebApplicationException(response(error(400, TokenErrorResponseType.INVALID_GRANT, "Client mismatch."), auditLog));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
import io.jans.as.common.service.common.ApplicationFactory;
Expand Down Expand Up @@ -140,7 +141,7 @@ public static CacheControl cacheControlWithNoStoreTransformAndPrivate() {
}

public static ObjectMapper createJsonMapper() {
final AnnotationIntrospector jaxb = new JaxbAnnotationIntrospector();
final AnnotationIntrospector jaxb = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance());
final AnnotationIntrospector jackson = new JacksonAnnotationIntrospector();

final AnnotationIntrospector pair = AnnotationIntrospector.pair(jackson, jaxb);
Expand Down
Loading

0 comments on commit 812e605

Please sign in to comment.