All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.alm.provider.JaxrsClientProvider Maven / Gradle / Ivy

Go to download

Convenience utilities that provides authentication data based on different types of authenticators from the core module

There is a newer version: 0.6.4
Show newest version
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root.

package com.microsoft.alm.provider;

import com.microsoft.alm.auth.Authenticator;
import com.microsoft.alm.auth.PromptBehavior;
import com.microsoft.alm.secret.Credential;
import com.microsoft.alm.secret.Token;
import com.microsoft.alm.secret.TokenPair;
import com.microsoft.alm.helpers.Debug;
import com.microsoft.alm.helpers.ObjectExtensions;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.glassfish.jersey.SslConfigurator;
import org.glassfish.jersey.apache.connector.ApacheClientProperties;
import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.RequestEntityProcessing;
import org.glassfish.jersey.client.spi.ConnectorProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import java.io.IOException;
import java.net.URI;

/**
 * Provides authenticated JAXRS client based on different authenticators
 *
 * In case of Credential and Personal Access Token type of authentication data, basic auth is used.  For PAT, the user
 * name is hardcoded to identify Personal Access Token authentication type instead of the user.
 *
 * In case of OAuth2 token, we embedded the token as a "Bearer" token in the Authorization header.
 */
public class JaxrsClientProvider {

    private static final Logger logger = LoggerFactory.getLogger(JaxrsClientProvider.class);

    private Authenticator authenticator;

    /**
     * Provides authenticated JAX RS clients based on {@link Authenticator} provided
     *
     * @param authenticator
     *      an authenticator that handles generates authentication data
     */
    public JaxrsClientProvider(final Authenticator authenticator) {
        this.authenticator = authenticator;
    }

    /**
     * Get a globally authenticated JAXRS client - a client that can potentially access all accounts the user owns with
     * ${@link PromptBehavior} AUTO.
     *
     * Be aware that a globally authenticated client DOES NOT mean the client can represent the principal and has
     * permission to everything the user owns.  If backed by {@link com.microsoft.alm.auth.pat.VstsPatAuthenticator}
     * the client is limited to the scopes defined in the Personal Access Token.  If no Personal Access {@link Token}
     * exists, it will generate one with the default {@link Options}.
     *
     * @return client
     *      authenticated JAXRS client.  {@code null} if authentication failed.
     */
    public Client getClient() {
        return getClient(PromptBehavior.AUTO, Options.getDefaultOptions());
    }

    /**
     * Get a globally authenticated JAXRS client - a client that can potentially access all accounts the user owns with
     * the specified {@link PromptBehavior} behavior.
     *
     * Be aware that a globally authenticated client DOES NOT mean the client can represent the principal and has
     * permission to everything the user owns.  If backed by {@link com.microsoft.alm.auth.pat.VstsPatAuthenticator}
     * the client is limited to the scopes defined in the Personal Access Token. If no Personal Access {@link Token}
     * exists, it will generate one with the specified {@link Options} if we allow PAT generation with the specified
     * prompt behavior.
     *
     * @param promptBehavior
     *      dictates we allow prompting the user or not.  In case of VstsPatAuthenticator, prompting also means generate
     *      a new PAT.
     * @param options
     *      options specified by users.
     *
     * @return client
     *      authenticated JAXRS client.  {@code null} if authentication failed.
     */
    public Client getClient(final PromptBehavior promptBehavior, final Options options) {
        Debug.Assert(promptBehavior != null, "promptBehavior cannot be null");
        Debug.Assert(options != null, "options cannot be null");

        Client client = null;

        logger.info("Getting a jaxrs client that works across multiple accounts.");
        if (authenticator.isOAuth2TokenSupported()) {
            logger.debug("Getting a jaxrs client backed by OAuth2 token.");
            final TokenPair tokenPair = authenticator.getOAuth2TokenPair(promptBehavior);
            client = getClientWithOAuth2RequestFilter(tokenPair);
        }
        // Get a client backed by a global PAT
        else if (authenticator.isPersonalAccessTokenSupported()) {
            logger.debug("Getting a jaxrs client backed by PersonalAccessToken.");
            final Token token = authenticator.getPersonalAccessToken(
                    options.patGenerationOptions.tokenScope,
                    options.patGenerationOptions.displayName,
                    promptBehavior);

            if (token != null) {
                client = getClientWithUsernamePassword(authenticator.getAuthType(), token.Value);
            }
        }

        logger.info("Successfully created an authenticated client? {}", client != null);

        return client;
    }

    /**
     * Get an authenticated JAXRS client for the specific account URI with {@link PromptBehavior} AUTO.
     *
     * If backed by {@link com.microsoft.alm.auth.pat.VstsPatAuthenticator} and no PAT exists,  will generate one
     * with default {@link Options}.
     *
     * @param uri
     *      target uri we want to send request against
     *
     * @return client
     *      authenticated JAXRS client.  {@code null} if authentication failed.
     */
    public Client getClientFor(final URI uri) {
        return getClientFor(uri, PromptBehavior.AUTO, Options.getDefaultOptions());
    }

