package com.auth0; import com.auth0.client.auth.AuthAPI; import com.auth0.client.auth.AuthorizeUrlBuilder; import com.auth0.json.auth.TokenHolder; import com.auth0.jwk.JwkProvider; import com.auth0.net.AuthRequest; import com.auth0.net.Telemetry; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; 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 javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.*; import static org.mockito.Mockito.*; @SuppressWarnings("deprecated") public class AuthenticationControllerTest { @Rule public ExpectedException exception = ExpectedException.none(); @Mock private AuthAPI client; @Mock private IdTokenVerifier.Options verificationOptions; @Captor private ArgumentCaptor<SignatureVerifier> signatureVerifierCaptor; private AuthenticationController.Builder builderSpy; @Before public void setUp() { MockitoAnnotations.initMocks(this); AuthenticationController.Builder builder = AuthenticationController.newBuilder("domain", "clientId", "clientSecret"); builderSpy = spy(builder); doReturn(client).when(builderSpy).createAPIClient(eq("domain"), eq("clientId"), eq("clientSecret")); doReturn(verificationOptions).when(builderSpy).createIdTokenVerificationOptions(eq("https://domain/"), eq("clientId"), signatureVerifierCaptor.capture()); doReturn("1.2.3").when(builderSpy).obtainPackageVersion(); } @Test public void shouldSetupClientWithTelemetry() { AuthenticationController controller = builderSpy.build(); ArgumentCaptor<Telemetry> telemetryCaptor = ArgumentCaptor.forClass(Telemetry.class); assertThat(controller, is(notNullValue())); RequestProcessor requestProcessor = controller.getRequestProcessor(); assertThat(requestProcessor.getClient(), is(client)); verify(client).setTelemetry(telemetryCaptor.capture()); Telemetry capturedTelemetry = telemetryCaptor.getValue(); assertThat(capturedTelemetry, is(notNullValue())); assertThat(capturedTelemetry.getName(), is("auth0-java-mvc-common")); assertThat(capturedTelemetry.getVersion(), is("1.2.3")); } @Test public void shouldDisableTelemetry() { AuthenticationController controller = builderSpy.build(); controller.doNotSendTelemetry(); verify(client).doNotSendTelemetry(); } @Test public void shouldEnableLogging() { AuthenticationController controller = builderSpy.build(); controller.setLoggingEnabled(true); verify(client).setLoggingEnabled(true); } @Test public void shouldDisableLogging() { AuthenticationController controller = builderSpy.build(); controller.setLoggingEnabled(true); verify(client).setLoggingEnabled(true); } @Test public void shouldCreateWithSymmetricSignatureVerifierForNoCodeGrants() { AuthenticationController controller = builderSpy .withResponseType("id_token") .build(); SignatureVerifier signatureVerifier = signatureVerifierCaptor.getValue(); assertThat(signatureVerifier, is(notNullValue())); assertThat(signatureVerifier, instanceOf(SymmetricSignatureVerifier.class)); assertThat(verificationOptions, is(controller.getRequestProcessor().verifyOptions)); controller = builderSpy .withResponseType("token") .build(); signatureVerifier = signatureVerifierCaptor.getValue(); assertThat(signatureVerifier, is(notNullValue())); assertThat(signatureVerifier, instanceOf(SymmetricSignatureVerifier.class)); assertThat(verificationOptions, is(controller.getRequestProcessor().verifyOptions)); } @Test public void shouldCreateWithAsymmetricSignatureVerifierWhenJwkProviderIsExplicitlySet() { JwkProvider jwkProvider = mock(JwkProvider.class); AuthenticationController controller = builderSpy .withResponseType("code id_token") .withJwkProvider(jwkProvider) .build(); SignatureVerifier signatureVerifier = signatureVerifierCaptor.getValue(); assertThat(signatureVerifier, is(notNullValue())); assertThat(signatureVerifier, instanceOf(AsymmetricSignatureVerifier.class)); assertThat(verificationOptions, is(controller.getRequestProcessor().verifyOptions)); controller = builderSpy .withResponseType("code token") .withJwkProvider(jwkProvider) .build(); signatureVerifier = signatureVerifierCaptor.getValue(); assertThat(signatureVerifier, is(notNullValue())); assertThat(signatureVerifier, instanceOf(AsymmetricSignatureVerifier.class)); assertThat(verificationOptions, is(controller.getRequestProcessor().verifyOptions)); controller = builderSpy .withResponseType("code id_token token") .withJwkProvider(jwkProvider) .build(); signatureVerifier = signatureVerifierCaptor.getValue(); assertThat(signatureVerifier, is(notNullValue())); assertThat(signatureVerifier, instanceOf(AsymmetricSignatureVerifier.class)); assertThat(verificationOptions, is(controller.getRequestProcessor().verifyOptions)); controller = builderSpy .withResponseType("code") .withJwkProvider(jwkProvider) .build(); signatureVerifier = signatureVerifierCaptor.getValue(); assertThat(signatureVerifier, is(notNullValue())); assertThat(signatureVerifier, instanceOf(AsymmetricSignatureVerifier.class)); assertThat(verificationOptions, is(controller.getRequestProcessor().verifyOptions)); controller = builderSpy .withResponseType("id_token") .withJwkProvider(jwkProvider) .build(); signatureVerifier = signatureVerifierCaptor.getValue(); assertThat(signatureVerifier, is(notNullValue())); assertThat(signatureVerifier, instanceOf(AsymmetricSignatureVerifier.class)); assertThat(verificationOptions, is(controller.getRequestProcessor().verifyOptions)); controller = builderSpy .withResponseType("token") .withJwkProvider(jwkProvider) .build(); signatureVerifier = signatureVerifierCaptor.getValue(); assertThat(signatureVerifier, is(notNullValue())); assertThat(signatureVerifier, instanceOf(AsymmetricSignatureVerifier.class)); assertThat(verificationOptions, is(controller.getRequestProcessor().verifyOptions)); } @Test public void shouldCreateWithAlgorithmNameSignatureVerifierForResponseTypesIncludingCode() { AuthenticationController controller = builderSpy .withResponseType("code id_token") .build(); SignatureVerifier signatureVerifier = signatureVerifierCaptor.getValue(); assertThat(signatureVerifier, is(notNullValue())); assertThat(signatureVerifier, instanceOf(AlgorithmNameVerifier.class)); assertThat(verificationOptions, is(controller.getRequestProcessor().verifyOptions)); controller = builderSpy .withResponseType("code token") .build(); signatureVerifier = signatureVerifierCaptor.getValue(); assertThat(signatureVerifier, is(notNullValue())); assertThat(signatureVerifier, instanceOf(AlgorithmNameVerifier.class)); assertThat(verificationOptions, is(controller.getRequestProcessor().verifyOptions)); controller = builderSpy .withResponseType("code token id_token") .build(); signatureVerifier = signatureVerifierCaptor.getValue(); assertThat(signatureVerifier, is(notNullValue())); assertThat(signatureVerifier, instanceOf(AlgorithmNameVerifier.class)); assertThat(verificationOptions, is(controller.getRequestProcessor().verifyOptions)); controller = builderSpy .withResponseType("code") .build(); signatureVerifier = signatureVerifierCaptor.getValue(); assertThat(signatureVerifier, is(notNullValue())); assertThat(signatureVerifier, instanceOf(AlgorithmNameVerifier.class)); assertThat(verificationOptions, is(controller.getRequestProcessor().verifyOptions)); } @Test public void shouldThrowOnMissingDomain() { exception.expect(NullPointerException.class); AuthenticationController.newBuilder(null, "clientId", "clientSecret"); } @Test public void shouldThrowOnMissingClientId() { exception.expect(NullPointerException.class); AuthenticationController.newBuilder("domain", null, "clientSecret"); } @Test public void shouldThrowOnMissingClientSecret() { exception.expect(NullPointerException.class); AuthenticationController.newBuilder("domain", "clientId", null); } @Test public void shouldThrowOnMissingJwkProvider() { exception.expect(NullPointerException.class); AuthenticationController.newBuilder("domain", "clientId", "clientSecret") .withJwkProvider(null); } @Test public void shouldThrowOnMissingResponseType() { exception.expect(NullPointerException.class); AuthenticationController.newBuilder("domain", "clientId", "clientSecret") .withResponseType(null); } @Test public void shouldCreateWithDefaultValues() { AuthenticationController controller = AuthenticationController.newBuilder("domain", "clientId", "clientSecret") .build(); assertThat(controller, is(notNullValue())); RequestProcessor requestProcessor = controller.getRequestProcessor(); assertThat(requestProcessor.getResponseType(), contains("code")); assertThat(requestProcessor.verifyOptions.audience, is("clientId")); assertThat(requestProcessor.verifyOptions.issuer, is("https://domain/")); assertThat(requestProcessor.verifyOptions.verifier, is(notNullValue())); assertThat(requestProcessor.verifyOptions.clockSkew, is(nullValue())); assertThat(requestProcessor.verifyOptions.clock, is(nullValue())); assertThat(requestProcessor.verifyOptions.nonce, is(nullValue())); assertThat(requestProcessor.verifyOptions.getMaxAge(), is(nullValue())); } @Test public void shouldHandleHttpDomain() { AuthenticationController controller = AuthenticationController.newBuilder("http://domain/", "clientId", "clientSecret") .build(); assertThat(controller, is(notNullValue())); RequestProcessor requestProcessor = controller.getRequestProcessor(); assertThat(requestProcessor.getResponseType(), contains("code")); assertThat(requestProcessor.verifyOptions.audience, is("clientId")); assertThat(requestProcessor.verifyOptions.issuer, is("http://domain/")); assertThat(requestProcessor.verifyOptions.verifier, is(notNullValue())); assertThat(requestProcessor.verifyOptions.clockSkew, is(nullValue())); assertThat(requestProcessor.verifyOptions.clock, is(nullValue())); assertThat(requestProcessor.verifyOptions.nonce, is(nullValue())); assertThat(requestProcessor.verifyOptions.getMaxAge(), is(nullValue())); } @Test public void shouldHandleHttpsDomain() { AuthenticationController controller = AuthenticationController.newBuilder("https://domain/", "clientId", "clientSecret") .build(); assertThat(controller, is(notNullValue())); RequestProcessor requestProcessor = controller.getRequestProcessor(); assertThat(requestProcessor.getResponseType(), contains("code")); assertThat(requestProcessor.verifyOptions.audience, is("clientId")); assertThat(requestProcessor.verifyOptions.issuer, is("https://domain/")); assertThat(requestProcessor.verifyOptions.verifier, is(notNullValue())); assertThat(requestProcessor.verifyOptions.clockSkew, is(nullValue())); assertThat(requestProcessor.verifyOptions.clock, is(nullValue())); assertThat(requestProcessor.verifyOptions.nonce, is(nullValue())); assertThat(requestProcessor.verifyOptions.getMaxAge(), is(nullValue())); } @Test public void shouldCreateWithResponseType() { AuthenticationController controller = AuthenticationController.newBuilder("domain", "clientId", "clientSecret") .withResponseType("toKEn Id_TokEn cOdE") .build(); RequestProcessor requestProcessor = controller.getRequestProcessor(); assertThat(requestProcessor.getResponseType(), contains("token", "id_token", "code")); } @Test public void shouldCreateWithJwkProvider() { JwkProvider provider = mock(JwkProvider.class); AuthenticationController.newBuilder("domain", "clientId", "clientSecret") .withJwkProvider(provider) .build(); } @Test public void shouldCreateWithIDTokenVerificationLeeway() { AuthenticationController controller = AuthenticationController.newBuilder("domain", "clientId", "clientSecret") .withClockSkew(12345) .build(); RequestProcessor requestProcessor = controller.getRequestProcessor(); assertThat(requestProcessor.verifyOptions.clockSkew, is(12345)); } @Test public void shouldCreateWithMaxAge() { AuthenticationController controller = AuthenticationController.newBuilder("domain", "clientId", "clientSecret") .withAuthenticationMaxAge(12345) .build(); RequestProcessor requestProcessor = controller.getRequestProcessor(); assertThat(requestProcessor.verifyOptions.getMaxAge(), is(12345)); } @Test public void shouldProcessRequest() throws IdentityVerificationException { RequestProcessor requestProcessor = mock(RequestProcessor.class); AuthenticationController controller = new AuthenticationController(requestProcessor); HttpServletRequest req = new MockHttpServletRequest(); HttpServletResponse response = new MockHttpServletResponse(); controller.handle(req, response); verify(requestProcessor).process(req, response); } @Test public void shouldBuildAuthorizeUriWithRandomStateAndNonce() { RequestProcessor requestProcessor = mock(RequestProcessor.class); AuthenticationController controller = new AuthenticationController(requestProcessor); HttpServletRequest request = new MockHttpServletRequest(); HttpServletResponse response = new MockHttpServletResponse(); controller.buildAuthorizeUrl(request, response,"https://redirect.uri/here"); verify(requestProcessor).buildAuthorizeUrl(eq(request), eq(response), eq("https://redirect.uri/here"), anyString(), anyString()); } @Test public void shouldSetLaxCookiesAndNoLegacyCookieWhenCodeFlow() { MockHttpServletResponse response = new MockHttpServletResponse(); AuthenticationController controller = AuthenticationController.newBuilder("domain", "clientId", "clientSecret") .withResponseType("code") .build(); controller.buildAuthorizeUrl(new MockHttpServletRequest(), response, "https://redirect.uri/here") .withState("state") .build(); List<String> headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(1)); assertThat(headers, everyItem(is("com.auth0.state=state; HttpOnly; Max-Age=600; SameSite=Lax"))); } @Test public void shouldSetSameSiteNoneCookiesAndLegacyCookieWhenIdTokenResponse() { MockHttpServletResponse response = new MockHttpServletResponse(); AuthenticationController controller = AuthenticationController.newBuilder("domain", "clientId", "clientSecret") .withResponseType("id_token") .build(); controller.buildAuthorizeUrl(new MockHttpServletRequest(), response, "https://redirect.uri/here") .withState("state") .withNonce("nonce") .build(); List<String> headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(4)); assertThat(headers, hasItem("com.auth0.state=state; HttpOnly; Max-Age=600; SameSite=None; Secure")); assertThat(headers, hasItem("_com.auth0.state=state; HttpOnly; Max-Age=600")); assertThat(headers, hasItem("com.auth0.nonce=nonce; HttpOnly; Max-Age=600; SameSite=None; Secure")); assertThat(headers, hasItem("_com.auth0.nonce=nonce; HttpOnly; Max-Age=600")); } @Test public void shouldSetSameSiteNoneCookiesAndNoLegacyCookieWhenIdTokenResponse() { MockHttpServletResponse response = new MockHttpServletResponse(); AuthenticationController controller = AuthenticationController.newBuilder("domain", "clientId", "clientSecret") .withResponseType("id_token") .withLegacySameSiteCookie(false) .build(); controller.buildAuthorizeUrl(new MockHttpServletRequest(), response, "https://redirect.uri/here") .withState("state") .withNonce("nonce") .build(); List<String> headers = response.getHeaders("Set-Cookie"); assertThat(headers.size(), is(2)); assertThat(headers, hasItem("com.auth0.state=state; HttpOnly; Max-Age=600; SameSite=None; Secure")); assertThat(headers, hasItem("com.auth0.nonce=nonce; HttpOnly; Max-Age=600; SameSite=None; Secure")); } @Test public void shouldCheckSessionFallbackWhenHandleCalledWithRequestAndResponse() throws Exception { AuthenticationController controller = builderSpy.withResponseType("code").build(); AuthRequest codeExchangeRequest = mock(AuthRequest.class); TokenHolder tokenHolder = mock(TokenHolder.class); when(codeExchangeRequest.execute()).thenReturn(tokenHolder); when(client.exchangeCode("abc123", "http://localhost")).thenReturn(codeExchangeRequest); AuthorizeUrlBuilder mockBuilder = mock(AuthorizeUrlBuilder.class); when(mockBuilder.withResponseType("code")).thenReturn(mockBuilder); when(mockBuilder.withScope("openid")).thenReturn(mockBuilder); when(client.authorizeUrl("https://redirect.uri/here")).thenReturn(mockBuilder); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); // build auth URL using deprecated method, which stores state and nonce in session String authUrl = controller.buildAuthorizeUrl(request, "https://redirect.uri/here") .withState("state") .withNonce("nonce") .build(); String state = (String) request.getSession().getAttribute("com.auth0.state"); String nonce = (String) request.getSession().getAttribute("com.auth0.nonce"); assertThat(state, is("state")); assertThat(nonce, is("nonce")); request.setParameter("state", "state"); request.setParameter("nonce", "nonce"); request.setParameter("code", "abc123"); // handle called with request and response, which should use cookies but fallback to session controller.handle(request, response); } @Test public void shouldCheckSessionFallbackWhenHandleCalledWithRequest() throws Exception { AuthenticationController controller = builderSpy.withResponseType("code").build(); AuthRequest codeExchangeRequest = mock(AuthRequest.class); TokenHolder tokenHolder = mock(TokenHolder.class); when(codeExchangeRequest.execute()).thenReturn(tokenHolder); when(client.exchangeCode("abc123", "http://localhost")).thenReturn(codeExchangeRequest); AuthorizeUrlBuilder mockBuilder = mock(AuthorizeUrlBuilder.class); when(mockBuilder.withResponseType("code")).thenReturn(mockBuilder); when(mockBuilder.withScope("openid")).thenReturn(mockBuilder); when(client.authorizeUrl("https://redirect.uri/here")).thenReturn(mockBuilder); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); // build auth URL using request and response, which stores state and nonce in cookies and also session as a fallback String authUrl = controller.buildAuthorizeUrl(request, response,"https://redirect.uri/here") .withState("state") .withNonce("nonce") .build(); String state = (String) request.getSession().getAttribute("com.auth0.state"); String nonce = (String) request.getSession().getAttribute("com.auth0.nonce"); assertThat(state, is("state")); assertThat(nonce, is("nonce")); request.setParameter("state", "state"); request.setParameter("nonce", "nonce"); request.setParameter("code", "abc123"); // handle called with request, which should use session controller.handle(request); } }