/* * SoapUI, Copyright (C) 2004-2016 SmartBear Software * * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * * http://ec.europa.eu/idabc/eupl * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the Licence for the specific language governing permissions and limitations * under the Licence. */ package com.eviware.soapui.impl.rest.actions.oauth; import com.eviware.soapui.SoapUI; import com.eviware.soapui.impl.rest.OAuth2Profile; import com.eviware.soapui.impl.wsdl.support.http.HttpClientSupport; import com.eviware.soapui.support.StringUtils; import com.eviware.soapui.support.TimeUtils; import org.apache.oltu.oauth2.client.OAuthClient; import org.apache.oltu.oauth2.client.request.OAuthClientRequest; import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse; import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.types.GrantType; import org.apache.oltu.oauth2.common.token.OAuthToken; import org.apache.oltu.oauth2.common.utils.OAuthUtils; import org.apache.oltu.oauth2.httpclient4.HttpClient4; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; /** * */ public class OAuth2TokenExtractor { public static final String CODE = "code"; public static final String TITLE = "<TITLE>"; public static final String TOKEN = "token"; public static final String ACCESS_TOKEN = "access_token"; protected List<BrowserListener> browserListeners = new ArrayList<BrowserListener>(); public void extractAccessToken(final OAuth2Parameters parameters) throws OAuthSystemException, MalformedURLException, URISyntaxException, OAuthProblemException { OAuth2Profile.OAuth2Flow i = parameters.getOAuth2Flow(); if (i.equals(OAuth2Profile.OAuth2Flow.IMPLICIT_GRANT)) { extractAccessTokenForImplicitGrantFlow(parameters); } else if (i.equals(OAuth2Profile.OAuth2Flow.RESOURCE_OWNER_PASSWORD_CREDENTIALS)) { extractAccessTokenForROPC(parameters); } else if (i.equals(OAuth2Profile.OAuth2Flow.AUTHORIZATION_CODE_GRANT)) { extractAccessTokenForAuthorizationCodeGrantFlow(parameters); } else if (i.equals(OAuth2Profile.OAuth2Flow.CLIENT_CREDENTIALS_GRANT)) { extractAccessTokenForClientCredentialsGrant(parameters); } else { throw OAuthProblemException.error("Unsupported OAuth 2.0 grant flow"); } } void extractAccessTokenForAuthorizationCodeGrantFlow(final OAuth2Parameters parameters) throws URISyntaxException, MalformedURLException, OAuthSystemException { final UserBrowserFacade browserFacade = getBrowserFacade(); addBrowserInteractionHandler(browserFacade, parameters); addExternalListeners(browserFacade); browserFacade.addBrowserListener(new BrowserListenerAdapter() { @Override public void locationChanged(String newLocation) { getAccessTokenAndSaveToProfile(browserFacade, parameters, extractAuthorizationCodeFromForm(extractFormData(newLocation), CODE)); } @Override public void contentChanged(String newContent) { int titlePosition = newContent.indexOf(TITLE); if (titlePosition != -1) { String title = newContent.substring(titlePosition + TITLE.length(), newContent.indexOf("</TITLE>")); getAccessTokenAndSaveToProfile(browserFacade, parameters, extractAuthorizationCodeFromTitle(title)); } } @Override public void browserClosed() { super.browserClosed(); if (!parameters.isAccessTokenRetrivedFromServer()) { setRetrievedCanceledStatus(parameters); } } }); browserFacade.open(new URI(createAuthorizationURL(parameters, CODE)).toURL()); parameters.waitingForAuthorization(); } void extractAccessTokenForROPC(final OAuth2Parameters parameters) throws OAuthProblemException, OAuthSystemException { OAuthClientRequest accessTokenRequest = getClientRequestForROPC(parameters); OAuthClient oAuthClient = getOAuthClient(); OAuthToken oAuthToken = oAuthClient.accessToken(accessTokenRequest, OAuthJSONAccessTokenResponse.class).getOAuthToken(); parameters.applyRetrievedAccessToken(oAuthToken.getAccessToken()); parameters.setAccessTokenIssuedTimeInProfile(TimeUtils.getCurrentTimeInSeconds()); parameters.setAccessTokenExpirationTimeInProfile(oAuthToken.getExpiresIn()); parameters.setRefreshTokenInProfile(oAuthToken.getRefreshToken()); } public OAuthClientRequest getClientRequestForROPC(final OAuth2Parameters parameters) throws OAuthSystemException { OAuthClientRequest accessTokenRequest = OAuthClientRequest .tokenLocation(parameters.accessTokenUri) .setGrantType(GrantType.PASSWORD) .setClientId(parameters.clientId) .setClientSecret(parameters.clientSecret) .setUsername(parameters.resourceOwnerName) .setPassword(parameters.resourceOwnerPassword) .setScope(parameters.scope) .buildBodyMessage(); return accessTokenRequest; } public void extractAccessTokenForClientCredentialsGrant(final OAuth2Parameters parameters) throws OAuthProblemException, OAuthSystemException { OAuthClientRequest accessTokenRequest = getClientRequestForClientCredentialsGrant(parameters); OAuthClient oAuthClient = getOAuthClient(); OAuthToken oAuthToken = oAuthClient.accessToken(accessTokenRequest, OAuthJSONAccessTokenResponse.class).getOAuthToken(); parameters.applyRetrievedAccessToken(oAuthToken.getAccessToken()); parameters.setAccessTokenIssuedTimeInProfile(TimeUtils.getCurrentTimeInSeconds()); parameters.setAccessTokenExpirationTimeInProfile(oAuthToken.getExpiresIn()); parameters.setRefreshTokenInProfile(oAuthToken.getRefreshToken()); } public OAuthClientRequest getClientRequestForClientCredentialsGrant(final OAuth2Parameters parameters) throws OAuthSystemException { OAuthClientRequest accessTokenRequest = OAuthClientRequest .tokenLocation(parameters.accessTokenUri) .setGrantType(GrantType.CLIENT_CREDENTIALS) .setClientId(parameters.clientId) .setClientSecret(parameters.clientSecret) .setScope(parameters.scope) .buildBodyMessage(); return accessTokenRequest; } void extractAccessTokenForImplicitGrantFlow(final OAuth2Parameters parameters) throws OAuthSystemException, URISyntaxException, MalformedURLException { final UserBrowserFacade browserFacade = getBrowserFacade(); addBrowserInteractionHandler(browserFacade, parameters); addExternalListeners(browserFacade); browserFacade.addBrowserListener(new BrowserListenerAdapter() { @Override public void locationChanged(String newLocation) { String accessToken = extractAuthorizationCodeFromForm(extractFormData(newLocation), ACCESS_TOKEN); if (!StringUtils.isNullOrEmpty(accessToken)) { parameters.setAccessTokenInProfile(accessToken); parameters.setRefreshTokenInProfile(null); parameters.setAccessTokenExpirationTimeInProfile(0); parameters.setAccessTokenIssuedTimeInProfile(TimeUtils.getCurrentTimeInSeconds()); browserFacade.close(); } } @Override public void browserClosed() { super.browserClosed(); if (!parameters.isAccessTokenRetrivedFromServer()) { setRetrievedCanceledStatus(parameters); } } }); browserFacade.open(new URI(createAuthorizationURL(parameters, TOKEN)).toURL()); parameters.waitingForAuthorization(); } void refreshAccessToken(OAuth2Parameters parameters) throws OAuthProblemException, OAuthSystemException { OAuthClientRequest accessTokenRequest = OAuthClientRequest .tokenLocation(parameters.accessTokenUri) .setGrantType(GrantType.REFRESH_TOKEN) .setClientId(parameters.clientId) .setClientSecret(parameters.clientSecret) .setRefreshToken(parameters.refreshToken) .buildBodyMessage(); OAuthClient oAuthClient = getOAuthClient(); OAuthToken oAuthToken = oAuthClient.accessToken(accessTokenRequest, OAuthJSONAccessTokenResponse.class).getOAuthToken(); parameters.applyRetrievedAccessToken(oAuthToken.getAccessToken()); parameters.setAccessTokenIssuedTimeInProfile(TimeUtils.getCurrentTimeInSeconds()); } public void addBrowserListener(BrowserListener listener) { browserListeners.add(listener); } protected OAuthClient getOAuthClient() { return new OAuthClient(new HttpClient4(HttpClientSupport.getHttpClient())); } protected UserBrowserFacade getBrowserFacade() { return new WebViewUserBrowserFacade(); } /* Helper methods */ private void setRetrievedCanceledStatus(OAuth2Parameters parameters) { parameters.retrivalCanceled(); } private void addExternalListeners(UserBrowserFacade browserFacade) { for (BrowserListener browserListener : browserListeners) { browserFacade.addBrowserListener(browserListener); } } private void addBrowserInteractionHandler(UserBrowserFacade browserFacade, OAuth2Parameters parameters) { if (parameters.getJavaScripts().isEmpty()) { return; } browserFacade.addBrowserListener(new BrowserInteractionMonitor(browserFacade, parameters.getJavaScripts())); } private String createAuthorizationURL(OAuth2Parameters parameters, String responseType) throws OAuthSystemException { return OAuthClientRequest .authorizationLocation(parameters.authorizationUri) .setClientId(parameters.clientId) .setResponseType(responseType) .setScope(parameters.scope) .setRedirectURI(parameters.redirectUri) .buildQueryMessage().getLocationUri(); } private String extractFormData(String url) { int questionMarkIndex = url.indexOf('?'); if (questionMarkIndex != -1) { return url.substring(questionMarkIndex + 1); } int hashIndex = url.indexOf("#"); if (hashIndex != -1) { return url.substring(hashIndex + 1); } return ""; } private String extractAuthorizationCodeFromTitle(String title) { if (title.contains("code=")) { return title.substring(title.indexOf("code=") + 5); } return null; } private String extractAuthorizationCodeFromForm(String formData, String parameterName) { return (String) OAuthUtils.decodeForm(formData).get(parameterName); } private void getAccessTokenAndSaveToProfile(UserBrowserFacade browserFacade, OAuth2Parameters parameters, String authorizationCode) { if (authorizationCode != null) { try { parameters.receivedAuthorizationCode(); OAuthClientRequest accessTokenRequest = OAuthClientRequest .tokenLocation(parameters.accessTokenUri) .setGrantType(GrantType.AUTHORIZATION_CODE) .setClientId(parameters.clientId) .setClientSecret(parameters.clientSecret) .setRedirectURI(parameters.redirectUri) .setCode(authorizationCode) .buildBodyMessage(); OAuthToken token = getOAuthClient().accessToken(accessTokenRequest, OAuth2AccessTokenResponse.class) .getOAuthToken(); if (token != null && token.getAccessToken() != null) { parameters.setAccessTokenInProfile(token.getAccessToken()); parameters.setRefreshTokenInProfile(token.getRefreshToken()); if (token.getExpiresIn() != null) { parameters.setAccessTokenExpirationTimeInProfile(token.getExpiresIn()); } parameters.setAccessTokenIssuedTimeInProfile(TimeUtils.getCurrentTimeInSeconds()); browserFacade.close(); } } catch (OAuthSystemException e) { SoapUI.logError(e); } catch (OAuthProblemException e) { SoapUI.logError(e); } } } /* Helper class that runs automation JavaScripts registered in the OAuth2 profile */ private class BrowserInteractionMonitor extends BrowserListenerAdapter { private final List<String> javaScripts; int pageIndex = 0; private UserBrowserFacade browserFacade; public BrowserInteractionMonitor(UserBrowserFacade browserFacade, List<String> javaScripts) { this.browserFacade = browserFacade; this.javaScripts = javaScripts; } @Override public void contentChanged(String newContent) { if (javaScripts.size() > pageIndex) { String script = javaScripts.get(pageIndex); try { browserFacade.executeJavaScript(script); } catch (Exception e) { SoapUI.log.warn("Error when running JavaScript [" + script + "]: " + e.getMessage()); } pageIndex++; } } } }