    /**
     * Get an authenticated JAXRS client for the specified account with the specified {@link PromptBehavior} behavior.
     *
     * If backed by {@link com.microsoft.alm.auth.pat.VstsPatAuthenticator} and no PAT exists, will generate one with
     * the specified {@link Options} if we allow PAT generation with the specified prompt behavior.
     *
     * @param uri
     *      target uri we want to send request against
     * @param promptBehavior
     *      dictates we allow prompting the user or not.  In case of VstsPatAuthenticator, prompting also means generate
     *      a new PAT.
     * @param options
     *      options specified by users.
     *
     * @return client
     *      authenticated JAXRS client.  {@code null} if authentication failed.
     */
    public Client getClientFor(final URI uri, final PromptBehavior promptBehavior, final Options options) {
        Debug.Assert(uri != null, "uri cannot be null");
        Debug.Assert(promptBehavior != null, "promptBehavior cannot be null");
        Debug.Assert(options != null, "options cannot be null");

        Client client = null;
        logger.info("Getting a jaxrs client for uri: {}.", uri);

        if (authenticator.isCredentialSupported()) {
            logger.debug("Getting a jaxrs client backed by basic auth.");
            final Credential credential = authenticator.getCredential(uri, promptBehavior);
            if (credential != null) {
                client = getClientWithUsernamePassword(credential.Username, credential.Password);
            }
        }
        /*
         * Although this function calls for a URI specific client, the client returned by the OAuth2 provider is still
         * global as OAuth2 token is not scoped to one account
         */
        else if (authenticator.isOAuth2TokenSupported()) {
            logger.debug("Getting a jaxrs client backed by OAuth2 token.");
            final TokenPair tokenPair = authenticator.getOAuth2TokenPair(promptBehavior);
            client = getClientWithOAuth2RequestFilter(tokenPair);
        }

        else if (authenticator.isPersonalAccessTokenSupported()) {
            logger.debug("Getting a jaxrs client backed by PersonalAccessToken.");
            final Token token = authenticator.getPersonalAccessToken(
                    uri,
                    options.patGenerationOptions.tokenScope,
                    options.patGenerationOptions.displayName,
                    promptBehavior);

            if (token != null) {
                client = getClientWithUsernamePassword(authenticator.getAuthType(), token.Value);
            }
        }

        logger.debug("Successfully created an authenticated client for uri: {}? {}", uri, client != null);
        return client;
    }

    private Client getClientWithOAuth2RequestFilter(final TokenPair tokenPair) {
        // default Jersey client with HttpURLConnection as the connector
        final Client client;

        // add proxy information
        final ClientConfig clientConfig = new ClientConfig();
        addProxySettings(clientConfig);

        if (tokenPair != null && tokenPair.AccessToken != null) {
            client = ClientBuilder.newClient(clientConfig);

            client.register(new ClientRequestFilter() {
                @Override
                public void filter(final ClientRequestContext requestContext) throws IOException {
                    requestContext.getHeaders().putSingle("Authorization", "Bearer " + tokenPair.AccessToken.Value);
                }
            });
        } else {
            client = null;
        }

        return client;
    }

    private Client getClientWithUsernamePassword(final String username, final String password) {
        final ClientConfig clientConfig = getClientConfig(username, password);

        return ClientBuilder.newClient(clientConfig);
    }

    private ClientConfig getClientConfig(final String username, final String password) {
        Debug.Assert(username != null, "username cannot be null");
        Debug.Assert(password != null, "password cannot be null");

        final Credentials credentials
                = new UsernamePasswordCredentials(username, password);

        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, credentials);

        final ConnectorProvider connectorProvider = new ApacheConnectorProvider();

        final ClientConfig clientConfig = new ClientConfig().connectorProvider(connectorProvider);
        clientConfig.property(ApacheClientProperties.CREDENTIALS_PROVIDER, credentialsProvider);

        clientConfig.property(ApacheClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, true);
        clientConfig.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED);

        addProxySettings(clientConfig);

        return clientConfig;
    }

    private void addProxySettings(final ClientConfig clientConfig) {
        // favor http proxyHost
        final String proxyHost = System.getProperty("http.proxyHost");
        final String proxyPort = ObjectExtensions.coalesce(System.getProperty("http.proxyPort"), "8080");

        if (proxyHost != null) {
            final String proxyUrl = String.format("http://%s:%s", proxyHost, proxyPort);
            logger.debug("Proxy is set, adding proxy: {}", proxyUrl);

            clientConfig.property(ClientProperties.PROXY_URI, proxyUrl);

            final SslConfigurator sslConfigurator = getSslConfigurator();
            if (sslConfigurator != null) {
                logger.debug("Setting up ssl configurator.");
                clientConfig.property(ApacheClientProperties.SSL_CONFIG, sslConfigurator);
            }
        }
    }

    private SslConfigurator getSslConfigurator() {
        final String trustStore = System.getProperty("javax.net.ssl.trustStore");
        final String trustStorePassword = System.getProperty("javax.net.ssl.trustStorePassword");

        final SslConfigurator sslConfigurator;
        if (trustStore != null && trustStorePassword != null) {
            logger.debug("Setting up ssl configurator with trustStore: {}", trustStore);
            sslConfigurator = SslConfigurator.newInstance()
                    .trustStoreFile(trustStore)
                    .trustStorePassword(trustStorePassword)
                    .trustStoreType("JKS")
                    .trustManagerFactoryAlgorithm("PKIX")
                    .securityProtocol("SSL");

        } else {
            logger.debug("trustStore exists? {}, trustStorePassword is specified? {}",
                    trustStore != null,
                    trustStorePassword != null);
            sslConfigurator = null;
        }

        return sslConfigurator;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy