Forum Discussion

AubreyS's avatar
AubreyS
New Member
7 years ago

Is there any way to increase the timeout for OAuth2 access token retrieval with built-in Auth tab?

Hi all,

 

I am trying to figure out a way to increase the timeout for OAuth2 access token retrieval in SoapUI.  I have set up some OAuth2 profiles using SoapUI's Auth tab and have created a short automation script to automate that retrieval.  Functionally, the automation script works well.  However, it takes so long for the login page to load, that SoapUI "times out" on the token retrieval and then cancels the outgoing request that needed to get the token.  Looking in the SoapUI logs, this is what I see:

 

Thu Jan 18 12:02:59 MST 2018:INFO:The access token has expired, trying to retrieve a new one with JavaScript automation.
Thu Jan 18 12:03:05 MST 2018:WARN:OAuth2 access token retrieval timed out after 5000 ms
Thu Jan 18 12:03:05 MST 2018:ERROR:Exception in request: java.lang.RuntimeException: Unable to refresh expired access token.
Thu Jan 18 12:03:05 MST 2018:ERROR:An error occurred [Unable to refresh expired access token.], see error log for details

 

So, it would appear that my issue could be resolved if I could just figure out how to increase the timeout.  Has anyone figured out a way to do this?

 

Thanks!

  • JHunt's avatar
    JHunt
    Community Hero

    Here's something to try. It seems to work for me in that it doesn't give any script errors. But I really have no clue if it will have the desired effect and no way to test it.

     

    The plan is to address the OAuth timeout which is hardcoded into a class called OAuth2RequestFilter. Luckily, Groovy scripts let us interact with SoapUI's own class instances at run time. So we define a new class that's a copy of that one, but with a different timeout. Then tell SoapUI to use our class instead through its RequestTransportRegistry.

     

    If it does work, I think that as soon as you run this script once the change to the timeout will last until you reload SoapUI. So you could put it in the load script for your Project or something.

     

    import com.eviware.soapui.impl.wsdl.submit.filters.AbstractRequestFilter
    import com.eviware.soapui.impl.wsdl.submit.filters.OAuth2RequestFilter
    import com.eviware.soapui.impl.wsdl.submit.RequestTransportRegistry
    RequestTransportRegistry.getTransport("https").replaceRequestFilter(OAuth2RequestFilter, new modifiedOAuth2RequestFilter())


    /*
     *   below is a copy of class OAuth2RequestFilter with a new name as retrieved from
     *   https://github.com/SmartBear/soapui/blob/next/soapui/src/main/java/com/eviware/soapui/impl/wsdl/submit/filters/OAuth2RequestFilter.java
     *   except that the ACCESS_TOKEN_RETRIEVAL_TIMEOUT value has been changed from 5000 to 10000
     */   

    import com.eviware.soapui.config.TimeUnitConfig;
    import com.eviware.soapui.impl.rest.OAuth1Profile;
    import com.eviware.soapui.impl.rest.OAuth1ProfileContainer;
    import com.eviware.soapui.impl.rest.OAuth2Profile;
    import com.eviware.soapui.impl.rest.OAuth2ProfileContainer;
    import com.eviware.soapui.impl.rest.RestRequestInterface;
    import com.eviware.soapui.impl.rest.actions.oauth.GoogleOAuth1ClientFacade;
    import com.eviware.soapui.impl.rest.actions.oauth.OAuth1ClientFacade;
    import com.eviware.soapui.impl.rest.actions.oauth.OAuth2ClientFacade;
    import com.eviware.soapui.impl.rest.actions.oauth.OltuOAuth2ClientFacade;
    import com.eviware.soapui.impl.support.AbstractHttpRequest;
    import com.eviware.soapui.impl.wsdl.submit.transports.http.BaseHttpRequestTransport;
    import com.eviware.soapui.model.iface.SubmitContext;
    import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
    import com.eviware.soapui.support.StringUtils;
    import com.eviware.soapui.support.TimeUtils;
    import org.apache.http.client.methods.HttpRequestBase;
    import org.apache.log4j.Logger;

    import static com.eviware.soapui.config.CredentialsConfig.AuthType.O_AUTH_1_0;
    import static com.eviware.soapui.config.CredentialsConfig.AuthType.O_AUTH_2_0;

    public class modifiedOAuth2RequestFilter extends AbstractRequestFilter {
        private static final int ACCESS_TOKEN_RETRIEVAL_TIMEOUT = 10000;
        
        // intentionally left non-final to facilitate testing, but should not be modified in production!
        private static Logger log = Logger.getLogger(OAuth2RequestFilter.class);

        /* setLog() and getLog() should only be used for testing */

        static Logger getLog() {
            return log;
        }

        static void setLog(Logger newLog) {
            log = newLog;
        }

        @Override
        public void filterRestRequest(SubmitContext context, RestRequestInterface request) {

            HttpRequestBase httpMethod = (HttpRequestBase) context.getProperty(BaseHttpRequestTransport.HTTP_METHOD);

            if (O_AUTH_2_0.toString().equals(request.getAuthType())) {
                OAuth2ProfileContainer profileContainer = request.getResource().getService().getProject()
                        .getOAuth2ProfileContainer();
                OAuth2Profile profile = profileContainer.getProfileByName(((AbstractHttpRequest) request).getSelectedAuthProfile());
                if (profile == null || StringUtils.isNullOrEmpty(profile.getAccessToken())) {
                    return;
                }
                OAuth2ClientFacade oAuth2Client = getOAuth2ClientFacade();

                if (accessTokenIsExpired(profile)) {
                    if (profile.shouldReloadAccessTokenAutomatically()) {
                        reloadAccessToken(profile, oAuth2Client);
                    } else {
                        profile.setAccessTokenStatus(OAuth2Profile.AccessTokenStatus.EXPIRED);
                    }
                }
                oAuth2Client.applyAccessToken(profile, httpMethod, request.getRequestContent());
            } else if (O_AUTH_1_0.toString().equals(request.getAuthType())) {
                OAuth1ProfileContainer profileContainer = request.getResource().getService().getProject()
                        .getOAuth1ProfileContainer();
                OAuth1Profile profile = profileContainer.getProfileByName(
                        ((AbstractHttpRequest) request).getSelectedAuthProfile());

                if (profile == null || StringUtils.isNullOrEmpty(profile.getAccessToken())) {
                    return;
                }
                OAuth1ClientFacade oAuth1Client = getOAuth1ClientFacade();

                oAuth1Client.applyAccessToken(profile, httpMethod, request.getRequestContent());
            }
        }

        protected OAuth2ClientFacade getOAuth2ClientFacade() {
            return new OltuOAuth2ClientFacade();
        }

        protected OAuth1ClientFacade getOAuth1ClientFacade() {
            return new GoogleOAuth1ClientFacade();
        }

        private boolean accessTokenIsExpired(OAuth2Profile profile) {
            long currentTime = TimeUtils.getCurrentTimeInSeconds();
            long issuedTime = profile.getAccessTokenIssuedTime();
            long expirationTime;

            if (profile.useManualAccessTokenExpirationTime()) {
                String expirationTimeString = profile.getManualAccessTokenExpirationTime() == null ? "" : profile.getManualAccessTokenExpirationTime();
                String expandedValue = PropertyExpander.expandProperties(profile.getContainer().getProject(), expirationTimeString);
                expirationTime = convertExpirationTimeToSeconds(expandedValue, profile.getManualAccessTokenExpirationTimeUnit());
            } else {
                expirationTime = profile.getAccessTokenExpirationTime();
            }

            //10 second buffer to make sure that the access token doesn't expire by the time request is sent
            return !(issuedTime <= 0 || expirationTime <= 0) && expirationTime < (currentTime + 10) - issuedTime;
        }

        private long convertExpirationTimeToSeconds(String expirationTimeString, TimeUnitConfig.Enum timeUnit) throws IllegalArgumentException {
            long expirationTime;
            try {
                expirationTime = Long.valueOf(expirationTimeString.trim());
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException("Manual expiration time cannot be parsed due to invalid characters." +
                        "Please review it and make sure it is set correctly.", e);
            }
            if (timeUnit.equals(TimeUnitConfig.HOURS)) {
                return expirationTime * 3600;
            } else if (timeUnit.equals(TimeUnitConfig.MINUTES)) {
                return expirationTime * 60;
            } else {
                return expirationTime;
            }
        }

        private void reloadAccessToken(OAuth2Profile profile, OAuth2ClientFacade oAuth2Client) {
            try {
                if (profile.getRefreshToken() != null) {
                    log.info("The access token has expired, trying to refresh it.");
                    oAuth2Client.refreshAccessToken(profile);
                    log.info("The access token has been refreshed successfully.");
                } else {
                    if (profile.hasAutomationJavaScripts()) {
                        log.info("The access token has expired, trying to retrieve a new one with JavaScript automation.");
                        oAuth2Client.requestAccessToken(profile);
                        profile.waitForAccessTokenStatus(OAuth2Profile.AccessTokenStatus.RETRIEVED_FROM_SERVER,
                                ACCESS_TOKEN_RETRIEVAL_TIMEOUT);
                        if (profile.getAccessTokenStatus() == OAuth2Profile.AccessTokenStatus.RETRIEVED_FROM_SERVER) {
                            log.info("A new access token has been retrieved successfully.");
                        } else {
                            log.warn("OAuth2 access token retrieval timed out after " + ACCESS_TOKEN_RETRIEVAL_TIMEOUT + " ms");
                            throw new RuntimeException("OAuth2 access token retrieval timed out after " + ACCESS_TOKEN_RETRIEVAL_TIMEOUT + " ms");
                        }
                    } else {
                        log.warn("No automation JavaScripts added to OAuth2 profile – cannot retrieve new access token");
                        throw new RuntimeException("No automation JavaScripts added to OAuth2 profile – cannot retrieve new access token");
                    }
                }
            } catch (Exception e) {
                //Propagate it up so that it is shown as a failure message in test case log
                throw new RuntimeException("Unable to refresh expired access token.", e);
            }
        }
    }