diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index c147ee8..bbd90e1 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -20,7 +20,7 @@ jobs: uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 - name: Test and Assemble and ApiDiff with Gradle - run: ./gradlew assemble apiDiff check jacocoTestReport --continue --console=plain + run: ./gradlew assemble check jacocoTestReport --continue --console=plain - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 with: diff --git a/build.gradle b/build.gradle index 40928ee..2b31102 100644 --- a/build.gradle +++ b/build.gradle @@ -29,87 +29,79 @@ logger.lifecycle("Using version ${version} for ${name} group $group") import me.champeau.gradle.japicmp.JapicmpTask -project.afterEvaluate { - def versions = project.ext.testInJavaVersions - for (pluginJavaTestVersion in versions) { - def taskName = "testInJava-${pluginJavaTestVersion}" - tasks.register(taskName, Test) { - def versionToUse = taskName.split("-").getAt(1) as Integer - description = "Runs unit tests on Java version ${versionToUse}." - project.logger.quiet("Test will be running in ${versionToUse}") - group = 'verification' - javaLauncher.set(javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(versionToUse) - }) - shouldRunAfter(tasks.named('test')) - } - tasks.named('check') { - dependsOn(taskName) - } - } - - project.configure(project) { - def baselineVersion = project.ext.baselineCompareVersion - task('apiDiff', type: JapicmpTask, dependsOn: 'jar') { - oldClasspath.from(files(getBaselineJar(project, baselineVersion))) - newClasspath.from(files(jar.archiveFile)) - onlyModified = true - failOnModification = true - ignoreMissingClasses = true - htmlOutputFile = file("$buildDir/reports/apiDiff/apiDiff.html") - txtOutputFile = file("$buildDir/reports/apiDiff/apiDiff.txt") - doLast { - project.logger.quiet("Comparing against baseline version ${baselineVersion}") - } - } - } -} - -private static File getBaselineJar(Project project, String baselineVersion) { - // Use detached configuration: https://github.com/square/okhttp/blob/master/build.gradle#L270 - def group = project.group - try { - def baseline = "${project.group}:${project.name}:$baselineVersion" - project.group = 'virtual_group_for_japicmp' - def dependency = project.dependencies.create(baseline + "@jar") - return project.configurations.detachedConfiguration(dependency).files.find { - it.name == "${project.name}-${baselineVersion}.jar" - } - } finally { - project.group = group - } -} +//project.afterEvaluate { +// def versions = project.ext.testInJavaVersions +// for (pluginJavaTestVersion in versions) { +// def taskName = "testInJava-${pluginJavaTestVersion}" +// tasks.register(taskName, Test) { +// def versionToUse = taskName.split("-").getAt(1) as Integer +// description = "Runs unit tests on Java version ${versionToUse}." +// project.logger.quiet("Test will be running in ${versionToUse}") +// group = 'verification' +// javaLauncher.set(javaToolchains.launcherFor { +// languageVersion = JavaLanguageVersion.of(versionToUse) +// }) +// shouldRunAfter(tasks.named('test')) +// } +// tasks.named('check') { +// dependsOn(taskName) +// } +// } +// +// project.configure(project) { +// def baselineVersion = project.ext.baselineCompareVersion +// task('apiDiff', type: JapicmpTask, dependsOn: 'jar') { +// oldClasspath.from(files(getBaselineJar(project, baselineVersion))) +// newClasspath.from(files(jar.archiveFile)) +// onlyModified = true +// failOnModification = true +// ignoreMissingClasses = true +// htmlOutputFile = file("$buildDir/reports/apiDiff/apiDiff.html") +// txtOutputFile = file("$buildDir/reports/apiDiff/apiDiff.txt") +// doLast { +// project.logger.quiet("Comparing against baseline version ${baselineVersion}") +// } +// } +// } +//} +// +//private static File getBaselineJar(Project project, String baselineVersion) { +// // Use detached configuration: https://github.com/square/okhttp/blob/master/build.gradle#L270 +// def group = project.group +// try { +// def baseline = "${project.group}:${project.name}:$baselineVersion" +// project.group = 'virtual_group_for_japicmp' +// def dependency = project.dependencies.create(baseline + "@jar") +// return project.configurations.detachedConfiguration(dependency).files.find { +// it.name == "${project.name}-${baselineVersion}.jar" +// } +// } finally { +// project.group = group +// } +//} ext { baselineCompareVersion = '1.5.0' - testInJavaVersions = [8, 11, 17, 21] + testInJavaVersions = [17, 21] } jacocoTestReport { reports { - xml.enabled = true - html.enabled = true + xml.required = true + html.required = true } } java { toolchain { - languageVersion = JavaLanguageVersion.of(8) - } - // Needed because of broken gradle metadata, see https://github.com/google/guava/issues/6612#issuecomment-1614992368 - sourceSets.all { - configurations.getByName(runtimeClasspathConfigurationName) { - attributes.attribute(Attribute.of("org.gradle.jvm.environment", String), "standard-jvm") - } - configurations.getByName(compileClasspathConfigurationName) { - attributes.attribute(Attribute.of("org.gradle.jvm.environment", String), "standard-jvm") - } + languageVersion = JavaLanguageVersion.of(17) } } + compileJava { - sourceCompatibility '1.8' - targetCompatibility '1.8' + sourceCompatibility '17' + targetCompatibility '17' } test { @@ -121,21 +113,20 @@ test { } dependencies { - implementation 'javax.servlet:javax.servlet-api:3.1.0' + implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' implementation 'org.apache.commons:commons-lang3:3.20.0' - implementation 'com.google.guava:guava-annotations:r03' + implementation 'com.google.guava:guava:32.0.1-jre' implementation 'commons-codec:commons-codec:1.22.0' - api 'com.auth0:auth0:1.45.1' - api 'com.auth0:java-jwt:3.19.4' + api 'com.auth0:auth0:3.3.0' + api 'com.auth0:java-jwt:4.5.0' api 'com.auth0:jwks-rsa:0.23.0' - testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' - testImplementation 'org.hamcrest:java-hamcrest:2.0.0.0' - testImplementation 'org.hamcrest:hamcrest-core:1.3' - testImplementation 'org.mockito:mockito-core:2.28.2' + testImplementation 'org.hamcrest:hamcrest:2.2' + testImplementation 'org.mockito:mockito-core:4.11.0' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' - testImplementation 'org.springframework:spring-test:4.3.30.RELEASE' + testImplementation 'org.springframework:spring-test:6.0.14' + testImplementation 'org.springframework:spring-web:6.0.14' testImplementation 'com.squareup.okhttp3:okhttp:4.12.0' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c51cbf1..e1adfb4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/auth0/AuthenticationController.java b/src/main/java/com/auth0/AuthenticationController.java index 9fc2724..8a14c9a 100644 --- a/src/main/java/com/auth0/AuthenticationController.java +++ b/src/main/java/com/auth0/AuthenticationController.java @@ -1,14 +1,12 @@ package com.auth0; -import com.auth0.client.HttpOptions; import com.auth0.client.auth.AuthAPI; import com.auth0.jwk.JwkProvider; -import com.auth0.net.Telemetry; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.Validate; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; /** * Base Auth0 Authenticator class. @@ -33,13 +31,9 @@ RequestProcessor getRequestProcessor() { } /** - * Create a new {@link Builder} instance to configure the - * {@link AuthenticationController} response type and algorithm used on the - * verification. - * By default it will request response type 'code' and later perform the Code - * Exchange, but if the response type is changed to 'token' it will handle - * the Implicit Grant using the HS256 algorithm with the Client Secret as - * secret. + * Create a new {@link Builder} instance to configure the {@link AuthenticationController} response type and algorithm used on the verification. + * By default it will request response type 'code' and later perform the Code Exchange, but if the response type is changed to 'token' it will handle + * the Implicit Grant using the HS256 algorithm with the Client Secret as secret. * * @param domain the Auth0 domain * @param clientId the Auth0 application's client id @@ -85,7 +79,6 @@ public static class Builder { private boolean useLegacySameSiteCookie; private String organization; private String invitation; - private HttpOptions httpOptions; private String cookiePath; private DomainResolver domainResolver; @@ -150,21 +143,7 @@ public Builder withDomainResolver(DomainResolver domainResolver) { } /** - * Customize certain aspects of the underlying HTTP client networking library, - * such as timeouts and proxy configuration. - * - * @param httpOptions a non-null {@code HttpOptions} - * @return this same builder instance. - */ - public Builder withHttpOptions(HttpOptions httpOptions) { - Validate.notNull(httpOptions); - this.httpOptions = httpOptions; - return this; - } - - /** - * Specify that transient authentication-based cookies such as state and nonce - * are created with the specified + * Specify that transient authentication-based cookies such as state and nonce are created with the specified * {@code Path} cookie attribute. * * @param cookiePath the path to set on the cookie. @@ -177,12 +156,9 @@ public Builder withCookiePath(String cookiePath) { } /** - * Change the response type to request in the Authorization step. Default value - * is 'code'. + * Change the response type to request in the Authorization step. Default value is 'code'. * - * @param responseType the response type to request. Any combination of 'code', - * 'token' and 'id_token' but 'token id_token' is allowed, - * using a space as separator. + * @param responseType the response type to request. Any combination of 'code', 'token' and 'id_token' but 'token id_token' is allowed, using a space as separator. * @return this same builder instance. */ public Builder withResponseType(String responseType) { @@ -192,10 +168,8 @@ public Builder withResponseType(String responseType) { } /** - * Sets the Jwk Provider that will return the Public Key required to verify the - * token in case of Implicit Grant flows. - * This is required if the Auth0 Application is signing the tokens with the - * RS256 algorithm. + * Sets the Jwk Provider that will return the Public Key required to verify the token in case of Implicit Grant flows. + * This is required if the Auth0 Application is signing the tokens with the RS256 algorithm. * * @param jwkProvider a valid Jwk provider. * @return this same builder instance. @@ -207,8 +181,7 @@ public Builder withJwkProvider(JwkProvider jwkProvider) { } /** - * Sets the clock-skew or leeway value to use in the ID Token verification. The - * value must be in seconds. + * Sets the clock-skew or leeway value to use in the ID Token verification. The value must be in seconds. * Defaults to 60 seconds. * * @param clockSkew the clock-skew to use for ID Token verification, in seconds. @@ -221,8 +194,7 @@ public Builder withClockSkew(Integer clockSkew) { } /** - * Sets the allowable elapsed time in seconds since the last time user was - * authenticated. + * Sets the allowable elapsed time in seconds since the last time user was authenticated. * By default there is no limit. * * @param maxAge the max age of the authentication, in seconds. @@ -235,14 +207,10 @@ public Builder withAuthenticationMaxAge(Integer maxAge) { } /** - * Sets whether fallback cookies will be set for clients that do not support - * SameSite=None cookie attribute. - * The SameSite Cookie attribute will only be set to "None" if the reponseType - * includes "id_token". + * Sets whether fallback cookies will be set for clients that do not support SameSite=None cookie attribute. + * The SameSite Cookie attribute will only be set to "None" if the reponseType includes "id_token". * By default this is true. - * - * @param useLegacySameSiteCookie whether fallback auth-based cookies should be - * set. + * @param useLegacySameSiteCookie whether fallback auth-based cookies should be set. * @return this same builder instance. */ public Builder withLegacySameSiteCookie(boolean useLegacySameSiteCookie) { @@ -251,8 +219,7 @@ public Builder withLegacySameSiteCookie(boolean useLegacySameSiteCookie) { } /** - * Sets the organization query string parameter value used to login to an - * organization. + * Sets the organization query string parameter value used to login to an organization. * * @param organization The ID or name of the organization to log the user in to. * @return the builder instance. @@ -264,12 +231,10 @@ public Builder withOrganization(String organization) { } /** - * Sets the invitation query string parameter to join an organization. If using - * this, you must also specify the + * Sets the invitation query string parameter to join an organization. If using this, you must also specify the * organization using {@linkplain Builder#withOrganization(String)}. * - * @param invitation The ID of the invitation to accept. This is available on - * the URL that is provided when accepting an invitation. + * @param invitation The ID of the invitation to accept. This is available on the URL that is provided when accepting an invitation. * @return the builder instance. */ public Builder withInvitation(String invitation) { @@ -279,14 +244,10 @@ public Builder withInvitation(String invitation) { } /** - * Create a new {@link AuthenticationController} instance that will handle both - * Code Grant and Implicit Grant flows using either Code Exchange or Token - * Signature verification. + * Create a new {@link AuthenticationController} instance that will handle both Code Grant and Implicit Grant flows using either Code Exchange or Token Signature verification. * * @return a new instance of {@link AuthenticationController}. - * @throws UnsupportedOperationException if the Implicit Grant is chosen and the - * environment doesn't support UTF-8 - * encoding. + * @throws UnsupportedOperationException if the Implicit Grant is chosen and the environment doesn't support UTF-8 encoding. */ public AuthenticationController build() throws UnsupportedOperationException { validateDomainConfiguration(); @@ -298,7 +259,7 @@ public AuthenticationController build() throws UnsupportedOperationException { SignatureVerifier signatureVerifier = buildSignatureVerifier(); RequestProcessor processor = new RequestProcessor.Builder(domainProvider, responseType, clientId, - clientSecret, httpOptions, signatureVerifier) + clientSecret, signatureVerifier) .withClockSkew(clockSkew) .withAuthenticationMaxAge(authenticationMaxAge) .withLegacySameSiteCookie(useLegacySameSiteCookie) @@ -330,26 +291,18 @@ private SignatureVerifier buildSignatureVerifier() { } @VisibleForTesting - AuthAPI createAPIClient(String domain, String clientId, String clientSecret, HttpOptions httpOptions) { - if (httpOptions != null) { - return new AuthAPI(domain, clientId, clientSecret, httpOptions); - } - return new AuthAPI(domain, clientId, clientSecret); - } - - @VisibleForTesting - void setupTelemetry(AuthAPI client) { - if (client == null) - return; - Telemetry telemetry = new Telemetry("auth0-java-mvc-common", obtainPackageVersion()); - client.setTelemetry(telemetry); + IdTokenVerifier.Options createIdTokenVerificationOptions(String issuer, String audience, SignatureVerifier signatureVerifier) { + return new IdTokenVerifier.Options(issuer, audience, signatureVerifier); } - @VisibleForTesting - String obtainPackageVersion() { - // Value if taken from jar's manifest file. - // Call will return null on dev environment (outside of a jar) - return getClass().getPackage().getImplementationVersion(); + private String getIssuer(String domain) { + if (!domain.startsWith("http://") && !domain.startsWith("https://")) { + domain = "https://" + domain; + } + if (!domain.endsWith("/")) { + domain = domain + "/"; + } + return domain; } } @@ -360,7 +313,6 @@ String obtainPackageVersion() { * @param enabled whether to enable the HTTP logger or not. */ public void setLoggingEnabled(boolean enabled) { - // No longer requestProcessor.getClient()... (which was null) requestProcessor.setLoggingEnabled(enabled); } @@ -372,35 +324,23 @@ public void doNotSendTelemetry() { } /** - * Process a request to obtain a set of {@link Tokens} that represent successful - * authentication or authorization. + * Process a request to obtain a set of {@link Tokens} that represent successful authentication or authorization. * - * This method should be called when processing the callback request to your - * application. It will validate - * authentication-related request parameters, handle performing a Code Exchange - * request if using - * the "code" response type, and verify the integrity of the ID token (if - * present). + * This method should be called when processing the callback request to your application. It will validate + * authentication-related request parameters, handle performing a Code Exchange request if using + * the "code" response type, and verify the integrity of the ID token (if present). * - *

- * Important: When using this API, you must - * also use - * {@link AuthenticationController#buildAuthorizeUrl(HttpServletRequest, HttpServletResponse, String)} - * when building the {@link AuthorizeUrl} that the user will be redirected to to - * login. Failure to do so may result - * in a broken login experience for the user. - *

+ *

Important: When using this API, you must also use {@link AuthenticationController#buildAuthorizeUrl(HttpServletRequest, HttpServletResponse, String)} + * when building the {@link AuthorizeUrl} that the user will be redirected to to login. Failure to do so may result + * in a broken login experience for the user.

* - * @param request the received request to process. + * @param request the received request to process. * @param response the received response to process. * @return the Tokens obtained after the user authentication. - * @throws InvalidRequestException if the error is result of making an - * invalid authentication request. - * @throws IdentityVerificationException if an error occurred while verifying - * the request tokens. + * @throws InvalidRequestException if the error is result of making an invalid authentication request. + * @throws IdentityVerificationException if an error occurred while verifying the request tokens. */ - public Tokens handle(HttpServletRequest request, HttpServletResponse response) - throws IdentityVerificationException { + public Tokens handle(HttpServletRequest request, HttpServletResponse response) throws IdentityVerificationException { Validate.notNull(request, "request must not be null"); Validate.notNull(response, "response must not be null"); @@ -408,104 +348,18 @@ public Tokens handle(HttpServletRequest request, HttpServletResponse response) } /** - * Process a request to obtain a set of {@link Tokens} that represent successful - * authentication or authorization. - * - * This method should be called when processing the callback request to your - * application. It will validate - * authentication-related request parameters, handle performing a Code Exchange - * request if using - * the "code" response type, and verify the integrity of the ID token (if - * present). - * - *

- * Important: When using this API, you must - * also use the - * {@link AuthenticationController#buildAuthorizeUrl(HttpServletRequest, String)} - * when building the {@link AuthorizeUrl} that the user will be redirected to to - * login. Failure to do so may result - * in a broken login experience for the user. - *

- * - * @deprecated This method uses the {@link javax.servlet.http.HttpSession} for - * auth-based data, and is incompatible - * with clients that are using the "id_token" or "token" - * responseType with browsers that enforce SameSite cookie - * restrictions. This method will be removed in version 2.0.0. Use - * {@link AuthenticationController#handle(HttpServletRequest, HttpServletResponse)} - * instead. - * - * @param request the received request to process. - * @return the Tokens obtained after the user authentication. - * @throws InvalidRequestException if the error is result of making an - * invalid authentication request. - * @throws IdentityVerificationException if an error occurred while verifying - * the request tokens. - */ - @Deprecated - public Tokens handle(HttpServletRequest request) throws IdentityVerificationException { - Validate.notNull(request, "request must not be null"); - - return requestProcessor.process(request, null); - } - - /** - * Pre builds an Auth0 Authorize Url with the given redirect URI using a random - * state and a random nonce if applicable. - * - *

- * Important: When using this API, you must - * also obtain the tokens using the - * {@link AuthenticationController#handle(HttpServletRequest)} method. Failure - * to do so may result in a broken login - * experience for users. - *

- * - * @deprecated This method stores data in the - * {@link javax.servlet.http.HttpSession}, and is incompatible with - * clients - * that are using the "id_token" or "token" responseType with - * browsers that enforce SameSite cookie restrictions. - * This method will be removed in version 2.0.0. Use - * {@link AuthenticationController#buildAuthorizeUrl(HttpServletRequest, HttpServletResponse, String)} - * instead. - * - * @param request the caller request. Used to keep the session context. - * @param redirectUri the url to call back with the authentication result. - * @return the authorize url builder to continue any further parameter - * customization. - */ - @Deprecated - public AuthorizeUrl buildAuthorizeUrl(HttpServletRequest request, String redirectUri) { - Validate.notNull(request, "request must not be null"); - Validate.notNull(redirectUri, "redirectUri must not be null"); - - String state = StorageUtils.secureRandomString(); - String nonce = StorageUtils.secureRandomString(); - - return requestProcessor.buildAuthorizeUrl(request, null, redirectUri, state, nonce); - } - - /** - * Pre builds an Auth0 Authorize Url with the given redirect URI using a random - * state and a random nonce if applicable. + * Pre builds an Auth0 Authorize Url with the given redirect URI using a random state and a random nonce if applicable. * - *

- * Important: When using this API, you must - * also obtain the tokens using the - * {@link AuthenticationController#handle(HttpServletRequest, HttpServletResponse)} - * method. Failure to do so will result in a broken login - * experience for users. - *

+ *

Important: When using this API, you must also obtain the tokens using the + * {@link AuthenticationController#handle(HttpServletRequest, HttpServletResponse)} method. Failure to do so will result in a broken login + * experience for users.

* * @param request the HTTP request * @param response the HTTP response. Used to store auth-based cookies. * @param redirectUri the url to call back with the authentication result. - * @return the authorize url builder to continue any further parameter - * customization. + * @return the authorize url builder to continue any further parameter customization. */ - public AuthorizeUrl buildAuthorizeUrl(HttpServletRequest request, HttpServletResponse response, - String redirectUri) { + public AuthorizeUrl buildAuthorizeUrl(HttpServletRequest request, HttpServletResponse response, String redirectUri) { Validate.notNull(request, "request must not be null"); Validate.notNull(response, "response must not be null"); Validate.notNull(redirectUri, "redirectUri must not be null"); diff --git a/src/main/java/com/auth0/AuthorizeUrl.java b/src/main/java/com/auth0/AuthorizeUrl.java index 092b0fd..d0269ce 100644 --- a/src/main/java/com/auth0/AuthorizeUrl.java +++ b/src/main/java/com/auth0/AuthorizeUrl.java @@ -5,8 +5,7 @@ import com.auth0.exception.Auth0Exception; import com.auth0.json.auth.PushedAuthorizationResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import java.util.*; import static com.auth0.IdentityVerificationException.API_ERROR; @@ -21,7 +20,6 @@ public class AuthorizeUrl { private static final String SCOPE_OPENID = "openid"; private HttpServletResponse response; - private HttpServletRequest request; private final String responseType; private boolean useLegacySameSiteCookie = true; private boolean setSecureCookie = false; @@ -39,19 +37,15 @@ public class AuthorizeUrl { /** * Creates a new instance that can be used to build an Auth0 Authorization URL. * - * Using this constructor with a non-null {@link HttpServletResponse} will store the state and nonce as - * cookies when the {@link AuthorizeUrl#build()} method is called, with the appropriate SameSite attribute depending - * on the responseType. State and nonce will also be stored in the {@link javax.servlet.http.HttpSession} as a fallback, - * but this behavior will be removed in a future release, and only cookies will be used. + * Stores the state and nonce as cookies when the {@link AuthorizeUrl#build()} method is called, + * with the appropriate SameSite attribute depending on the responseType. * * @param client the Auth0 Authentication API client - * @parem request the HTTP request. Used to store state and nonce as a fallback if cookies not set. * @param response the response where the state and nonce will be stored as cookies * @param redirectUri the url to redirect to after authentication * @param responseType the response type to use */ - AuthorizeUrl(AuthAPI client, HttpServletRequest request, HttpServletResponse response, String redirectUri, String responseType) { - this.request = request; + AuthorizeUrl(AuthAPI client, HttpServletResponse response, String redirectUri, String responseType) { this.response = response; this.responseType = responseType; this.authAPI = client; @@ -113,7 +107,7 @@ public AuthorizeUrl withSecureCookie(boolean secureCookie) { /** * Sets whether a fallback cookie should be used for clients that do not support "SameSite=None". - * Only applicable when this instance is created with {@link AuthorizeUrl#AuthorizeUrl(AuthAPI, HttpServletRequest, HttpServletResponse, String, String)}. + * Only applicable when this instance is created with {@link AuthorizeUrl#AuthorizeUrl(AuthAPI, HttpServletResponse, String, String)}. * * @param useLegacySameSiteCookie whether or not to set fallback auth cookies for clients that do not support "SameSite=None" * @return the builder instance @@ -236,7 +230,7 @@ public String fromPushedAuthorizationRequest() throws InvalidRequestException { storeTransient(); try { - PushedAuthorizationResponse pushedAuthResponse = authAPI.pushedAuthorizationRequest(redirectUri, responseType, params).execute(); + PushedAuthorizationResponse pushedAuthResponse = authAPI.pushedAuthorizationRequest(redirectUri, responseType, params).execute().getBody(); String requestUri = pushedAuthResponse.getRequestURI(); if (requestUri == null || requestUri.isEmpty()) { throw new InvalidRequestException(API_ERROR, "The PAR request returned a missing or empty request_uri value"); @@ -255,24 +249,17 @@ private void storeTransient() { throw new IllegalStateException("The AuthorizeUrl instance must not be reused."); } - if (response != null) { - SameSite sameSiteValue = containsFormPost() ? SameSite.NONE : SameSite.LAX; + SameSite sameSiteValue = containsFormPost() ? SameSite.NONE : SameSite.LAX; - TransientCookieStore.storeState(response, state, sameSiteValue, useLegacySameSiteCookie, setSecureCookie, cookiePath); - TransientCookieStore.storeNonce(response, nonce, sameSiteValue, useLegacySameSiteCookie, setSecureCookie, cookiePath); + TransientCookieStore.storeState(response, state, sameSiteValue, useLegacySameSiteCookie, setSecureCookie, cookiePath); + TransientCookieStore.storeNonce(response, nonce, sameSiteValue, useLegacySameSiteCookie, setSecureCookie, cookiePath); - // Store HMAC-signed origin domain with the same SameSite value as state/nonce - if (originDomain != null && clientSecret != null) { - TransientCookieStore.storeSignedOriginDomain(response, originDomain, - sameSiteValue, cookiePath, setSecureCookie, clientSecret); - } + // Store HMAC-signed origin domain with the same SameSite value as state/nonce + if (originDomain != null && clientSecret != null) { + TransientCookieStore.storeSignedOriginDomain(response, originDomain, + sameSiteValue, cookiePath, setSecureCookie, clientSecret); } - // Also store in Session just in case developer uses deprecated - // AuthenticationController.handle(HttpServletRequest) API - RandomStorage.setSessionState(request, state); - RandomStorage.setSessionNonce(request, nonce); - used = true; } diff --git a/src/main/java/com/auth0/DomainProvider.java b/src/main/java/com/auth0/DomainProvider.java index 081a3e7..edd8330 100644 --- a/src/main/java/com/auth0/DomainProvider.java +++ b/src/main/java/com/auth0/DomainProvider.java @@ -1,6 +1,6 @@ package com.auth0; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; interface DomainProvider { String getDomain(HttpServletRequest request); diff --git a/src/main/java/com/auth0/DomainResolver.java b/src/main/java/com/auth0/DomainResolver.java index ea441e4..725e560 100644 --- a/src/main/java/com/auth0/DomainResolver.java +++ b/src/main/java/com/auth0/DomainResolver.java @@ -1,6 +1,6 @@ package com.auth0; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; public interface DomainResolver { /** diff --git a/src/main/java/com/auth0/InvalidRequestException.java b/src/main/java/com/auth0/InvalidRequestException.java index 037338f..03690ca 100644 --- a/src/main/java/com/auth0/InvalidRequestException.java +++ b/src/main/java/com/auth0/InvalidRequestException.java @@ -18,15 +18,4 @@ public class InvalidRequestException extends IdentityVerificationException { super(code, description != null ? description : DEFAULT_DESCRIPTION, cause); } - /** - * Getter for the description of the error. - * - * @return the error description if available, null otherwise. - * @deprecated use {@link #getMessage()} - */ - @Deprecated - public String getDescription() { - return getMessage(); - } - } diff --git a/src/main/java/com/auth0/RandomStorage.java b/src/main/java/com/auth0/RandomStorage.java index 66659a0..e69de29 100644 --- a/src/main/java/com/auth0/RandomStorage.java +++ b/src/main/java/com/auth0/RandomStorage.java @@ -1,52 +0,0 @@ -package com.auth0; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - -class RandomStorage extends SessionUtils { - - /** - * Check's if the request {@link HttpSession} saved state is equal to the given state. - * After the check, the value will be removed from the session. - * - * @param req the request - * @param state the state value to compare against. - * @return whether the state matches the expected one or not. - */ - static boolean checkSessionState(HttpServletRequest req, String state) { - String currentState = (String) remove(req, StorageUtils.STATE_KEY); - return (currentState == null && state == null) || currentState != null && currentState.equals(state); - } - - /** - * Saves the given state in the request {@link HttpSession}. - * If a state is already bound to the session, the value is replaced. - * - * @param req the request. - * @param state the state value to set. - */ - static void setSessionState(HttpServletRequest req, String state) { - set(req, StorageUtils.STATE_KEY, state); - } - - /** - * Saves the given nonce in the request {@link HttpSession}. - * If a nonce is already bound to the session, the value is replaced. - * - * @param req the request. - * @param nonce the nonce value to set. - */ - static void setSessionNonce(HttpServletRequest req, String nonce) { - set(req, StorageUtils.NONCE_KEY, nonce); - } - - /** - * Removes the nonce present in the request {@link HttpSession} and then returns it. - * - * @param req the HTTP Servlet request. - * @return the nonce value or null if it was not set. - */ - static String removeSessionNonce(HttpServletRequest req) { - return (String) remove(req, StorageUtils.NONCE_KEY); - } -} \ No newline at end of file diff --git a/src/main/java/com/auth0/RequestProcessor.java b/src/main/java/com/auth0/RequestProcessor.java index cc58e7c..0919da6 100644 --- a/src/main/java/com/auth0/RequestProcessor.java +++ b/src/main/java/com/auth0/RequestProcessor.java @@ -1,14 +1,14 @@ package com.auth0; -import com.auth0.client.HttpOptions; +import com.auth0.client.LoggingOptions; import com.auth0.client.auth.AuthAPI; import com.auth0.exception.Auth0Exception; import com.auth0.json.auth.TokenHolder; -import com.auth0.net.Telemetry; -import com.google.common.annotations.VisibleForTesting; +import com.auth0.net.client.DefaultHttpClient; +import org.apache.commons.lang3.Validate; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.Arrays; import java.util.List; @@ -16,8 +16,7 @@ /** * Main class to handle the Authorize Redirect request. - * It will try to parse the parameters looking for tokens or an authorization - * code to perform a Code Exchange against the Auth0 servers. + * It will try to parse the parameters looking for tokens or an authorization code to perform a Code Exchange against the Auth0 servers. */ class RequestProcessor { @@ -40,7 +39,6 @@ class RequestProcessor { private final String responseType; private final String clientId; private final String clientSecret; - private final HttpOptions httpOptions; private SignatureVerifier signatureVerifier; // Configuration values passed from Builder for creating per-request @@ -51,7 +49,6 @@ class RequestProcessor { private final String invitation; final boolean useLegacySameSiteCookie; - private AuthAPI client; private final IdTokenVerifier tokenVerifier; private final String cookiePath; private boolean loggingEnabled = false; @@ -62,7 +59,6 @@ static class Builder { private final String responseType; private final String clientId; private final String clientSecret; - private final HttpOptions httpOptions; private final SignatureVerifier signatureVerifier; private boolean useLegacySameSiteCookie = true; @@ -76,13 +72,11 @@ public Builder(DomainProvider domainProvider, String responseType, String clientId, String clientSecret, - HttpOptions httpOptions, SignatureVerifier signatureVerifier) { this.domainProvider = domainProvider; this.responseType = responseType; this.clientId = clientId; this.clientSecret = clientSecret; - this.httpOptions = httpOptions; this.signatureVerifier = signatureVerifier; } @@ -118,21 +112,19 @@ Builder withInvitation(String invitation) { RequestProcessor build() { - return new RequestProcessor(domainProvider, responseType, clientId, clientSecret, httpOptions, + return new RequestProcessor(domainProvider, responseType, clientId, clientSecret, signatureVerifier, new IdTokenVerifier(), useLegacySameSiteCookie, clockSkew, authenticationMaxAge, organization, invitation, cookiePath); } } - private RequestProcessor(DomainProvider domainProvider, String responseType, String clientId, String clientSecret, - HttpOptions httpOptions, SignatureVerifier signatureVerifier, IdTokenVerifier tokenVerifier, + private RequestProcessor(DomainProvider domainProvider, String responseType, String clientId, String clientSecret, SignatureVerifier signatureVerifier, IdTokenVerifier tokenVerifier, boolean useLegacySameSiteCookie, Integer clockSkew, Integer authenticationMaxAge, String organization, String invitation, String cookiePath) { this.domainProvider = domainProvider; this.responseType = responseType; this.clientId = clientId; this.clientSecret = clientSecret; - this.httpOptions = httpOptions; this.signatureVerifier = signatureVerifier; this.tokenVerifier = tokenVerifier; this.useLegacySameSiteCookie = useLegacySameSiteCookie; @@ -153,67 +145,36 @@ void doNotSendTelemetry() { this.telemetryDisabled = true; } - /** - * Getter for the AuthAPI client instance. - * Used to customize options such as Telemetry and Logging. - * - * @return the AuthAPI client. - */ - AuthAPI getClient() { - return client; - } - AuthAPI createClientForDomain(String domain) { - final AuthAPI client; + DefaultHttpClient.Builder httpBuilder = DefaultHttpClient.newBuilder() + .telemetryEnabled(!telemetryDisabled); - if (httpOptions != null) { - client = new AuthAPI(domain, clientId, clientSecret, httpOptions); - } else { - client = new AuthAPI(domain, clientId, clientSecret); + if (loggingEnabled) { + httpBuilder.withLogging(new LoggingOptions(LoggingOptions.LogLevel.BODY)); } - // Apply deferred settings - client.setLoggingEnabled(loggingEnabled); - if (telemetryDisabled) { - client.doNotSendTelemetry(); - } else { - setupTelemetry(client); - } - - return client; - } - - void setupTelemetry(AuthAPI client) { - Telemetry telemetry = new Telemetry("auth0-java-mvc-common", obtainPackageVersion()); - client.setTelemetry(telemetry); - } - - @VisibleForTesting - String obtainPackageVersion() { - return getClass().getPackage().getImplementationVersion(); + return AuthAPI.newBuilder(domain, clientId, clientSecret) + .withHttpClient(httpBuilder.build()) + .build(); } /** - * Pre builds an Auth0 Authorize Url with the given redirect URI, state and - * nonce parameters. + * Pre builds an Auth0 Authorize Url with the given redirect URI, state and nonce parameters. * - * @param request the request, used to store state and nonce in the Session - * @param response the response, used to set state and nonce as cookies. If - * null, session will be used instead. + * @param request the HTTP request. + * @param response the HTTP response, used to set state and nonce as cookies. * @param redirectUri the url to call with the authentication result. * @param state a valid state value. - * @param nonce the nonce value that will be used if the response type - * contains 'id_token'. Can be null. - * @return the authorize url builder to continue any further parameter - * customization. + * @param nonce the nonce value that will be used if the response type contains 'id_token'. Can be null. + * @return the authorize url builder to continue any further parameter customization. */ AuthorizeUrl buildAuthorizeUrl(HttpServletRequest request, HttpServletResponse response, String redirectUri, - String state, String nonce) { + String state, String nonce) { String originDomain = domainProvider.getDomain(request); AuthAPI client = createClientForDomain(originDomain); - AuthorizeUrl creator = new AuthorizeUrl(client, request, response, redirectUri, responseType) + AuthorizeUrl creator = new AuthorizeUrl(client, response, redirectUri, responseType) .withState(state); if (this.organization != null) { @@ -226,12 +187,8 @@ AuthorizeUrl buildAuthorizeUrl(HttpServletRequest request, HttpServletResponse r creator.withCookiePath(this.cookiePath); } - // null response means state and nonce will be stored in session, so legacy - // cookie flag does not apply and origin domain cookie cannot be set - if (response != null) { - creator.withLegacySameSiteCookie(useLegacySameSiteCookie); - creator.withOriginDomain(originDomain, clientSecret); - } + creator.withLegacySameSiteCookie(useLegacySameSiteCookie); + creator.withOriginDomain(originDomain, clientSecret); return getAuthorizeUrl(nonce, creator); } @@ -240,14 +197,12 @@ AuthorizeUrl buildAuthorizeUrl(HttpServletRequest request, HttpServletResponse r * Entrypoint for HTTP request *

* 1). Responsible for validating the request. - * 2). Exchanging the authorization code received with this HTTP request for - * Auth0 tokens. + * 2). Exchanging the authorization code received with this HTTP request for Auth0 tokens. * 3). Validating the ID Token. * 4). Clearing the stored state, nonce and max_age values. * 5). Handling success and any failure outcomes. * - * @throws IdentityVerificationException if an error occurred while processing - * the request + * @throws IdentityVerificationException if an error occurred while processing the request */ Tokens process(HttpServletRequest request, HttpServletResponse response) throws IdentityVerificationException { assertNoError(request); @@ -256,9 +211,9 @@ Tokens process(HttpServletRequest request, HttpServletResponse response) throws // Extract origin_domain from the HMAC-signed transaction state cookie. // If the cookie was tampered with, getSignedOriginDomain returns null. String originDomain = null; - if (response != null) { - originDomain = TransientCookieStore.getSignedOriginDomain(request, response, clientSecret); - } + + originDomain = TransientCookieStore.getSignedOriginDomain(request, response, clientSecret); + // Fallback for session-based (deprecated) flow or if cookie was not set if (originDomain == null) { @@ -288,36 +243,19 @@ static boolean requiresFormPostResponseMode(List responseType) { /** * Obtains code request tokens (if using Code flow) and validates the ID token. - * - * @param request the HTTP request - * @param response the HTTP response + * @param request the HTTP request * @param frontChannelTokens the tokens obtained from the front channel - * @param responseTypeList the reponse types - * @param originDomain the domain for this specific request - * @param originIssuer the issuer for this specific request - * @return a Tokens object that wraps the values obtained from the front-channel - * and/or the code request response. + * @param responseTypeList the reponse types + * @return a Tokens object that wraps the values obtained from the front-channel and/or the code request response. * @throws IdentityVerificationException */ - private Tokens getVerifiedTokens(HttpServletRequest request, HttpServletResponse response, - Tokens frontChannelTokens, - List responseTypeList, String originDomain, String originIssuer) + private Tokens getVerifiedTokens(HttpServletRequest request, HttpServletResponse response, Tokens frontChannelTokens, List responseTypeList, String originDomain, String originIssuer) throws IdentityVerificationException { String authorizationCode = request.getParameter(KEY_CODE); Tokens codeExchangeTokens = null; - // Get nonce for this specific request - String nonce; - if (response != null) { - nonce = TransientCookieStore.getNonce(request, response); - // Fallback to session if cookie was not set (deprecated API path) - if (nonce == null) { - nonce = RandomStorage.removeSessionNonce(request); - } - } else { - nonce = RandomStorage.removeSessionNonce(request); - } + String nonce = TransientCookieStore.getNonce(request, response); IdTokenVerifier.Options requestVerifyOptions = createRequestVerifyOptions(originIssuer, nonce); @@ -341,11 +279,9 @@ private Tokens getVerifiedTokens(HttpServletRequest request, HttpServletResponse } } } catch (TokenValidationException e) { - throw new IdentityVerificationException(JWT_VERIFICATION_ERROR, - "An error occurred while trying to verify the ID Token.", e); + throw new IdentityVerificationException(JWT_VERIFICATION_ERROR, "An error occurred while trying to verify the ID Token.", e); } catch (Auth0Exception e) { - throw new IdentityVerificationException(API_ERROR, - "An error occurred while exchanging the authorization code.", e); + throw new IdentityVerificationException(API_ERROR, "An error occurred while exchanging the authorization code.", e); } // Keep the front-channel ID Token and the code-exchange Access Token. return mergeTokens(frontChannelTokens, codeExchangeTokens); @@ -394,20 +330,16 @@ private AuthorizeUrl getAuthorizeUrl(String nonce, AuthorizeUrl creator) { } /** - * Extract the tokens from the request parameters, present when using the - * Implicit or Hybrid Grant. + * Extract the tokens from the request parameters, present when using the Implicit or Hybrid Grant. * - * @param request the request + * @param request the request * @param originDomain the domain that issued these tokens * @param originIssuer the issuer that issued these tokens - * @return a new instance of Tokens wrapping the values present in the request - * parameters. + * @return a new instance of Tokens wrapping the values present in the request parameters. */ private Tokens getFrontChannelTokens(HttpServletRequest request, String originDomain, String originIssuer) { - Long expiresIn = request.getParameter(KEY_EXPIRES_IN) == null ? null - : Long.parseLong(request.getParameter(KEY_EXPIRES_IN)); - return new Tokens(request.getParameter(KEY_ACCESS_TOKEN), request.getParameter(KEY_ID_TOKEN), null, - request.getParameter(KEY_TOKEN_TYPE), expiresIn, originDomain, originIssuer); + Long expiresIn = request.getParameter(KEY_EXPIRES_IN) == null ? null : Long.parseLong(request.getParameter(KEY_EXPIRES_IN)); + return new Tokens(request.getParameter(KEY_ACCESS_TOKEN), request.getParameter(KEY_ID_TOKEN), null, request.getParameter(KEY_TOKEN_TYPE), expiresIn, originDomain, originIssuer); } /** @@ -425,62 +357,28 @@ private void assertNoError(HttpServletRequest request) throws InvalidRequestExce } /** - * Checks whether the state received in the request parameters is the same as - * the one in the state cookie or session + * Checks whether the state received in the request parameters is the same as the one in the state cookie * for this request. * - * @param request the request - * @throws InvalidRequestException if the request contains a different state - * from the expected one + * @param request the request + * @param response the response, used to remove the state cookie + * @throws InvalidRequestException if the request contains a different state from the expected one */ - private void assertValidState(HttpServletRequest request, HttpServletResponse response) - throws InvalidRequestException { - // TODO in v2: - // - only store state/nonce in cookies, remove session storage - // - create specific exception classes for various state validation failures - // (missing from auth response, missing - // state cookie, mismatch) - + private void assertValidState(HttpServletRequest request, HttpServletResponse response) throws InvalidRequestException { String stateFromRequest = request.getParameter(KEY_STATE); if (stateFromRequest == null) { - throw new InvalidRequestException(INVALID_STATE_ERROR, - "The received state doesn't match the expected one. No state parameter was found on the authorization response."); - } - - // If response is null, check the Session. - // This can happen when the deprecated handle method that only takes the request - // parameter is called - if (response == null) { - checkSessionState(request, stateFromRequest); - return; + throw new InvalidRequestException(INVALID_STATE_ERROR, "The received state doesn't match the expected one. No state parameter was found on the authorization response."); } String cookieState = TransientCookieStore.getState(request, response); - // Just in case state was stored in Session by building auth URL with deprecated - // method, but then called the - // supported handle method with the request and response if (cookieState == null) { - if (SessionUtils.get(request, StorageUtils.STATE_KEY) == null) { - throw new InvalidRequestException(INVALID_STATE_ERROR, - "The received state doesn't match the expected one. No state cookie or state session attribute found. Check that you are using non-deprecated methods and that cookies are not being removed on the server."); - } - checkSessionState(request, stateFromRequest); - return; + throw new InvalidRequestException(INVALID_STATE_ERROR, "The received state doesn't match the expected one. No state cookie found. Check that cookies are not being removed on the server."); } if (!cookieState.equals(stateFromRequest)) { - throw new InvalidRequestException(INVALID_STATE_ERROR, - "The received state doesn't match the expected one."); - } - } - - private void checkSessionState(HttpServletRequest request, String stateFromRequest) throws InvalidRequestException { - boolean valid = RandomStorage.checkSessionState(request, stateFromRequest); - if (!valid) { - throw new InvalidRequestException(INVALID_STATE_ERROR, - "The received state doesn't match the expected one."); + throw new InvalidRequestException(INVALID_STATE_ERROR, "The received state doesn't match the expected one."); } } @@ -489,26 +387,23 @@ private void checkSessionState(HttpServletRequest request, String stateFromReque * * @param authorizationCode the code received on the login response. * @param redirectUri the redirect uri used on login request. - * @param originDomain the domain that issued these tokens. * @return a new instance of {@link Tokens} with the received credentials. * @throws Auth0Exception if the request to the Auth0 server failed. * @see AuthAPI#exchangeCode(String, String) */ - private Tokens exchangeCodeForTokens(String authorizationCode, String redirectUri, String originDomain) - throws Auth0Exception { + private Tokens exchangeCodeForTokens(String authorizationCode, String redirectUri, String originDomain) throws Auth0Exception { AuthAPI client = createClientForDomain(originDomain); TokenHolder holder = client .exchangeCode(authorizationCode, redirectUri) - .execute(); + .execute() + .getBody(); String originIssuer = constructIssuer(originDomain); - return new Tokens(holder.getAccessToken(), holder.getIdToken(), holder.getRefreshToken(), holder.getTokenType(), - holder.getExpiresIn(), originDomain, originIssuer); + return new Tokens(holder.getAccessToken(), holder.getIdToken(), holder.getRefreshToken(), holder.getTokenType(), holder.getExpiresIn(), originDomain, originIssuer); } /** * Used to keep the best version of each token. - * It will prioritize the ID Token received in the front-channel, and the Access - * Token received in the code exchange request. + * It will prioritize the ID Token received in the front-channel, and the Access Token received in the code exchange request. * * @param frontChannelTokens the front-channel obtained tokens. * @param codeExchangeTokens the code-exchange obtained tokens. @@ -535,8 +430,7 @@ private Tokens mergeTokens(Tokens frontChannelTokens, Tokens codeExchangeTokens) } // Prefer ID token from the front-channel - String idToken = frontChannelTokens.getIdToken() != null ? frontChannelTokens.getIdToken() - : codeExchangeTokens.getIdToken(); + String idToken = frontChannelTokens.getIdToken() != null ? frontChannelTokens.getIdToken() : codeExchangeTokens.getIdToken(); // Refresh token only available from the code exchange String refreshToken = codeExchangeTokens.getRefreshToken(); diff --git a/src/main/java/com/auth0/ResolverDomainProvider.java b/src/main/java/com/auth0/ResolverDomainProvider.java index e3ed73e..86dd9eb 100644 --- a/src/main/java/com/auth0/ResolverDomainProvider.java +++ b/src/main/java/com/auth0/ResolverDomainProvider.java @@ -1,6 +1,6 @@ package com.auth0; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; class ResolverDomainProvider implements DomainProvider { private final DomainResolver resolver; diff --git a/src/main/java/com/auth0/SessionUtils.java b/src/main/java/com/auth0/SessionUtils.java index a6906dc..e69de29 100644 --- a/src/main/java/com/auth0/SessionUtils.java +++ b/src/main/java/com/auth0/SessionUtils.java @@ -1,64 +0,0 @@ -package com.auth0; - -import org.apache.commons.lang3.Validate; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - -/** - * Helper class to handle easy session key-value storage. - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -public abstract class SessionUtils { - - /** - * Extracts the HttpSession from the given request. - * - * @param req a valid request to get the session from - * @return the session of the request - */ - protected static HttpSession getSession(HttpServletRequest req) { - return req.getSession(true); - } - - /** - * Set's the attribute value to the request session. - * - * @param req a valid request to get the session from - * @param name the name of the attribute - * @param value the value to set - */ - public static void set(HttpServletRequest req, String name, Object value) { - Validate.notNull(req); - Validate.notNull(name); - getSession(req).setAttribute(name, value); - } - - /** - * Get the attribute with the given name from the request session. - * - * @param req a valid request to get the session from - * @param name the name of the attribute - * @return the attribute stored in the session or null if it doesn't exists - */ - public static Object get(HttpServletRequest req, String name) { - Validate.notNull(req); - Validate.notNull(name); - return getSession(req).getAttribute(name); - } - - /** - * Same as {@link #get(HttpServletRequest, String)} but it also removes the value from the request session. - * - * @param req a valid request to get the session from - * @param name the name of the attribute - * @return the attribute stored in the session or null if it doesn't exists - */ - public static Object remove(HttpServletRequest req, String name) { - Validate.notNull(req); - Validate.notNull(name); - Object value = get(req, name); - getSession(req).removeAttribute(name); - return value; - } -} diff --git a/src/main/java/com/auth0/StaticDomainProvider.java b/src/main/java/com/auth0/StaticDomainProvider.java index c0421ca..1f806a3 100644 --- a/src/main/java/com/auth0/StaticDomainProvider.java +++ b/src/main/java/com/auth0/StaticDomainProvider.java @@ -1,6 +1,6 @@ package com.auth0; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; class StaticDomainProvider implements DomainProvider { private final String domain; diff --git a/src/main/java/com/auth0/TransientCookieStore.java b/src/main/java/com/auth0/TransientCookieStore.java index 8ede8f3..7b45ab9 100644 --- a/src/main/java/com/auth0/TransientCookieStore.java +++ b/src/main/java/com/auth0/TransientCookieStore.java @@ -2,9 +2,9 @@ import org.apache.commons.lang3.Validate; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; diff --git a/src/test/java/com/auth0/AuthenticationControllerTest.java b/src/test/java/com/auth0/AuthenticationControllerTest.java index 32239d8..4fc78ea 100644 --- a/src/test/java/com/auth0/AuthenticationControllerTest.java +++ b/src/test/java/com/auth0/AuthenticationControllerTest.java @@ -1,6 +1,5 @@ package com.auth0; -import com.auth0.client.HttpOptions; import com.auth0.jwk.JwkProvider; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -9,8 +8,8 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; @@ -30,8 +29,6 @@ public class AuthenticationControllerTest { @Mock private JwkProvider mockJwkProvider; @Mock - private HttpOptions mockHttpOptions; - @Mock private DomainResolver mockDomainResolver; @Mock private Tokens mockTokens; @@ -41,7 +38,7 @@ public class AuthenticationControllerTest { @BeforeEach public void setUp() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); request = new MockHttpServletRequest(); response = new MockHttpServletResponse(); } @@ -90,7 +87,6 @@ public void shouldConfigureBuilderWithAllOptions() { .withLegacySameSiteCookie(false) .withOrganization("org_123") .withInvitation("inv_456") - .withHttpOptions(mockHttpOptions) .withCookiePath("/custom") .build(); @@ -139,7 +135,6 @@ public void shouldValidateNullParameters() { assertThrows(NullPointerException.class, () -> builder.withAuthenticationMaxAge(null)); assertThrows(NullPointerException.class, () -> builder.withOrganization(null)); assertThrows(NullPointerException.class, () -> builder.withInvitation(null)); - assertThrows(NullPointerException.class, () -> builder.withHttpOptions(null)); assertThrows(NullPointerException.class, () -> builder.withCookiePath(null)); } @@ -326,15 +321,6 @@ public void shouldBuildWithDomainResolver() { assertThat(controller, is(notNullValue())); } - @Test - public void shouldBuildWithCustomHttpOptions() { - AuthenticationController controller = AuthenticationController.newBuilder(DOMAIN, CLIENT_ID, CLIENT_SECRET) - .withHttpOptions(mockHttpOptions) - .build(); - - assertThat(controller, is(notNullValue())); - } - @Test public void shouldBuildWithOrganizationAndInvitation() { AuthenticationController controller = AuthenticationController.newBuilder(DOMAIN, CLIENT_ID, CLIENT_SECRET) diff --git a/src/test/java/com/auth0/AuthorizeUrlTest.java b/src/test/java/com/auth0/AuthorizeUrlTest.java index 5818265..d8058ec 100644 --- a/src/test/java/com/auth0/AuthorizeUrlTest.java +++ b/src/test/java/com/auth0/AuthorizeUrlTest.java @@ -1,23 +1,21 @@ package com.auth0; -import com.auth0.client.HttpOptions; import com.auth0.client.auth.AuthAPI; import com.auth0.exception.Auth0Exception; import com.auth0.json.auth.PushedAuthorizationResponse; import com.auth0.net.Request; +import com.auth0.net.Response; import okhttp3.HttpUrl; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import java.util.Collection; -import java.util.Map; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.matchesPattern; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.*; @@ -28,18 +26,16 @@ public class AuthorizeUrlTest { private AuthAPI client; private HttpServletResponse response; - private HttpServletRequest request; @BeforeEach public void setUp() { - client = new AuthAPI("domain.auth0.com", "clientId", "clientSecret"); - request = new MockHttpServletRequest(); + client = AuthAPI.newBuilder("domain.auth0.com", "clientId", "clientSecret").build(); response = new MockHttpServletResponse(); } @Test public void shouldBuildValidStringUrl() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .build(); assertThat(url, is(notNullValue())); assertThat(HttpUrl.parse(url), is(notNullValue())); @@ -47,28 +43,28 @@ public void shouldBuildValidStringUrl() { @Test public void shouldSetDefaultScope() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .build(); assertThat(HttpUrl.parse(url).queryParameter("scope"), is("openid")); } @Test public void shouldSetResponseType() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .build(); assertThat(HttpUrl.parse(url).queryParameter("response_type"), is("id_token token")); } @Test public void shouldSetRedirectUrl() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .build(); assertThat(HttpUrl.parse(url).queryParameter("redirect_uri"), is("https://redirect.to/me")); } @Test public void shouldSetConnection() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .withConnection("facebook") .build(); assertThat(HttpUrl.parse(url).queryParameter("connection"), is("facebook")); @@ -76,7 +72,7 @@ public void shouldSetConnection() { @Test public void shouldSetAudience() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .withAudience("https://api.auth0.com/") .build(); assertThat(HttpUrl.parse(url).queryParameter("audience"), is("https://api.auth0.com/")); @@ -84,20 +80,20 @@ public void shouldSetAudience() { @Test public void shouldSetNonceSameSiteAndLegacyCookieByDefault() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .withNonce("asdfghjkl") .build(); assertThat(HttpUrl.parse(url).queryParameter("nonce"), is("asdfghjkl")); Collection headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(2)); - assertThat(headers, hasItem("com.auth0.nonce=asdfghjkl; HttpOnly; Max-Age=600; SameSite=None; Secure")); - assertThat(headers, hasItem("_com.auth0.nonce=asdfghjkl; HttpOnly; Max-Age=600")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.nonce=asdfghjkl; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=None"))); + assertThat(headers, hasItem(matchesPattern("_com\\.auth0\\.nonce=asdfghjkl; Max-Age=600; Expires=.*?; HttpOnly"))); } @Test public void shouldSetNonceSameSiteAndNotLegacyCookieWhenConfigured() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .withNonce("asdfghjkl") .withLegacySameSiteCookie(false) .build(); @@ -105,25 +101,25 @@ public void shouldSetNonceSameSiteAndNotLegacyCookieWhenConfigured() { Collection headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(1)); - assertThat(headers, hasItem("com.auth0.nonce=asdfghjkl; HttpOnly; Max-Age=600; SameSite=None; Secure")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.nonce=asdfghjkl; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=None"))); } @Test public void shouldSetStateSameSiteAndLegacyCookieByDefault() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .withState("asdfghjkl") .build(); assertThat(HttpUrl.parse(url).queryParameter("state"), is("asdfghjkl")); Collection headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(2)); - assertThat(headers, hasItem("com.auth0.state=asdfghjkl; HttpOnly; Max-Age=600; SameSite=None; Secure")); - assertThat(headers, hasItem("_com.auth0.state=asdfghjkl; HttpOnly; Max-Age=600")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.state=asdfghjkl; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=None"))); + assertThat(headers, hasItem(matchesPattern("_com\\.auth0\\.state=asdfghjkl; Max-Age=600; Expires=.*?; HttpOnly"))); } @Test public void shouldSetStateSameSiteAndNotLegacyCookieWhenConfigured() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .withState("asdfghjkl") .withLegacySameSiteCookie(false) .build(); @@ -131,12 +127,12 @@ public void shouldSetStateSameSiteAndNotLegacyCookieWhenConfigured() { Collection headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(1)); - assertThat(headers, hasItem("com.auth0.state=asdfghjkl; HttpOnly; Max-Age=600; SameSite=None; Secure")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.state=asdfghjkl; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=None"))); } @Test public void shouldSetSecureCookieWhenConfiguredTrue() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "code") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "code") .withState("asdfghjkl") .withSecureCookie(true) .build(); @@ -144,12 +140,12 @@ public void shouldSetSecureCookieWhenConfiguredTrue() { Collection headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(1)); - assertThat(headers, hasItem("com.auth0.state=asdfghjkl; HttpOnly; Max-Age=600; SameSite=Lax; Secure")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.state=asdfghjkl; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=Lax"))); } @Test public void shouldSetSecureCookieWhenConfiguredFalseAndSameSiteNone() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token") .withState("asdfghjkl") .withSecureCookie(false) .build(); @@ -157,24 +153,13 @@ public void shouldSetSecureCookieWhenConfiguredFalseAndSameSiteNone() { Collection headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(2)); - assertThat(headers, hasItem("com.auth0.state=asdfghjkl; HttpOnly; Max-Age=600; SameSite=None; Secure")); - assertThat(headers, hasItem("_com.auth0.state=asdfghjkl; HttpOnly; Max-Age=600")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.state=asdfghjkl; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=None"))); + assertThat(headers, hasItem(matchesPattern("_com\\.auth0\\.state=asdfghjkl; Max-Age=600; Expires=.*?; HttpOnly"))); } @Test public void shouldSetNoCookiesWhenNonceAndStateNotSet() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") - .build(); - assertThat(HttpUrl.parse(url).queryParameter("state"), nullValue()); - assertThat(HttpUrl.parse(url).queryParameter("nonce"), nullValue()); - - Collection headers = response.getHeaders("Set-Cookie"); - assertThat(headers.size(), is(0)); - } - - @Test - public void shouldSetNoSessionValuesWhenNonceAndStateNotSet() { - String url = new AuthorizeUrl(client, request, null, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .build(); assertThat(HttpUrl.parse(url).queryParameter("state"), nullValue()); assertThat(HttpUrl.parse(url).queryParameter("nonce"), nullValue()); @@ -185,7 +170,7 @@ public void shouldSetNoSessionValuesWhenNonceAndStateNotSet() { @Test public void shouldSetScope() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .withScope("openid profile email") .build(); assertThat(HttpUrl.parse(url).queryParameter("scope"), is("openid profile email")); @@ -193,7 +178,7 @@ public void shouldSetScope() { @Test public void shouldSetCustomParameterScope() { - String url = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + String url = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .withParameter("custom", "value") .build(); assertThat(HttpUrl.parse(url).queryParameter("custom"), is("value")); @@ -201,7 +186,7 @@ public void shouldSetCustomParameterScope() { @Test public void shouldThrowWhenReusingTheInstance() { - AuthorizeUrl builder = new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token"); + AuthorizeUrl builder = new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token"); String firstCall = builder.build(); assertThat(firstCall, is(notNullValue())); IllegalStateException e = assertThrows(IllegalStateException.class, builder::build); @@ -212,7 +197,7 @@ public void shouldThrowWhenReusingTheInstance() { public void shouldThrowWhenChangingTheRedirectURI() { IllegalArgumentException e = assertThrows( IllegalArgumentException.class, - () -> new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + () -> new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .withParameter("redirect_uri", "new_value")); assertEquals("Redirect URI cannot be changed once set.", e.getMessage()); } @@ -221,7 +206,7 @@ public void shouldThrowWhenChangingTheRedirectURI() { public void shouldThrowWhenChangingTheResponseType() { IllegalArgumentException e = assertThrows( IllegalArgumentException.class, - () -> new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + () -> new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .withParameter("response_type", "new_value")); assertEquals("Response type cannot be changed once set.", e.getMessage()); } @@ -230,7 +215,7 @@ public void shouldThrowWhenChangingTheResponseType() { public void shouldThrowWhenChangingTheStateUsingCustomParameterSetter() { IllegalArgumentException e = assertThrows( IllegalArgumentException.class, - () -> new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + () -> new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .withParameter("state", "new_value")); assertEquals("Please, use the dedicated methods for setting the 'nonce' and 'state' parameters.", e.getMessage()); } @@ -239,20 +224,26 @@ public void shouldThrowWhenChangingTheStateUsingCustomParameterSetter() { public void shouldThrowWhenChangingTheNonceUsingCustomParameterSetter() { IllegalArgumentException e = assertThrows( IllegalArgumentException.class, - () -> new AuthorizeUrl(client, request, response, "https://redirect.to/me", "id_token token") + () -> new AuthorizeUrl(client, response, "https://redirect.to/me", "id_token token") .withParameter("nonce", "new_value")); assertEquals("Please, use the dedicated methods for setting the 'nonce' and 'state' parameters.", e.getMessage()); } @Test public void shouldGetAuthorizeUrlFromPAR() throws Exception { - AuthAPIStub authAPIStub = new AuthAPIStub("https://domain.com", "clientId", "clientSecret"); + AuthAPI authAPIMock = mock(AuthAPI.class); Request requestMock = mock(Request.class); - when(requestMock.execute()).thenReturn(new PushedAuthorizationResponse("urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2", 90)); + Response pushedAuthorizationResponseResponse = mock(Response.class); + when(requestMock.execute()).thenReturn(pushedAuthorizationResponseResponse); + when(requestMock.execute().getBody()).thenReturn(new PushedAuthorizationResponse("urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2", 90)); - authAPIStub.pushedAuthorizationResponseRequest = requestMock; - String url = new AuthorizeUrl(authAPIStub, request, response, "https://domain.com/callback", "code") + when(authAPIMock.pushedAuthorizationRequest(eq("https://domain.com/callback"), eq("code"), anyMap())) + .thenReturn(requestMock); + when(authAPIMock.authorizeUrlWithPAR("urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2")) + .thenReturn("https://domain.com/authorize?client_id=clientId&request_uri=urn%3Aexample%3Abwc4JK-ESC0w8acc191e-Y1LTC2"); + + String url = new AuthorizeUrl(authAPIMock, response, "https://domain.com/callback", "code") .fromPushedAuthorizationRequest(); assertThat(url, is("https://domain.com/authorize?client_id=clientId&request_uri=urn%3Aexample%3Abwc4JK-ESC0w8acc191e-Y1LTC2")); @@ -260,14 +251,17 @@ public void shouldGetAuthorizeUrlFromPAR() throws Exception { @Test public void fromPushedAuthorizationRequestThrowsWhenRequestUriIsNull() throws Exception { - AuthAPIStub authAPIStub = new AuthAPIStub("https://domain.com", "clientId", "clientSecret"); + AuthAPI authAPIMock = mock(AuthAPI.class); Request requestMock = mock(Request.class); - when(requestMock.execute()).thenReturn(new PushedAuthorizationResponse(null, 90)); + Response pushedAuthorizationResponseResponse = mock(Response.class); + when(requestMock.execute()).thenReturn(pushedAuthorizationResponseResponse); + when(requestMock.execute().getBody()).thenReturn(new PushedAuthorizationResponse(null, 90)); - authAPIStub.pushedAuthorizationResponseRequest = requestMock; + when(authAPIMock.pushedAuthorizationRequest(eq("https://domain.com/callback"), eq("code"), anyMap())) + .thenReturn(requestMock); InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> { - new AuthorizeUrl(authAPIStub, request, response, "https://domain.com/callback", "code") + new AuthorizeUrl(authAPIMock, response, "https://domain.com/callback", "code") .fromPushedAuthorizationRequest(); }); @@ -276,14 +270,17 @@ public void fromPushedAuthorizationRequestThrowsWhenRequestUriIsNull() throws Ex @Test public void fromPushedAuthorizationRequestThrowsWhenRequestUriIsEmpty() throws Exception { - AuthAPIStub authAPIStub = new AuthAPIStub("https://domain.com", "clientId", "clientSecret"); + AuthAPI authAPIMock = mock(AuthAPI.class); Request requestMock = mock(Request.class); - when(requestMock.execute()).thenReturn(new PushedAuthorizationResponse("urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2", null)); + Response pushedAuthorizationResponseResponse = mock(Response.class); + when(requestMock.execute()).thenReturn(pushedAuthorizationResponseResponse); + when(requestMock.execute().getBody()).thenReturn(new PushedAuthorizationResponse("urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2", null)); - authAPIStub.pushedAuthorizationResponseRequest = requestMock; + when(authAPIMock.pushedAuthorizationRequest(eq("https://domain.com/callback"), eq("code"), anyMap())) + .thenReturn(requestMock); InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> { - new AuthorizeUrl(authAPIStub, request, response, "https://domain.com/callback", "code") + new AuthorizeUrl(authAPIMock, response, "https://domain.com/callback", "code") .fromPushedAuthorizationRequest(); }); @@ -292,14 +289,17 @@ public void fromPushedAuthorizationRequestThrowsWhenRequestUriIsEmpty() throws E @Test public void fromPushedAuthorizationRequestThrowsWhenExpiresInIsNull() throws Exception { - AuthAPIStub authAPIStub = new AuthAPIStub("https://domain.com", "clientId", "clientSecret"); + AuthAPI authAPIMock = mock(AuthAPI.class); Request requestMock = mock(Request.class); - when(requestMock.execute()).thenReturn(new PushedAuthorizationResponse(null, 90)); + Response pushedAuthorizationResponseResponse = mock(Response.class); + when(requestMock.execute()).thenReturn(pushedAuthorizationResponseResponse); + when(requestMock.execute().getBody()).thenReturn(new PushedAuthorizationResponse(null, 90)); - authAPIStub.pushedAuthorizationResponseRequest = requestMock; + when(authAPIMock.pushedAuthorizationRequest(eq("https://domain.com/callback"), eq("code"), anyMap())) + .thenReturn(requestMock); InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> { - new AuthorizeUrl(authAPIStub, request, response, "https://domain.com/callback", "code") + new AuthorizeUrl(authAPIMock, response, "https://domain.com/callback", "code") .fromPushedAuthorizationRequest(); }); @@ -317,7 +317,7 @@ public void fromPushedAuthorizationRequestThrowsWhenRequestThrows() throws Excep .thenReturn(requestMock); InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> { - new AuthorizeUrl(authAPIMock, request, response, "https://domain.com/callback", "code") + new AuthorizeUrl(authAPIMock, response, "https://domain.com/callback", "code") .fromPushedAuthorizationRequest(); }); @@ -325,21 +325,4 @@ public void fromPushedAuthorizationRequestThrowsWhenRequestThrows() throws Excep assertThat(exception.getCause(), instanceOf(Auth0Exception.class)); } - static class AuthAPIStub extends AuthAPI { - - Request pushedAuthorizationResponseRequest; - - public AuthAPIStub(String domain, String clientId, String clientSecret, HttpOptions options) { - super(domain, clientId, clientSecret, options); - } - - public AuthAPIStub(String domain, String clientId, String clientSecret) { - super(domain, clientId, clientSecret); - } - - @Override - public Request pushedAuthorizationRequest(String redirectUri, String responseType, Map params) { - return pushedAuthorizationResponseRequest; - } - } } diff --git a/src/test/java/com/auth0/InvalidRequestExceptionTest.java b/src/test/java/com/auth0/InvalidRequestExceptionTest.java index e513d58..848c01a 100644 --- a/src/test/java/com/auth0/InvalidRequestExceptionTest.java +++ b/src/test/java/com/auth0/InvalidRequestExceptionTest.java @@ -15,10 +15,9 @@ public void setUp() { exception = new InvalidRequestException("error", "message"); } - @SuppressWarnings("deprecation") @Test - public void shouldGetDescription() { - assertThat(exception.getDescription(), is("message")); + public void shouldGetMessage() { + assertThat(exception.getMessage(), is("message")); } @Test diff --git a/src/test/java/com/auth0/RandomStorageTest.java b/src/test/java/com/auth0/RandomStorageTest.java deleted file mode 100644 index 49a4af7..0000000 --- a/src/test/java/com/auth0/RandomStorageTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.auth0; - -import org.junit.jupiter.api.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; - -public class RandomStorageTest { - - @Test - public void shouldSetState() { - MockHttpServletRequest req = new MockHttpServletRequest(); - - RandomStorage.setSessionState(req, "123456"); - assertThat(req.getSession().getAttribute("com.auth0.state"), is("123456")); - } - - @Test - public void shouldAcceptBothNullStates() { - MockHttpServletRequest req = new MockHttpServletRequest(); - boolean validState = RandomStorage.checkSessionState(req, null); - assertThat(validState, is(true)); - } - - @Test - public void shouldFailIfSessionStateIsNullButCurrentStateNotNull() { - MockHttpServletRequest req = new MockHttpServletRequest(); - boolean validState = RandomStorage.checkSessionState(req, "12345"); - assertThat(validState, is(false)); - } - - @Test - public void shouldCheckAndRemoveInvalidState() { - MockHttpServletRequest req = new MockHttpServletRequest(); - req.getSession().setAttribute("com.auth0.state", "123456"); - - boolean validState = RandomStorage.checkSessionState(req, "abcdef"); - assertThat(validState, is(false)); - assertThat(req.getSession().getAttribute("com.auth0.state"), is(nullValue())); - } - - @Test - public void shouldCheckAndRemoveCorrectState() { - MockHttpServletRequest req = new MockHttpServletRequest(); - req.getSession().setAttribute("com.auth0.state", "123456"); - - boolean validState = RandomStorage.checkSessionState(req, "123456"); - assertThat(validState, is(true)); - assertThat(req.getSession().getAttribute("com.auth0.state"), is(nullValue())); - } - - @Test - public void shouldSetNonce() { - MockHttpServletRequest req = new MockHttpServletRequest(); - - RandomStorage.setSessionNonce(req, "123456"); - assertThat(req.getSession().getAttribute("com.auth0.nonce"), is("123456")); - } - - @Test - public void shouldGetAndRemoveNonce() { - MockHttpServletRequest req = new MockHttpServletRequest(); - req.getSession().setAttribute("com.auth0.nonce", "123456"); - - String nonce = RandomStorage.removeSessionNonce(req); - assertThat(nonce, is("123456")); - assertThat(req.getSession().getAttribute("com.auth0.nonce"), is(nullValue())); - } - - @Test - public void shouldGetAndRemoveNonceIfMissing() { - MockHttpServletRequest req = new MockHttpServletRequest(); - - String nonce = RandomStorage.removeSessionNonce(req); - assertThat(nonce, is(nullValue())); - assertThat(req.getSession().getAttribute("com.auth0.nonce"), is(nullValue())); - } -} diff --git a/src/test/java/com/auth0/RequestProcessorTest.java b/src/test/java/com/auth0/RequestProcessorTest.java index 7205b37..c719dea 100644 --- a/src/test/java/com/auth0/RequestProcessorTest.java +++ b/src/test/java/com/auth0/RequestProcessorTest.java @@ -1,23 +1,26 @@ package com.auth0; -import com.auth0.client.HttpOptions; import com.auth0.client.auth.AuthAPI; +import com.auth0.exception.Auth0Exception; import com.auth0.json.auth.TokenHolder; +import com.auth0.net.Response; import com.auth0.net.TokenRequest; -import com.auth0.net.Telemetry; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; @@ -40,27 +43,20 @@ public class RequestProcessorTest { @Mock private SignatureVerifier mockSignatureVerifier; @Mock - private IdTokenVerifier mockIdTokenVerifier; - @Mock - private HttpOptions mockHttpOptions; - @Mock private AuthAPI mockAuthAPI; @Mock private TokenRequest mockTokenRequest; @Mock + private Response mockTokenResponse; + @Mock private TokenHolder mockTokenHolder; - @Captor - private ArgumentCaptor stringCaptor; - @Captor - private ArgumentCaptor verifyOptionsCaptor; - private MockHttpServletRequest request; private MockHttpServletResponse response; @BeforeEach public void setUp() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); request = new MockHttpServletRequest(); response = new MockHttpServletResponse(); request.setSecure(true); @@ -75,7 +71,6 @@ public void shouldBuildRequestProcessorWithRequiredParameters() { RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .build(); @@ -89,7 +84,6 @@ public void shouldBuildRequestProcessorWithAllOptionalParameters() { RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .withClockSkew(120) .withAuthenticationMaxAge(3600) @@ -102,6 +96,8 @@ public void shouldBuildRequestProcessorWithAllOptionalParameters() { assertThat(processor, is(notNullValue())); } + // --- Legacy SameSite Cookie Tests --- + @Test public void shouldSetDefaultLegacySameSiteCookieToTrue() { RequestProcessor processor = createDefaultRequestProcessor(); @@ -116,7 +112,6 @@ public void shouldDisableLegacySameSiteCookie() { RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .withLegacySameSiteCookie(false) .build(); @@ -142,32 +137,8 @@ public void shouldGetDomainFromProvider() { } @Test - public void shouldCreateClientForDomainWithHttpOptions() { - HttpOptions httpOptions = new HttpOptions(); - RequestProcessor processor = new RequestProcessor.Builder( - mockDomainProvider, - RESPONSE_TYPE_CODE, - CLIENT_ID, - CLIENT_SECRET, - httpOptions, - mockSignatureVerifier) - .build(); - - AuthAPI result = processor.createClientForDomain(DOMAIN); - - assertThat(result, is(notNullValue())); - } - - @Test - public void shouldCreateClientForDomainWithoutHttpOptions() { - RequestProcessor processor = new RequestProcessor.Builder( - mockDomainProvider, - RESPONSE_TYPE_CODE, - CLIENT_ID, - CLIENT_SECRET, - null, - mockSignatureVerifier) - .build(); + public void shouldCreateClientForDomain() { + RequestProcessor processor = createDefaultRequestProcessor(); AuthAPI result = processor.createClientForDomain(DOMAIN); @@ -178,14 +149,7 @@ public void shouldCreateClientForDomainWithoutHttpOptions() { @Test public void shouldSetLoggingEnabled() { - RequestProcessor processor = new RequestProcessor.Builder( - mockDomainProvider, - RESPONSE_TYPE_CODE, - CLIENT_ID, - CLIENT_SECRET, - null, - mockSignatureVerifier) - .build(); + RequestProcessor processor = createDefaultRequestProcessor(); processor.setLoggingEnabled(true); @@ -195,14 +159,7 @@ public void shouldSetLoggingEnabled() { @Test public void shouldDisableTelemetry() { - RequestProcessor processor = new RequestProcessor.Builder( - mockDomainProvider, - RESPONSE_TYPE_CODE, - CLIENT_ID, - CLIENT_SECRET, - null, - mockSignatureVerifier) - .build(); + RequestProcessor processor = createDefaultRequestProcessor(); processor.doNotSendTelemetry(); @@ -210,24 +167,6 @@ public void shouldDisableTelemetry() { assertThat(client, is(notNullValue())); } - @Test - public void shouldSetupTelemetryWithVersion() { - RequestProcessor processor = createDefaultRequestProcessor(); - - processor.setupTelemetry(mockAuthAPI); - - verify(mockAuthAPI).setTelemetry(any(Telemetry.class)); - } - - @Test - public void shouldReturnNullPackageVersionInDevEnvironment() { - RequestProcessor processor = createDefaultRequestProcessor(); - - String version = processor.obtainPackageVersion(); - - assertThat(version, is(nullValue())); - } - // --- Response Type Parsing Tests --- @Test @@ -299,6 +238,252 @@ public void shouldNotRequireFormPostForNullResponseType() { assertThat(requiresFormPost, is(false)); } + // --- Error Handling Tests --- + + @Test + public void shouldThrowOnProcessIfRequestHasError() { + request.setParameter("error", "access_denied"); + request.setParameter("error_description", "The user denied the request"); + + RequestProcessor processor = createDefaultRequestProcessor(); + + InvalidRequestException e = assertThrows( + InvalidRequestException.class, + () -> processor.process(request, response)); + + assertThat(e.getCode(), is("access_denied")); + assertThat(e.getMessage(), is("The user denied the request")); + } + + @Test + public void shouldThrowOnProcessIfRequestHasErrorWithDescription() { + Map params = new HashMap<>(); + params.put("error", "something happened"); + params.put("error_description", "something happened description"); + MockHttpServletRequest request = getRequest(params); + + RequestProcessor handler = createDefaultRequestProcessor(); + + InvalidRequestException e = assertThrows(InvalidRequestException.class, () -> handler.process(request, response)); + assertThat(e.getCode(), is("something happened")); + assertThat(e.getMessage(), is("something happened description")); + } + + @Test + public void shouldThrowOnProcessIfRequestHasInvalidStateInCookie() { + Map params = new HashMap<>(); + params.put("state", "1234"); + MockHttpServletRequest request = getRequest(params); + request.setCookies(new Cookie("com.auth0.state", "9999")); + + RequestProcessor handler = createDefaultRequestProcessor(); + + InvalidRequestException e = assertThrows(InvalidRequestException.class, () -> handler.process(request, response)); + assertThat(e.getCode(), is("a0.invalid_state")); + assertThat(e.getMessage(), is("The received state doesn't match the expected one.")); + } + + @Test + public void shouldThrowOnProcessIfRequestHasMissingStateParameter() { + MockHttpServletRequest request = getRequest(Collections.emptyMap()); + request.setCookies(new Cookie("com.auth0.state", "1234")); + + RequestProcessor handler = createDefaultRequestProcessor(); + + InvalidRequestException e = assertThrows(InvalidRequestException.class, () -> handler.process(request, response)); + assertThat(e.getCode(), is("a0.invalid_state")); + assertThat(e.getMessage(), is("The received state doesn't match the expected one. No state parameter was found on the authorization response.")); + } + + @Test + public void shouldThrowOnProcessIfRequestHasMissingStateCookie() { + Map params = new HashMap<>(); + params.put("state", "1234"); + MockHttpServletRequest request = getRequest(params); + + RequestProcessor handler = createDefaultRequestProcessor(); + + InvalidRequestException e = assertThrows(InvalidRequestException.class, () -> handler.process(request, response)); + assertThat(e.getCode(), is("a0.invalid_state")); + } + + @Test + public void shouldThrowOnProcessIfIdTokenRequestIsMissingIdToken() { + when(mockDomainProvider.getDomain(any())).thenReturn(DOMAIN); + + Map params = new HashMap<>(); + params.put("state", "1234"); + MockHttpServletRequest request = getRequest(params); + request.setCookies(new Cookie("com.auth0.state", "1234")); + + RequestProcessor handler = createRequestProcessorWithResponseType(RESPONSE_TYPE_ID_TOKEN); + + InvalidRequestException e = assertThrows(InvalidRequestException.class, () -> handler.process(request, response)); + assertThat(e.getCode(), is("a0.missing_id_token")); + assertThat(e.getMessage(), is("ID Token is missing from the response.")); + } + + @Test + public void shouldThrowOnProcessIfTokenRequestIsMissingAccessToken() { + when(mockDomainProvider.getDomain(any())).thenReturn(DOMAIN); + + Map params = new HashMap<>(); + params.put("state", "1234"); + MockHttpServletRequest request = getRequest(params); + request.setCookies(new Cookie("com.auth0.state", "1234")); + + RequestProcessor handler = createRequestProcessorWithResponseType(RESPONSE_TYPE_TOKEN); + + InvalidRequestException e = assertThrows(InvalidRequestException.class, () -> handler.process(request, response)); + assertThat(e.getCode(), is("a0.missing_access_token")); + assertThat(e.getMessage(), is("Access Token is missing from the response.")); + } + + // --- Code Exchange Flow Tests --- + + @Test + public void shouldThrowOnProcessIfCodeRequestFailsToExecuteCodeExchange() throws Exception { + when(mockDomainProvider.getDomain(any())).thenReturn(DOMAIN); + + Map params = new HashMap<>(); + params.put("code", "abc123"); + params.put("state", "1234"); + MockHttpServletRequest request = getRequest(params); + request.setCookies(new Cookie("com.auth0.state", "1234")); + + when(mockTokenRequest.execute()).thenThrow(Auth0Exception.class); + when(mockAuthAPI.exchangeCode(eq("abc123"), anyString())).thenReturn(mockTokenRequest); + + RequestProcessor handler = createDefaultRequestProcessor(); + RequestProcessor spy = spy(handler); + doReturn(mockAuthAPI).when(spy).createClientForDomain(anyString()); + + IdentityVerificationException e = assertThrows(IdentityVerificationException.class, () -> spy.process(request, response)); + assertThat(e.getCode(), is("a0.api_error")); + assertThat(e.getMessage(), is("An error occurred while exchanging the authorization code.")); + } + + @Test + public void shouldThrowOnProcessIfCodeRequestSucceedsButDoesNotPassIdTokenVerification() throws Exception { + when(mockDomainProvider.getDomain(any())).thenReturn(DOMAIN); + + Map params = new HashMap<>(); + params.put("code", "abc123"); + params.put("state", "1234"); + MockHttpServletRequest request = getRequest(params); + request.setCookies(new Cookie("com.auth0.state", "1234")); + + // Return a structurally valid JWT with wrong issuer so verification fails + String fakeJwt = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3dyb25nLyIsInN1YiI6InVzZXIxMjMiLCJhdWQiOiJ0ZXN0Q2xpZW50SWQiLCJleHAiOjk5OTk5OTk5OTksImlhdCI6MTYwMDAwMDAwMH0.signature"; + when(mockTokenHolder.getIdToken()).thenReturn(fakeJwt); + when(mockTokenResponse.getBody()).thenReturn(mockTokenHolder); + when(mockTokenRequest.execute()).thenReturn(mockTokenResponse); + when(mockAuthAPI.exchangeCode(eq("abc123"), anyString())).thenReturn(mockTokenRequest); + + // Use real AlgorithmNameVerifier so signature check passes but claim validation fails + RequestProcessor handler = new RequestProcessor.Builder( + mockDomainProvider, + RESPONSE_TYPE_CODE, + CLIENT_ID, + CLIENT_SECRET, + new AlgorithmNameVerifier()) + .build(); + RequestProcessor spy = spy(handler); + doReturn(mockAuthAPI).when(spy).createClientForDomain(anyString()); + + IdentityVerificationException e = assertThrows(IdentityVerificationException.class, () -> spy.process(request, response)); + assertThat(e.getCode(), is("a0.invalid_jwt_error")); + assertThat(e.getMessage(), is("An error occurred while trying to verify the ID Token.")); + } + + @Test + public void shouldReturnTokensOnProcessIfCodeRequestSucceeds() throws Exception { + when(mockDomainProvider.getDomain(any())).thenReturn(DOMAIN); + + Map params = new HashMap<>(); + params.put("code", "abc123"); + params.put("state", "1234"); + MockHttpServletRequest request = getRequest(params); + request.setCookies(new Cookie("com.auth0.state", "1234")); + + // Return no ID token so verification is skipped + when(mockTokenHolder.getIdToken()).thenReturn(null); + when(mockTokenHolder.getAccessToken()).thenReturn("backAccessToken"); + when(mockTokenHolder.getRefreshToken()).thenReturn("backRefreshToken"); + when(mockTokenHolder.getTokenType()).thenReturn("Bearer"); + when(mockTokenHolder.getExpiresIn()).thenReturn(3600L); + when(mockTokenResponse.getBody()).thenReturn(mockTokenHolder); + when(mockTokenRequest.execute()).thenReturn(mockTokenResponse); + when(mockAuthAPI.exchangeCode(eq("abc123"), anyString())).thenReturn(mockTokenRequest); + + RequestProcessor handler = createDefaultRequestProcessor(); + RequestProcessor spy = spy(handler); + doReturn(mockAuthAPI).when(spy).createClientForDomain(anyString()); + + Tokens tokens = spy.process(request, response); + + assertThat(tokens, is(notNullValue())); + assertThat(tokens.getAccessToken(), is("backAccessToken")); + assertThat(tokens.getRefreshToken(), is("backRefreshToken")); + assertThat(tokens.getType(), is("Bearer")); + assertThat(tokens.getExpiresIn(), is(3600L)); + } + + @Test + public void shouldReturnEmptyTokensWhenCodeRequestReturnsNoTokens() throws Exception { + when(mockDomainProvider.getDomain(any())).thenReturn(DOMAIN); + + Map params = new HashMap<>(); + params.put("code", "abc123"); + params.put("state", "1234"); + MockHttpServletRequest request = getRequest(params); + request.setCookies(new Cookie("com.auth0.state", "1234")); + + when(mockTokenResponse.getBody()).thenReturn(mockTokenHolder); + when(mockTokenRequest.execute()).thenReturn(mockTokenResponse); + when(mockAuthAPI.exchangeCode(eq("abc123"), anyString())).thenReturn(mockTokenRequest); + + RequestProcessor handler = createDefaultRequestProcessor(); + RequestProcessor spy = spy(handler); + doReturn(mockAuthAPI).when(spy).createClientForDomain(anyString()); + + Tokens tokens = spy.process(request, response); + + assertThat(tokens, is(notNullValue())); + assertThat(tokens.getIdToken(), is(nullValue())); + assertThat(tokens.getAccessToken(), is(nullValue())); + assertThat(tokens.getRefreshToken(), is(nullValue())); + } + + // --- Implicit / Hybrid Flow Tests --- + + @Test + public void shouldThrowOnProcessIfIdTokenRequestDoesNotPassIdTokenVerification() { + when(mockDomainProvider.getDomain(any())).thenReturn(DOMAIN); + + // Structurally valid JWT with wrong issuer so claim validation fails + String fakeJwt = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3dyb25nLyIsInN1YiI6InVzZXIxMjMiLCJhdWQiOiJ0ZXN0Q2xpZW50SWQiLCJleHAiOjk5OTk5OTk5OTksImlhdCI6MTYwMDAwMDAwMH0.signature"; + + Map params = new HashMap<>(); + params.put("state", "1234"); + params.put("id_token", fakeJwt); + MockHttpServletRequest request = getRequest(params); + request.setCookies(new Cookie("com.auth0.state", "1234")); + + // Use real AlgorithmNameVerifier so signature check passes but claim validation fails + RequestProcessor handler = new RequestProcessor.Builder( + mockDomainProvider, + RESPONSE_TYPE_ID_TOKEN, + CLIENT_ID, + CLIENT_SECRET, + new AlgorithmNameVerifier()) + .build(); + + IdentityVerificationException e = assertThrows(IdentityVerificationException.class, () -> handler.process(request, response)); + assertThat(e.getCode(), is("a0.invalid_jwt_error")); + assertThat(e.getMessage(), is("An error occurred while trying to verify the ID Token.")); + } + // --- AuthorizeUrl Building Tests --- @Test @@ -322,7 +507,6 @@ public void shouldBuildAuthorizeUrlWithOrganization() { RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .withOrganization("org_123") .build(); @@ -343,7 +527,6 @@ public void shouldBuildAuthorizeUrlWithInvitation() { RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .withInvitation("inv_456") .build(); @@ -364,7 +547,6 @@ public void shouldBuildAuthorizeUrlWithCustomCookiePath() { RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .withCookiePath("/custom") .build(); @@ -377,111 +559,28 @@ public void shouldBuildAuthorizeUrlWithCustomCookiePath() { assertThat(result, is(notNullValue())); } - // --- Error Handling Tests --- - - @Test - public void shouldThrowExceptionWhenErrorInRequest() { - request.setParameter("error", "access_denied"); - request.setParameter("error_description", "The user denied the request"); - - RequestProcessor processor = createDefaultRequestProcessor(); - - InvalidRequestException exception = assertThrows( - InvalidRequestException.class, - () -> processor.process(request, response)); - - assertThat(exception.getCode(), is("access_denied")); - assertThat(exception.getMessage(), is("The user denied the request")); - } - - @Test - public void shouldThrowExceptionWhenStateIsMissing() { - request.setParameter("code", "test_code"); - - RequestProcessor processor = createDefaultRequestProcessor(); - - InvalidRequestException exception = assertThrows( - InvalidRequestException.class, - () -> processor.process(request, response)); - - assertThat(exception.getCode(), is("a0.invalid_state")); - } - - @Test - public void shouldThrowExceptionWhenIdTokenMissingForImplicitGrant() { - request.setParameter("state", "validState"); - - RequestProcessor processor = createRequestProcessorWithResponseType(RESPONSE_TYPE_ID_TOKEN); - - InvalidRequestException exception = assertThrows( - InvalidRequestException.class, - () -> processor.process(request, response)); - - assertThat(exception, is(notNullValue())); - assertThat(exception.getCode(), is(notNullValue())); - } - @Test - public void shouldThrowExceptionWhenAccessTokenMissingForTokenGrant() { - request.setParameter("state", "validState"); - - RequestProcessor processor = createRequestProcessorWithResponseType(RESPONSE_TYPE_TOKEN); + public void shouldBuildAuthorizeUrlWithFormPostIfResponseTypeIsToken() { + when(mockDomainProvider.getDomain(any())).thenReturn(DOMAIN); + RequestProcessor handler = createRequestProcessorWithResponseType(RESPONSE_TYPE_TOKEN); + RequestProcessor spy = spy(handler); + doReturn(mockAuthAPI).when(spy).createClientForDomain(anyString()); - InvalidRequestException exception = assertThrows( - InvalidRequestException.class, - () -> processor.process(request, response)); + AuthorizeUrl result = spy.buildAuthorizeUrl(request, response, "https://redirect.uri/here", "state", "nonce"); - assertThat(exception, is(notNullValue())); - assertThat(exception.getCode(), is(notNullValue())); + assertThat(result, is(notNullValue())); } - // --- Token Processing Tests --- - @Test - public void shouldProcessCodeGrantFlow() throws Exception { - request.setParameter("code", "auth_code_123"); - request.setParameter("state", "validState"); - - RequestProcessor processor = createDefaultRequestProcessor(); - RequestProcessor spy = spy(processor); - + public void shouldBuildAuthorizeUrlWithNonceAndFormPostIfResponseTypeIsIdToken() { + when(mockDomainProvider.getDomain(any())).thenReturn(DOMAIN); + RequestProcessor handler = createRequestProcessorWithResponseType(RESPONSE_TYPE_ID_TOKEN); + RequestProcessor spy = spy(handler); doReturn(mockAuthAPI).when(spy).createClientForDomain(anyString()); - when(mockAuthAPI.exchangeCode(anyString(), anyString())).thenReturn(mockTokenRequest); - when(mockTokenRequest.execute()).thenReturn(mockTokenHolder); - when(mockTokenHolder.getAccessToken()).thenReturn("access_token_123"); - try { - Tokens result = spy.process(request, response); - assertThat(result, is(notNullValue())); - } catch (InvalidRequestException e) { - // Expected due to state cookie validation - assertThat(e.getCode(), is(notNullValue())); - } - } - - @Test - public void shouldProcessImplicitGrantFlow() throws Exception { - request.setParameter("access_token", "access_token_123"); - request.setParameter("id_token", createMockIdToken()); - request.setParameter("token_type", "Bearer"); - request.setParameter("expires_in", "3600"); - request.setParameter("state", "validState"); + AuthorizeUrl result = spy.buildAuthorizeUrl(request, response, "https://redirect.uri/here", "state", "nonce"); - response.addCookie(new javax.servlet.http.Cookie("com.auth0.state", "validState")); - - RequestProcessor processor = createRequestProcessorWithResponseType("id_token token"); - - try { - Tokens result = processor.process(request, response); - assertThat(result, is(notNullValue())); - assertThat(result.getAccessToken(), is("access_token_123")); - assertThat(result.getIdToken(), is(notNullValue())); - assertThat(result.getType(), is("Bearer")); - assertThat(result.getExpiresIn(), is(3600L)); - } catch (IdentityVerificationException e) { - // Expected due to token verification - assertThat(e, is(notNullValue())); - } + assertThat(result, is(notNullValue())); } // --- Builder Configuration Tests --- @@ -493,7 +592,6 @@ public void shouldSupportOrganizationParameter() { RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .withOrganization("org_123") .build(); @@ -508,7 +606,6 @@ public void shouldSupportInvitationParameter() { RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .withInvitation("inv_456") .build(); @@ -523,7 +620,6 @@ public void shouldSupportCustomCookiePath() { RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .withCookiePath("/custom/path") .build(); @@ -538,7 +634,6 @@ public void shouldSupportClockSkewConfiguration() { RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .withClockSkew(180) .build(); @@ -553,7 +648,6 @@ public void shouldSupportAuthenticationMaxAge() { RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .withAuthenticationMaxAge(7200) .build(); @@ -569,7 +663,6 @@ private RequestProcessor createDefaultRequestProcessor() { RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .build(); } @@ -580,17 +673,17 @@ private RequestProcessor createRequestProcessorWithResponseType(String responseT responseType, CLIENT_ID, CLIENT_SECRET, - mockHttpOptions, mockSignatureVerifier) .build(); } - private String createMockIdToken() { - String header = java.util.Base64.getUrlEncoder().withoutPadding() - .encodeToString("{\"typ\":\"JWT\",\"alg\":\"RS256\"}".getBytes()); - String payload = java.util.Base64.getUrlEncoder().withoutPadding() - .encodeToString(("{\"iss\":\"https://" + DOMAIN + "/\",\"sub\":\"user123\"}").getBytes()); - String signature = "signature"; - return header + "." + payload + "." + signature; + private MockHttpServletRequest getRequest(Map parameters) { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setScheme("https"); + request.setServerName("me.auth0.com"); + request.setServerPort(80); + request.setRequestURI("/callback"); + request.setParameters(parameters); + return request; } } diff --git a/src/test/java/com/auth0/SessionUtilsTest.java b/src/test/java/com/auth0/SessionUtilsTest.java deleted file mode 100644 index d7edf62..0000000 --- a/src/test/java/com/auth0/SessionUtilsTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.auth0; - -import org.junit.jupiter.api.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; - -public class SessionUtilsTest { - @Test - public void shouldGetAndRemoveAttribute() { - MockHttpServletRequest req = new MockHttpServletRequest(); - req.getSession().setAttribute("name", "value"); - - assertThat(SessionUtils.remove(req, "name"), is("value")); - assertThat(req.getSession().getAttribute("name"), is(nullValue())); - } - - @Test - public void shouldGetAttribute() { - MockHttpServletRequest req = new MockHttpServletRequest(); - req.getSession().setAttribute("name", "value"); - - assertThat(SessionUtils.get(req, "name"), is("value")); - assertThat(req.getSession().getAttribute("name"), is("value")); - } - - @Test - public void shouldGetNullAttributeIfMissing() { - MockHttpServletRequest req = new MockHttpServletRequest(); - - assertThat(SessionUtils.get(req, "name"), is(nullValue())); - assertThat(req.getSession().getAttribute("name"), is(nullValue())); - } - - @Test - public void shouldSetAttribute() { - MockHttpServletRequest req = new MockHttpServletRequest(); - - SessionUtils.set(req, "name", "value"); - assertThat(req.getSession().getAttribute("name"), is("value")); - } - -} diff --git a/src/test/java/com/auth0/SignatureVerifierTest.java b/src/test/java/com/auth0/SignatureVerifierTest.java index 326387f..b0b6678 100644 --- a/src/test/java/com/auth0/SignatureVerifierTest.java +++ b/src/test/java/com/auth0/SignatureVerifierTest.java @@ -4,9 +4,9 @@ import com.auth0.jwk.JwkException; import com.auth0.jwk.JwkProvider; import com.auth0.jwt.interfaces.DecodedJWT; -import org.bouncycastle.util.io.pem.PemReader; import org.junit.jupiter.api.Test; +import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; @@ -18,6 +18,7 @@ import java.security.interfaces.RSAPublicKey; import java.security.spec.EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; import java.util.Scanner; import static org.hamcrest.CoreMatchers.notNullValue; @@ -146,7 +147,7 @@ private JwkProvider getRSProvider(String rsaPath) throws Exception { private static RSAPublicKey readPublicKeyFromFile(final String path) throws IOException { Scanner scanner = null; - PemReader pemReader = null; + BufferedReader reader = null; try { scanner = new Scanner(Paths.get(path)); if (scanner.hasNextLine() && scanner.nextLine().startsWith("-----BEGIN CERTIFICATE-----")) { @@ -157,8 +158,15 @@ private static RSAPublicKey readPublicKeyFromFile(final String path) throws IOEx fs.close(); return (RSAPublicKey) key; } else { - pemReader = new PemReader(new FileReader(path)); - byte[] keyBytes = pemReader.readPemObject().getContent(); + reader = new BufferedReader(new FileReader(path)); + StringBuilder pemContent = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + if (!line.startsWith("-----BEGIN") && !line.startsWith("-----END")) { + pemContent.append(line); + } + } + byte[] keyBytes = Base64.getDecoder().decode(pemContent.toString()); KeyFactory kf = KeyFactory.getInstance("RSA"); EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); return (RSAPublicKey) kf.generatePublic(keySpec); @@ -169,8 +177,8 @@ private static RSAPublicKey readPublicKeyFromFile(final String path) throws IOEx if (scanner != null) { scanner.close(); } - if (pemReader != null) { - pemReader.close(); + if (reader != null) { + reader.close(); } } } diff --git a/src/test/java/com/auth0/TransientCookieStoreTest.java b/src/test/java/com/auth0/TransientCookieStoreTest.java index 9db31f4..00f30cb 100644 --- a/src/test/java/com/auth0/TransientCookieStoreTest.java +++ b/src/test/java/com/auth0/TransientCookieStoreTest.java @@ -6,13 +6,14 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import javax.servlet.http.Cookie; +import jakarta.servlet.http.Cookie; import java.net.URLEncoder; import java.util.Arrays; import java.util.List; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.matchesPattern; public class TransientCookieStoreTest { @@ -49,11 +50,9 @@ public void shouldHandleSpecialCharsWhenStoringState() throws Exception { List headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(2)); - String expectedEncodedState = URLEncoder.encode(stateVal, "UTF-8"); - assertThat(headers, hasItem( - String.format("com.auth0.state=%s; HttpOnly; Max-Age=600; SameSite=None; Secure", expectedEncodedState))); - assertThat(headers, hasItem( - String.format("_com.auth0.state=%s; HttpOnly; Max-Age=600", expectedEncodedState))); + String expectedEncodedState = URLEncoder.encode(stateVal, "UTF-8").replaceAll("\\+", "\\\\+"); + assertThat(headers, hasItem(matchesPattern(String.format("com\\.auth0\\.state=%s; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=None", expectedEncodedState)))); + assertThat(headers, hasItem(matchesPattern(String.format("_com\\.auth0\\.state=%s; Max-Age=600; Expires=.*?; HttpOnly", expectedEncodedState)))); } @Test @@ -63,8 +62,8 @@ public void shouldSetStateSameSiteCookieAndFallbackCookie() { List headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(2)); - assertThat(headers, hasItem("com.auth0.state=123456; HttpOnly; Max-Age=600; SameSite=None; Secure")); - assertThat(headers, hasItem("_com.auth0.state=123456; HttpOnly; Max-Age=600")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.state=123456; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=None"))); + assertThat(headers, hasItem(matchesPattern("_com\\.auth0\\.state=123456; Max-Age=600; Expires=.*?; HttpOnly"))); } @Test @@ -74,7 +73,7 @@ public void shouldSetStateSameSiteCookieAndNoFallbackCookie() { List headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(1)); - assertThat(headers, hasItem("com.auth0.state=123456; HttpOnly; Max-Age=600; SameSite=None; Secure")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.state=123456; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=None"))); } @Test @@ -84,7 +83,7 @@ public void shouldSetSecureCookieWhenSameSiteLaxAndConfigured() { List headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(1)); - assertThat(headers, hasItem("com.auth0.state=123456; HttpOnly; Max-Age=600; SameSite=Lax; Secure")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.state=123456; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=Lax"))); } @Test @@ -94,8 +93,8 @@ public void shouldSetSecureFallbackCookieWhenSameSiteNoneAndConfigured() { List headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(2)); - assertThat(headers, hasItem("com.auth0.state=123456; HttpOnly; Max-Age=600; SameSite=None; Secure")); - assertThat(headers, hasItem("_com.auth0.state=123456; HttpOnly; Max-Age=600; Secure")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.state=123456; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=None"))); + assertThat(headers, hasItem(matchesPattern("_com\\.auth0\\.state=123456; Max-Age=600; Expires=.*?; Secure; HttpOnly"))); } @Test @@ -105,7 +104,7 @@ public void shouldNotSetSecureCookieWhenSameSiteLaxAndConfigured() { List headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(1)); - assertThat(headers, hasItem("com.auth0.state=123456; HttpOnly; Max-Age=600; SameSite=Lax")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.state=123456; Max-Age=600; Expires=.*?; HttpOnly; SameSite=Lax"))); } @Test @@ -115,8 +114,8 @@ public void shouldSetNonceSameSiteCookieAndFallbackCookie() { List headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(2)); - assertThat(headers, hasItem("com.auth0.nonce=123456; HttpOnly; Max-Age=600; SameSite=None; Secure")); - assertThat(headers, hasItem("_com.auth0.nonce=123456; HttpOnly; Max-Age=600")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.nonce=123456; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=None"))); + assertThat(headers, hasItem(matchesPattern("_com\\.auth0\\.nonce=123456; Max-Age=600; Expires=.*?; HttpOnly"))); } @Test @@ -126,7 +125,7 @@ public void shouldSetNonceSameSiteCookieAndNoFallbackCookie() { List headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(1)); - assertThat(headers, hasItem("com.auth0.nonce=123456; HttpOnly; Max-Age=600; SameSite=None; Secure")); + assertThat(headers, hasItem(matchesPattern("com\\.auth0\\.nonce=123456; Max-Age=600; Expires=.*?; Secure; HttpOnly; SameSite=None"))); } @Test