Java tutorial
/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.test.oauth.internal; import static java.util.Collections.emptyMap; import static java.util.Collections.singleton; import static java.util.Collections.singletonMap; import static java.util.Optional.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentCaptor.forClass; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.initialiseIfNeeded; import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.startIfNeeded; import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.stopIfNeeded; import static org.mule.runtime.http.api.HttpConstants.Method.GET; import static org.mule.service.oauth.internal.OAuthConstants.CODE_PARAMETER; import static org.mule.service.oauth.internal.OAuthConstants.STATE_PARAMETER; import static org.mule.service.oauth.internal.state.StateEncoder.RESOURCE_OWNER_PARAM_NAME_ASSIGN; import org.mule.runtime.api.el.MuleExpressionLanguage; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.api.lifecycle.InitialisationException; import org.mule.runtime.api.lock.LockFactory; import org.mule.runtime.api.scheduler.SchedulerService; import org.mule.runtime.api.util.MultiMap; import org.mule.runtime.http.api.HttpService; import org.mule.runtime.http.api.client.HttpClient; import org.mule.runtime.http.api.client.HttpClientFactory; import org.mule.runtime.http.api.domain.entity.InputStreamHttpEntity; import org.mule.runtime.http.api.domain.message.request.HttpRequest; import org.mule.runtime.http.api.domain.message.response.HttpResponse; import org.mule.runtime.http.api.domain.request.HttpRequestContext; import org.mule.runtime.http.api.server.HttpServer; import org.mule.runtime.http.api.server.HttpServerFactory; import org.mule.runtime.http.api.server.RequestHandler; import org.mule.runtime.http.api.server.RequestHandlerManager; import org.mule.runtime.http.api.server.async.HttpResponseReadyCallback; import org.mule.runtime.oauth.api.AuthorizationCodeOAuthDancer; import org.mule.runtime.oauth.api.OAuthService; import org.mule.runtime.oauth.api.builder.AuthorizationCodeDanceCallbackContext; import org.mule.runtime.oauth.api.builder.OAuthAuthorizationCodeDancerBuilder; import org.mule.runtime.oauth.api.builder.OAuthClientCredentialsDancerBuilder; import org.mule.runtime.oauth.api.builder.OAuthDancerBuilder; import org.mule.runtime.oauth.api.state.DefaultResourceOwnerOAuthContext; import org.mule.service.oauth.internal.DefaultOAuthService; import org.mule.tck.junit4.AbstractMuleContextTestCase; import org.apache.commons.io.input.ReaderInputStream; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import java.io.IOException; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; import javax.inject.Inject; import io.qameta.allure.Feature; @Feature("OAuth Service") public class DancerConfigTestCase extends AbstractMuleContextTestCase { private OAuthService service; private HttpClient httpClient; private HttpServer httpServer; @Inject private LockFactory lockFactory; public DancerConfigTestCase() { setStartContext(true); } @Override protected boolean doTestClassInjection() { return true; } private ArgumentCaptor<RequestHandler> requestHandlerCaptor = forClass(RequestHandler.class); @Before public void before() throws Exception { final HttpService httpService = mock(HttpService.class); final HttpClientFactory httpClientFactory = mock(HttpClientFactory.class); httpClient = mock(HttpClient.class); when(httpClientFactory.create(any())).thenReturn(httpClient); when(httpService.getClientFactory()).thenReturn(httpClientFactory); final HttpServerFactory httpServerFactory = mock(HttpServerFactory.class); httpServer = mock(HttpServer.class); when(httpServer.addRequestHandler(anyString(), requestHandlerCaptor.capture())) .thenReturn(mock(RequestHandlerManager.class)); when(httpServer.addRequestHandler(any(), anyString(), requestHandlerCaptor.capture())) .thenReturn(mock(RequestHandlerManager.class)); when(httpServerFactory.create(any())).thenReturn(httpServer); when(httpService.getServerFactory()).thenReturn(httpServerFactory); service = new DefaultOAuthService(httpService, mock(SchedulerService.class)); final HttpResponse httpResponse = mock(HttpResponse.class); final InputStreamHttpEntity httpEntity = mock(InputStreamHttpEntity.class); when(httpEntity.getContent()).thenReturn(new ReaderInputStream(new StringReader(""))); when(httpResponse.getEntity()).thenReturn(httpEntity); when(httpClient.send(any(), anyInt(), anyBoolean(), any())).thenReturn(httpResponse); } @Test public void clientCredentialsMinimal() throws MuleException { final OAuthClientCredentialsDancerBuilder builder = baseClientCredentialsDancerBuilder(); builder.tokenUrl("http://host/token"); Object minimalDancer = startDancer(builder); verify(httpClient).start(); stopIfNeeded(minimalDancer); verify(httpClient).stop(); } @Test public void authorizationCodeMinimal() throws MuleException, MalformedURLException { final OAuthAuthorizationCodeDancerBuilder builder = baseAuthCodeDancerbuilder(); minimalAuthCodeConfig(builder); Object minimalDancer = startDancer(builder); verify(httpClient).start(); stopIfNeeded(minimalDancer); verify(httpClient).stop(); } private void minimalAuthCodeConfig(final OAuthAuthorizationCodeDancerBuilder builder) throws MalformedURLException { builder.tokenUrl("http://host/token"); builder.authorizationUrl("http://host/auth"); builder.localCallback(new URL("http://localhost:8080/localCallback")); } @Test public void clientCredentialsReuseHttpClient() throws MuleException, MalformedURLException { final OAuthAuthorizationCodeDancerBuilder builder = baseAuthCodeDancerbuilder(); minimalAuthCodeConfig(builder); builder.tokenUrl(httpClient, "http://host/token"); Object minimalDancer = startDancer(builder); verify(httpClient, never()).start(); stopIfNeeded(minimalDancer); verify(httpClient, never()).stop(); } @Test public void authorizationCodeReuseHttpClient() throws MuleException, MalformedURLException { final OAuthAuthorizationCodeDancerBuilder builder = baseAuthCodeDancerbuilder(); minimalAuthCodeConfig(builder); builder.tokenUrl(httpClient, "http://host/token"); builder.authorizationUrl("http://host/auth"); builder.localCallback(new URL("http://localhost:8080/localCallback")); Object minimalDancer = startDancer(builder); verify(httpClient, never()).start(); stopIfNeeded(minimalDancer); verify(httpClient, never()).stop(); } @Test public void authorizationCodeReuseHttpServer() throws MuleException, IOException { final OAuthAuthorizationCodeDancerBuilder builder = baseAuthCodeDancerbuilder(); minimalAuthCodeConfig(builder); builder.tokenUrl("http://host/token"); builder.authorizationUrl("http://host/auth"); builder.localCallback(httpServer, "/localCallback"); Object minimalDancer = startDancer(builder); verify(httpServer).start(); verify(httpServer).addRequestHandler(eq(singleton(GET.name())), eq("/localCallback"), any()); stopIfNeeded(minimalDancer); verify(httpServer).stop(); } @Test public void authorizationCodeBeforeCallback() throws MuleException, IOException { AtomicBoolean beforeCallbackCalled = new AtomicBoolean(false); String resourceOwner = "someOwner"; final OAuthAuthorizationCodeDancerBuilder builder = baseAuthCodeDancerbuilder(); minimalAuthCodeConfig(builder); builder.beforeDanceCallback(r -> { assertThat(r.getResourceOwnerId(), is(resourceOwner)); assertThat(r.getAuthorizationUrl(), is("http://host/auth")); assertThat(r.getTokenUrl(), is("http://host/token")); assertThat(r.getClientId(), is("clientId")); assertThat(r.getClientSecret(), is("clientSecret")); assertThat(r.getScopes(), is(nullValue())); assertThat(r.getState(), is(empty())); beforeCallbackCalled.set(true); return null; }); startDancer(builder); configureRequestHandler(resourceOwner, ""); assertThat(beforeCallbackCalled.get(), is(true)); } @Test public void authorizationCodeBeforeCallbackWithState() throws MuleException, IOException { AtomicBoolean beforeCallbackCalled = new AtomicBoolean(false); String originalState = "originalState"; String resourceOwner = "someOwner"; final OAuthAuthorizationCodeDancerBuilder builder = baseAuthCodeDancerbuilder(); minimalAuthCodeConfig(builder); builder.beforeDanceCallback(r -> { assertThat(r.getState().get(), is(originalState)); beforeCallbackCalled.set(true); return null; }); startDancer(builder); configureRequestHandler(resourceOwner, originalState); assertThat(beforeCallbackCalled.get(), is(true)); } @Test public void authorizationCodeBeforeCallbackWithScopes() throws MuleException, IOException { AtomicBoolean beforeCallbackCalled = new AtomicBoolean(false); String resourceOwner = "someOwner"; final OAuthAuthorizationCodeDancerBuilder builder = baseAuthCodeDancerbuilder(); minimalAuthCodeConfig(builder); builder.scopes("aScope"); builder.beforeDanceCallback(r -> { assertThat(r.getScopes(), is("aScope")); beforeCallbackCalled.set(true); return null; }); startDancer(builder); configureRequestHandler(resourceOwner, ""); assertThat(beforeCallbackCalled.get(), is(true)); } @Test public void authorizationCodeAfterCallback() throws MuleException, IOException { AtomicBoolean afterCallbackCalled = new AtomicBoolean(false); String resourceOwner = "someOwner"; final OAuthAuthorizationCodeDancerBuilder builder = baseAuthCodeDancerbuilder(); minimalAuthCodeConfig(builder); builder.responseAccessTokenExpr("someAccessToken").responseExpiresInExpr("someExpiresIn") .responseRefreshTokenExpr("someRefreshToken"); builder.afterDanceCallback((vars, ctx) -> { assertThat(ctx.getAccessToken(), is("someAccessToken")); assertThat(ctx.getExpiresIn(), is("someExpiresIn")); assertThat(ctx.getRefreshToken(), is("someRefreshToken")); assertThat(ctx.getResourceOwnerId(), is(resourceOwner)); assertThat(ctx.getState(), is(nullValue())); assertThat(ctx.getTokenResponseParameters(), equalTo(emptyMap())); afterCallbackCalled.set(true); }); startDancer(builder); configureRequestHandler(resourceOwner, ""); assertThat(afterCallbackCalled.get(), is(true)); } @Test public void authorizationCodeAfterCallbackWithState() throws MuleException, IOException { AtomicBoolean afterCallbackCalled = new AtomicBoolean(false); String originalState = "originalState"; String resourceOwner = "someOwner"; final OAuthAuthorizationCodeDancerBuilder builder = baseAuthCodeDancerbuilder(); minimalAuthCodeConfig(builder); builder.responseAccessTokenExpr("someAccessToken").responseExpiresInExpr("someExpiresIn") .responseRefreshTokenExpr("someRefreshToken"); builder.afterDanceCallback((vars, ctx) -> { assertThat(ctx.getState(), is(originalState)); afterCallbackCalled.set(true); }); startDancer(builder); configureRequestHandler(resourceOwner, originalState); assertThat(afterCallbackCalled.get(), is(true)); } @Test public void authorizationCodeAfterCallbackWithTokenResponseParams() throws MuleException, IOException { AtomicBoolean afterCallbackCalled = new AtomicBoolean(false); String resourceOwner = "someOwner"; final OAuthAuthorizationCodeDancerBuilder builder = baseAuthCodeDancerbuilder(); minimalAuthCodeConfig(builder); builder.responseAccessTokenExpr("someAccessToken").responseExpiresInExpr("someExpiresIn") .responseRefreshTokenExpr("someRefreshToken"); builder.customParametersExtractorsExprs(singletonMap("someKey", "someValue")); builder.afterDanceCallback((vars, ctx) -> { assertThat(ctx.getTokenResponseParameters(), equalTo(singletonMap("someKey", "someValue"))); afterCallbackCalled.set(true); }); startDancer(builder); configureRequestHandler(resourceOwner, ""); assertThat(afterCallbackCalled.get(), is(true)); } @Test public void authorizationCodeBeforeAfterCallbackVariables() throws MuleException, IOException { AtomicBoolean afterCallbackCalled = new AtomicBoolean(false); String resourceOwner = "someOwner"; AuthorizationCodeDanceCallbackContext callbackContext = paramKey -> empty(); final OAuthAuthorizationCodeDancerBuilder builder = baseAuthCodeDancerbuilder(); minimalAuthCodeConfig(builder); builder.beforeDanceCallback(r -> { return callbackContext; }); builder.afterDanceCallback((callbackCtx, ctx) -> { assertThat(callbackCtx, sameInstance(callbackContext)); afterCallbackCalled.set(true); }); startDancer(builder); configureRequestHandler(resourceOwner, ""); assertThat(afterCallbackCalled.get(), is(true)); } @Test public void multipleDancersShareTokensStore() throws MalformedURLException, InitialisationException, MuleException { final Map<String, Object> tokensStore = new HashMap<>(); final MuleExpressionLanguage el = mock(MuleExpressionLanguage.class); final OAuthAuthorizationCodeDancerBuilder builder1 = service .authorizationCodeGrantTypeDancerBuilder(lockFactory, tokensStore, el); final OAuthAuthorizationCodeDancerBuilder builder2 = service .authorizationCodeGrantTypeDancerBuilder(lockFactory, tokensStore, el); builder1.clientCredentials("clientId", "clientSecret"); builder2.clientCredentials("clientId", "clientSecret"); minimalAuthCodeConfig(builder1); minimalAuthCodeConfig(builder2); builder1.resourceOwnerIdTransformer(roid -> "conn1-" + roid); builder2.resourceOwnerIdTransformer(roid -> "conn2-" + roid); final AuthorizationCodeOAuthDancer dancer1 = startDancer(builder1); final AuthorizationCodeOAuthDancer dancer2 = startDancer(builder2); final DefaultResourceOwnerOAuthContext contextOwnerConn1 = new DefaultResourceOwnerOAuthContext( new ReentrantLock(), "owner"); final DefaultResourceOwnerOAuthContext contextOwnerConn2 = new DefaultResourceOwnerOAuthContext( new ReentrantLock(), "owner"); tokensStore.put("conn1-owner", contextOwnerConn1); tokensStore.put("conn2-owner", contextOwnerConn2); assertThat(dancer1.getContextForResourceOwner("owner"), sameInstance(contextOwnerConn1)); assertThat(dancer2.getContextForResourceOwner("owner"), sameInstance(contextOwnerConn2)); } private OAuthClientCredentialsDancerBuilder baseClientCredentialsDancerBuilder() { final OAuthClientCredentialsDancerBuilder builder = service.clientCredentialsGrantTypeDancerBuilder( lockFactory, new HashMap<>(), mock(MuleExpressionLanguage.class)); builder.clientCredentials("clientId", "clientSecret"); return builder; } private OAuthAuthorizationCodeDancerBuilder baseAuthCodeDancerbuilder() { final OAuthAuthorizationCodeDancerBuilder builder = service.authorizationCodeGrantTypeDancerBuilder( lockFactory, new HashMap<>(), mock(MuleExpressionLanguage.class)); builder.clientCredentials("clientId", "clientSecret"); return builder; } private <D> D startDancer(final OAuthDancerBuilder<D> builder) throws InitialisationException, MuleException { final D dancer = builder.build(); initialiseIfNeeded(dancer); startIfNeeded(dancer); return dancer; } private void configureRequestHandler(String resourceOwner, String state) { HttpRequest authorizationRequest = mock(HttpRequest.class); MultiMap<String, String> authReqQueryParams = new MultiMap<>(); authReqQueryParams.put(STATE_PARAMETER, state + RESOURCE_OWNER_PARAM_NAME_ASSIGN + resourceOwner); authReqQueryParams.put(CODE_PARAMETER, ""); when(authorizationRequest.getQueryParams()).thenReturn(authReqQueryParams); HttpRequestContext authorizationRequestContext = mock(HttpRequestContext.class); when(authorizationRequestContext.getRequest()).thenReturn(authorizationRequest); requestHandlerCaptor.getAllValues().get(0).handleRequest(authorizationRequestContext, mock(HttpResponseReadyCallback.class)); } }