diff --git a/docs/janssen-server/auth-server/endpoints/authorization-challenge.md b/docs/janssen-server/auth-server/endpoints/authorization-challenge.md index 723fcf29015..86c4e38b089 100644 --- a/docs/janssen-server/auth-server/endpoints/authorization-challenge.md +++ b/docs/janssen-server/auth-server/endpoints/authorization-challenge.md @@ -11,7 +11,7 @@ tags: Authorization Challenge Endpoint allows first-party native client obtain authorization code which later can be exchanged on access token. This can provide an entirely browserless OAuth 2.0 experience suited for native applications. -This endpoint conforms to [OAuth 2.0 for First-Party Native Applications](https://www.ietf.org/archive/id/draft-parecki-oauth-first-party-native-apps-02.html) specifications. +This endpoint conforms to [OAuth 2.0 for First-Party Applications](https://www.ietf.org/archive/id/draft-parecki-oauth-first-party-apps-02.html) specifications. URL to access authorization challenge endpoint on Janssen Server is listed in the response of Janssen Server's well-known [configuration endpoint](./configuration.md) given below. @@ -152,6 +152,15 @@ String clientId = context.getHttpRequest().getParameter("client_id"); authorizationChallengeSessionObject.getAttributes().getAttributes().put("client_id", clientId); ``` +AS automatically validates DPoP if it is set during auth session creation. +Thus it's recommended to set `jkt` of the auth session if DPoP is used. +```java +final String dpop = context.getHttpRequest().getHeader(DpopService.DPOP); +if (StringUtils.isNotBlank(dpop)) { + authorizationChallengeSessionObject.getAttributes().setJkt(getDpopJkt(dpop)); +} +``` + Full sample script can be found [here](../../../script-catalog/authorization_challenge/AuthorizationChallenge.java) ## Web session diff --git a/docs/janssen-server/auth-server/oauth-features/README.md b/docs/janssen-server/auth-server/oauth-features/README.md index a01b72050a7..1b38bcdb478 100644 --- a/docs/janssen-server/auth-server/oauth-features/README.md +++ b/docs/janssen-server/auth-server/oauth-features/README.md @@ -26,7 +26,7 @@ The [Janssen Authentication Server](https://github.com/JanssenProject/jans/tree/ - OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens [(spec)](https://datatracker.ietf.org/doc/html/rfc8705) - Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants [(spec)](https://www.rfc-editor.org/rfc/rfc7521.html) - JWT Secured Authorization Response Mode for OAuth 2.0 (JARM) [(spec)](https://openid.net/specs/oauth-v2-jarm.html) -- OAuth 2.0 for First-Party Native Applications [(spec draft)](https://www.ietf.org/archive/id/draft-parecki-oauth-first-party-native-apps-02.html) +- OAuth 2.0 for First-Party Applications [(spec draft)](https://www.ietf.org/archive/id/draft-parecki-oauth-first-party-apps-02.html) - The Use of Attestation in OAuth 2.0 Dynamic Client Registration [(spec draft)](https://www.ietf.org/id/draft-tschofenig-oauth-attested-dclient-reg-00.html) - OpenID Connect Core Error Code unmet_authentication_requirements [(spec)](https://openid.net/specs/openid-connect-unmet-authentication-requirements-1_0.html) - Transaction Tokens [(spec)](https://drafts.oauth.net/oauth-transaction-tokens/draft-ietf-oauth-transaction-tokens.html) diff --git a/docs/script-catalog/authorization_challenge/AuthorizationChallenge.java b/docs/script-catalog/authorization_challenge/AuthorizationChallenge.java index b10303f78bf..c0554577fed 100644 --- a/docs/script-catalog/authorization_challenge/AuthorizationChallenge.java +++ b/docs/script-catalog/authorization_challenge/AuthorizationChallenge.java @@ -1,5 +1,6 @@ import io.jans.as.common.model.common.User; import io.jans.as.common.model.session.AuthorizationChallengeSession; +import io.jans.as.server.auth.DpopService; import io.jans.as.server.authorize.ws.rs.AuthorizationChallengeSessionService; import io.jans.as.server.service.UserService; import io.jans.as.server.service.external.context.ExternalScriptContext; @@ -128,9 +129,15 @@ private AuthorizationChallengeSession prepareAuthorizationChallengeSession(Exter AuthorizationChallengeSessionService authorizationChallengeSessionService = CdiUtil.bean(AuthorizationChallengeSessionService.class); boolean newSave = authorizationChallengeSessionObject == null; if (newSave) { -// authorizationChallengeSessionObject = authorizationChallengeSessionService.newAuthorizationChallengeSession(); + authorizationChallengeSessionObject = authorizationChallengeSessionService.newAuthorizationChallengeSession(); } + final String dpop = context.getHttpRequest().getHeader(DpopService.DPOP); + if (StringUtils.isNotBlank(dpop)) { + authorizationChallengeSessionObject.getAttributes().setJkt(getDpopJkt(dpop)); + } + + String username = context.getHttpRequest().getParameter(USERNAME_PARAMETER); if (StringUtils.isNotBlank(username)) { authorizationChallengeSessionObject.getAttributes().getAttributes().put(USERNAME_PARAMETER, username); @@ -160,6 +167,19 @@ private AuthorizationChallengeSession prepareAuthorizationChallengeSession(Exter return authorizationChallengeSessionObject; } + public String getDpopJkt(String dpop) { + if (StringUtils.isBlank(dpop)) { + return null; + } + + try { + return DpopService.getDpopJwkThumbprint(dpop); + } catch (Exception e) { + scriptLogger.error("Failed to get jkt from DPoP: " + dpop,e); + return null; + } + } + private String getParameterFromAuthorizationChallengeSession(ExternalScriptContext context, String parameterName) { final AuthorizationChallengeSession sessionObject = context.getAuthzRequest().getAuthorizationChallengeSessionObject(); if (sessionObject != null) { diff --git a/docs/script-catalog/authorization_challenge/authorization-challenge.md b/docs/script-catalog/authorization_challenge/authorization-challenge.md index cab32d206af..5c92608d8ca 100644 --- a/docs/script-catalog/authorization_challenge/authorization-challenge.md +++ b/docs/script-catalog/authorization_challenge/authorization-challenge.md @@ -9,7 +9,7 @@ tags: ## Overview -The Jans-Auth server implements [OAuth 2.0 for First-Party Native Applications](https://www.ietf.org/archive/id/draft-parecki-oauth-first-party-native-apps-02.html). +The Jans-Auth server implements [OAuth 2.0 for First-Party Applications](https://www.ietf.org/archive/id/draft-parecki-oauth-first-party-apps-02.html). This script is used to control/customize Authorization Challenge Endpoint. ## Behavior diff --git a/jans-auth-server/common/src/main/java/io/jans/as/common/model/session/AuthorizationChallengeSessionAttributes.java b/jans-auth-server/common/src/main/java/io/jans/as/common/model/session/AuthorizationChallengeSessionAttributes.java index d1f1cf22f62..52f480c513d 100644 --- a/jans-auth-server/common/src/main/java/io/jans/as/common/model/session/AuthorizationChallengeSessionAttributes.java +++ b/jans-auth-server/common/src/main/java/io/jans/as/common/model/session/AuthorizationChallengeSessionAttributes.java @@ -18,6 +18,12 @@ public class AuthorizationChallengeSessionAttributes implements Serializable { @JsonProperty("acr_values") private String acrValues; + // jkt - JWK SHA-256 Thumbprint confirmation method. + // The value of the jkt member MUST be the base64url encoding (as defined in [RFC7515]) of the JWK SHA-256 Thumbprint + // (according to [RFC7638]) of the DPoP public key (in JWK format) to which the access token is bound. + @JsonProperty("jkt") + private String jkt; + @JsonProperty("attributes") private Map attributes; @@ -38,11 +44,21 @@ public void setAcrValues(String acrValues) { this.acrValues = acrValues; } + public String getJkt() { + return jkt; + } + + public AuthorizationChallengeSessionAttributes setJkt(String jkt) { + this.jkt = jkt; + return this; + } + @Override public String toString() { return "DeviceSessionAttributes{" + "acrValues='" + acrValues + '\'' + "attributes='" + attributes + '\'' + + "jkt='" + jkt + '\'' + '}'; } } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/auth/DpopService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/auth/DpopService.java index fa3ef0043f7..d20182d4703 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/auth/DpopService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/auth/DpopService.java @@ -221,7 +221,7 @@ private Response.ResponseBuilder error(int status, TokenErrorResponseType type, return Response.status(status).type(MediaType.APPLICATION_JSON_TYPE).entity(errorResponseFactory.errorAsJson(type, reason)); } - public String getDpopJwkThumbprint(String dpopStr) throws InvalidJwtException, NoSuchAlgorithmException, JWKException, NoSuchProviderException { + public static String getDpopJwkThumbprint(String dpopStr) throws InvalidJwtException, NoSuchAlgorithmException, JWKException, NoSuchProviderException { final Jwt dpop = Jwt.parseOrThrow(dpopStr); JSONWebKey jwk = JSONWebKey.fromJSONObject(dpop.getHeader().getJwk()); return jwk.getJwkThumbprint(); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeEndpoint.java b/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeEndpoint.java index 44113ce1f93..57ddd927342 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeEndpoint.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeEndpoint.java @@ -1,6 +1,7 @@ package io.jans.as.server.authorize.ws.rs; import io.jans.as.model.util.QueryStringDecoder; +import io.jans.as.server.auth.DpopService; import io.jans.as.server.service.RequestParameterService; import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletRequest; @@ -38,6 +39,8 @@ public Response requestAuthorizationPost( @FormParam("acr_values") String acrValues, @FormParam("auth_session") String authorizationChallengeSession, @FormParam("use_auth_session") String useAuthorizationChallengeSession, + @FormParam("device_session") String deviceSession, // old name in draft 00 + @FormParam("use_device_session") String useDeviceSession, // old name in draft 00 @FormParam("prompt") String prompt, @FormParam("state") String state, @FormParam("nonce") String nonce, @@ -63,6 +66,15 @@ public Response requestAuthorizationPost( authzRequest.setCodeChallenge(codeChallenge); authzRequest.setCodeChallengeMethod(codeChallengeMethod); authzRequest.setAuthzDetailsString(authorizationDetails); + authzRequest.setDpop(httpRequest.getHeader(DpopService.DPOP)); + + // backwards compatibilty: device_session (up to draft 02) vs auth_session (draft 02 and later) + if (authorizationChallengeSession == null && deviceSession != null) { + authzRequest.setAuthorizationChallengeSession(deviceSession); + } + if (useAuthorizationChallengeSession == null && useDeviceSession != null) { + authzRequest.setUseAuthorizationChallengeSession(Boolean.parseBoolean(useDeviceSession)); + } return authorizationChallengeService.requestAuthorization(authzRequest); } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeService.java index 841dc95b987..ed9ca039a96 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeService.java @@ -117,6 +117,8 @@ public void prepareAuthzRequest(AuthzRequest authzRequest) { if (StringUtils.isNotBlank(authzRequest.getAuthorizationChallengeSession())) { final AuthorizationChallengeSession session = authorizationChallengeSessionService.getAuthorizationChallengeSession(authzRequest.getAuthorizationChallengeSession()); + authorizationChallengeValidator.validateDpopJkt(session, authzRequest.getDpop()); + authzRequest.setAuthorizationChallengeSessionObject(session); if (session != null) { final Map attributes = session.getAttributes().getAttributes(); @@ -188,6 +190,7 @@ public Response authorize(AuthzRequest authzRequest) throws IOException, TokenBi authorizationGrant.setClaims(authzRequest.getClaims()); authorizationGrant.setSessionDn(sessionUser != null ? sessionUser.getDn() : "no_session_for_authorization_challenge"); // no need for session as at Authorization Endpoint authorizationGrant.setAcrValues(grantAcr); + authorizationGrant.setAuthorizationChallenge(true); authorizationGrant.save(); String authorizationCode = authorizationGrant.getAuthorizationCode().getCode(); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeSessionService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeSessionService.java index bc99f18ad8a..57767db1d2e 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeSessionService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeSessionService.java @@ -10,7 +10,10 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; -import java.util.*; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.UUID; /** * @author Yuriy Z diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeValidator.java b/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeValidator.java index e6751ccf6e3..3e0929e688f 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeValidator.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthorizationChallengeValidator.java @@ -1,10 +1,13 @@ package io.jans.as.server.authorize.ws.rs; import io.jans.as.common.model.registration.Client; +import io.jans.as.common.model.session.AuthorizationChallengeSession; import io.jans.as.model.authorize.AuthorizeErrorResponseType; import io.jans.as.model.common.GrantType; import io.jans.as.model.configuration.AppConfiguration; import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.model.token.TokenErrorResponseType; +import io.jans.as.server.auth.DpopService; import io.jans.as.server.model.config.Constants; import io.jans.as.server.service.ScopeService; import jakarta.enterprise.context.RequestScoped; @@ -12,6 +15,7 @@ import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Response; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import java.util.Arrays; @@ -35,6 +39,30 @@ public class AuthorizationChallengeValidator { @Inject private ScopeService scopeService; + public void validateDpopJkt(AuthorizationChallengeSession session, String dpop) { + final String jkt = session.getAttributes().getJkt(); + if (StringUtils.isBlank(jkt)) { + return; + } + + try { + final String dpopJwkThumbprint = DpopService.getDpopJwkThumbprint(dpop); + if (jkt.equals(dpopJwkThumbprint)) { + return; + } else { + log.debug("Unable to match dpopJkt: {} with sessionJkt: {}", dpopJwkThumbprint, jkt); + } + } catch (Exception e) { + String msg = String.format("Failed to validate dpop jtk. jkt: %s, dpop: %s", jkt, dpop); + log.debug(msg, e); + } + + throw new WebApplicationException(errorResponseFactory + .newErrorResponse(Response.Status.BAD_REQUEST) + .entity(errorResponseFactory.getErrorAsJson(TokenErrorResponseType.INVALID_DPOP_PROOF, "", "Invalid DPoP.")) + .build()); + } + public void validateGrantType(Client client, String state) { if (client == null) { final String msg = "Unable to find client."; diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthzRequest.java b/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthzRequest.java index c57bf15f17e..07edb4b60a7 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthzRequest.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/authorize/ws/rs/AuthzRequest.java @@ -55,6 +55,7 @@ public class AuthzRequest { private String claims; private String authReqId; private String dpopJkt; + private String dpop; private String authzDetailsString; private AuthzDetails authzDetails; private String httpMethod; @@ -95,6 +96,15 @@ public void setDpopJkt(String dpopJkt) { this.dpopJkt = dpopJkt; } + public String getDpop() { + return dpop; + } + + public AuthzRequest setDpop(String dpop) { + this.dpop = dpop; + return this; + } + public AuthorizationChallengeSession getAuthorizationChallengeSessionObject() { return authorizationChallengeSessionObject; } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AbstractAuthorizationGrant.java b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AbstractAuthorizationGrant.java index cab786c5796..acd2c448214 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AbstractAuthorizationGrant.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AbstractAuthorizationGrant.java @@ -79,6 +79,7 @@ public abstract class AbstractAuthorizationGrant implements IAuthorizationGrant private String acrValues; private String sessionDn; + private boolean isAuthorizationChallenge; protected final ConcurrentMap txTokens = new ConcurrentHashMap<>(); protected final ConcurrentMap accessTokens = new ConcurrentHashMap<>(); @@ -110,6 +111,15 @@ public void setReferenceId(String referenceId) { this.referenceId = referenceId; } + public boolean isAuthorizationChallenge() { + return isAuthorizationChallenge; + } + + public AbstractAuthorizationGrant setAuthorizationChallenge(boolean authorizationChallenge) { + isAuthorizationChallenge = authorizationChallenge; + return this; + } + public Integer getStatusListIndex() { return statusListIndex; } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrant.java b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrant.java index 4b78e439674..0b7afad2a29 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrant.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrant.java @@ -201,6 +201,7 @@ private void initTokenFromGrant(TokenEntity token) { } token.getAttributes().setAuthorizationDetails(getAuthzDetailsAsString()); + token.getAttributes().setAuthorizationChallenge(isAuthorizationChallenge()); token.setScope(getScopesAsString()); token.setAuthMode(getAcrValues()); token.setSessionDn(getSessionDn()); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrantList.java b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrantList.java index 4ab61cdd196..00c10688ad3 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrantList.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/model/common/AuthorizationGrantList.java @@ -354,6 +354,7 @@ public AuthorizationGrant asGrant(TokenEntity tokenEntity) { result.setTokenEntity(tokenEntity); result.setReferenceId(tokenEntity.getReferenceId()); result.setStatusListIndex(tokenEntity.getAttributes().getStatusListIndex()); + result.setAuthorizationChallenge(tokenEntity.getAttributes().isAuthorizationChallenge()); if (StringUtils.isNotBlank(grantId)) { result.setGrantId(grantId); } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceImpl.java b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceImpl.java index 95134a34a97..b32651ec9f2 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceImpl.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceImpl.java @@ -185,7 +185,7 @@ grantType, code, redirectUri, username, refreshToken, clientId, prepareForLogs(r scope = ServerUtil.urlDecode(scope); // it may be encoded in uma case try { - tokenRestWebServiceValidator.validateParams(grantType, code, redirectUri, refreshToken, auditLog); + tokenRestWebServiceValidator.validateParams(grantType, code, refreshToken, auditLog); GrantType gt = GrantType.fromString(grantType); log.debug("Grant type: '{}'", gt); @@ -212,7 +212,7 @@ grantType, code, redirectUri, username, refreshToken, clientId, prepareForLogs(r executionContext.setAuthzDetails(authzDetails); if (gt == GrantType.AUTHORIZATION_CODE) { - return processAuthorizationCode(code, scope, codeVerifier, sessionIdObj, executionContext); + return processAuthorizationCode(code, scope, codeVerifier, sessionIdObj, redirectUri, executionContext); } else if (gt == GrantType.REFRESH_TOKEN) { return processRefreshTokenGrant(scope, refreshToken, idTokenPreProcessing, executionContext); } else if (gt == GrantType.CLIENT_CREDENTIALS) { @@ -434,7 +434,7 @@ private TokenEntity lockAndRemoveRefreshToken(String refreshTokenCode) { return null; } - private Response processAuthorizationCode(String code, String scope, String codeVerifier, SessionId sessionIdObj, ExecutionContext executionContext) { + private Response processAuthorizationCode(String code, String scope, String codeVerifier, SessionId sessionIdObj, String redirectUri, ExecutionContext executionContext) { Client client = executionContext.getClient(); log.debug("Attempting to find authorizationCodeGrant by clientId: '{}', code: '{}'", client.getClientId(), code); @@ -442,6 +442,11 @@ private Response processAuthorizationCode(String code, String scope, String code executionContext.setGrant(authorizationCodeGrant); log.trace("AuthorizationCodeGrant : '{}'", authorizationCodeGrant); + // validate redirectUri only for Authorization Code Flow. For First-Party App redirect uri is blank. It is perfectly valid case. + if (!authorizationCodeGrant.isAuthorizationChallenge()) { + tokenRestWebServiceValidator.validateRedirectUri(redirectUri, executionContext.getAuditLog()); + } + // 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)); tokenRestWebServiceValidator.validatePKCE(authorizationCodeGrant, codeVerifier, executionContext.getAuditLog()); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidator.java b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidator.java index b498c42a64c..e87d36b368a 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidator.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidator.java @@ -82,7 +82,7 @@ public void validatePKCE(AuthorizationCodeGrant grant, String codeVerifier, OAut } public void validateParams(String grantType, String code, - String redirectUri, String refreshToken, OAuth2AuditLog auditLog) { + String refreshToken, OAuth2AuditLog auditLog) { log.debug("Starting to validate request parameters"); if (grantType == null || grantType.isEmpty()) { final String msg = "Grant Type is not set."; @@ -98,11 +98,6 @@ public void validateParams(String grantType, String code, log.trace(msg); throw new WebApplicationException(response(error(400, TokenErrorResponseType.INVALID_REQUEST, msg), auditLog)); } - if (StringUtils.isBlank(redirectUri)) { - final String msg = "redirect_uri is not set for AUTHORIZATION_CODE."; - log.trace(msg); - throw new WebApplicationException(response(error(400, TokenErrorResponseType.INVALID_REQUEST, msg), auditLog)); - } return; } @@ -173,6 +168,14 @@ public void validateGrant(AuthorizationGrant grant, Client client, Object identi validateGrant(grant, client, identifier, auditLog, null); } + public void validateRedirectUri(String redirectUri, OAuth2AuditLog auditLog) { + if (StringUtils.isBlank(redirectUri)) { + final String msg = "redirect_uri is not set for AUTHORIZATION_CODE."; + log.trace(msg); + throw new WebApplicationException(response(error(400, TokenErrorResponseType.INVALID_REQUEST, msg), auditLog)); + } + } + public void validateGrant(AuthorizationGrant grant, Client client, Object identifier, OAuth2AuditLog auditLog, Consumer onFailure) { if (grant == null) { diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidatorTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidatorTest.java index e9dadf62482..faf11a03f63 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidatorTest.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/token/ws/rs/TokenRestWebServiceValidatorTest.java @@ -172,7 +172,7 @@ public void validateActorTokenType_withValidTokenType_shouldPassSuccessfully() { @Test public void validateParams_whenGrantTypeIsBlank_shouldRaiseError() { try { - validator.validateParams("", "some_code", "https://my.redirect", "refresh_token", AUDIT_LOG); + validator.validateParams("", "some_code", "refresh_token", AUDIT_LOG); } catch (WebApplicationException e) { assertBadRequest(e.getResponse()); return; @@ -183,7 +183,7 @@ public void validateParams_whenGrantTypeIsBlank_shouldRaiseError() { @Test public void validateParams_whenGrantTypeIsAuthorizationCodeAndCodeIsBlank_shouldRaiseError() { try { - validator.validateParams(GrantType.AUTHORIZATION_CODE.getValue(), "", "https://my.redirect", "refresh_token", AUDIT_LOG); + validator.validateParams(GrantType.AUTHORIZATION_CODE.getValue(), "", "refresh_token", AUDIT_LOG); } catch (WebApplicationException e) { assertBadRequest(e.getResponse()); return; @@ -191,22 +191,10 @@ public void validateParams_whenGrantTypeIsAuthorizationCodeAndCodeIsBlank_should fail("No error for blank code for AUTHORIZATION_CODE grant type."); } - - @Test - public void validateParams_whenGrantTypeIsAuthorizationCodeAndRedirectUriIsBlank_shouldRaiseError() { - try { - validator.validateParams(GrantType.AUTHORIZATION_CODE.getValue(), "some_code", "", "refresh_token", AUDIT_LOG); - } catch (WebApplicationException e) { - assertBadRequest(e.getResponse()); - return; - } - fail("No error for blank redirect_uri for AUTHORIZATION_CODE grant type."); - } - @Test public void validateParams_whenGrantTypeIsRefreshTokenAndRefreshTokenIsBlank_shouldRaiseError() { try { - validator.validateParams(GrantType.REFRESH_TOKEN.getValue(), "some_code", "https://my.redirect", "", AUDIT_LOG); + validator.validateParams(GrantType.REFRESH_TOKEN.getValue(), "some_code", "", AUDIT_LOG); } catch (WebApplicationException e) { assertBadRequest(e.getResponse()); return; @@ -217,7 +205,7 @@ public void validateParams_whenGrantTypeIsRefreshTokenAndRefreshTokenIsBlank_sho @Test public void validateParams_whenGrantTypeIsAuthorizationCodeAndCodeIsNotBlank_shouldNotRaiseError() { try { - validator.validateParams(GrantType.AUTHORIZATION_CODE.getValue(), "some_code", "https://my.redirect", "", AUDIT_LOG); + validator.validateParams(GrantType.AUTHORIZATION_CODE.getValue(), "some_code", "", AUDIT_LOG); } catch (WebApplicationException e) { fail("Error occurs. We should not get it."); } @@ -226,7 +214,7 @@ public void validateParams_whenGrantTypeIsAuthorizationCodeAndCodeIsNotBlank_sho @Test public void validateParams_whenGrantTypeIsRefreshTokenAndRefreshTokenIsNotBlank_shouldNotRaiseError() { try { - validator.validateParams(GrantType.REFRESH_TOKEN.getValue(), "", "https://my.redirect", "refresh_token", AUDIT_LOG); + validator.validateParams(GrantType.REFRESH_TOKEN.getValue(), "", "refresh_token", AUDIT_LOG); } catch (WebApplicationException e) { fail("Error occurs. We should not get it."); } diff --git a/jans-core/service/src/main/java/io/jans/model/token/TokenAttributes.java b/jans-core/service/src/main/java/io/jans/model/token/TokenAttributes.java index c5a6961d7b2..621ec7851da 100644 --- a/jans-core/service/src/main/java/io/jans/model/token/TokenAttributes.java +++ b/jans-core/service/src/main/java/io/jans/model/token/TokenAttributes.java @@ -26,6 +26,8 @@ public class TokenAttributes implements Serializable { private String x5cs256; @JsonProperty("online_access") private boolean onlineAccess; + @JsonProperty("authorization_challenge") + private boolean authorizationChallenge; @JsonProperty("attributes") private Map attributes; @JsonProperty("dpopJkt") @@ -35,6 +37,15 @@ public class TokenAttributes implements Serializable { @JsonProperty("statusListIndex") private Integer statusListIndex; + public boolean isAuthorizationChallenge() { + return authorizationChallenge; + } + + public TokenAttributes setAuthorizationChallenge(boolean authorizationChallenge) { + this.authorizationChallenge = authorizationChallenge; + return this; + } + public Integer getStatusListIndex() { return statusListIndex; } @@ -92,6 +103,7 @@ public String toString() { "onlineAccess='" + onlineAccess + '\'' + "dpopJkt='" + dpopJkt + '\'' + "authorizationDetails='" + authorizationDetails + '\'' + + "authorizationChallenge='" + authorizationChallenge + '\'' + '}'; } }