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

com.microsoft.alm.oauth2.useragent.UserAgentImpl Maven / Gradle / Ivy

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root.

package com.microsoft.alm.oauth2.useragent;

import com.microsoft.alm.oauth2.useragent.subprocess.DefaultProcessFactory;
import com.microsoft.alm.oauth2.useragent.subprocess.ProcessCoordinator;
import com.microsoft.alm.oauth2.useragent.subprocess.TestableProcess;
import com.microsoft.alm.oauth2.useragent.subprocess.TestableProcessFactory;
import com.microsoft.alm.oauth2.useragent.utils.PackageLocator;
import com.microsoft.alm.oauth2.useragent.utils.StringHelper;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class UserAgentImpl implements UserAgent, ProviderScanner {

    static final String REQUEST_AUTHORIZATION_CODE = "requestAuthorizationCode";
    static final String JAVA_VERSION_STRING = System.getProperty("java.version");
    static final String JAVA_HOME = System.getProperty("java.home");
    static final String PATH_SEPARATOR = System.getProperty("path.separator");
    static final String NEW_LINE = System.getProperty("line.separator");
    static final String UTF_8 = "UTF-8";
    static final String USER_AGENT_PROVIDER_PROPERTY_NAME = "userAgentProvider";

    private static final String[] EMPTY_STRING_ARRAY = new String[0];
    private static final Set NETWORKING_PROPERTY_NAMES;
    private static final Map SAFE_REPLACEMENTS;

    static {
        // http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html
        final Set networkingPropertyNames = new HashSet();
        // HTTP
        networkingPropertyNames.add("http.proxyHost");
        networkingPropertyNames.add("http.proxyPort");
        networkingPropertyNames.add("http.nonProxyHosts");

        // HTTPS
        networkingPropertyNames.add("https.proxyHost");
        networkingPropertyNames.add("https.proxyPort");

        // SOCKS
        networkingPropertyNames.add("socksProxyHost");
        networkingPropertyNames.add("socksProxyPort");
        networkingPropertyNames.add("socksProxyVersion");
        networkingPropertyNames.add("java.net.socks.username");
        networkingPropertyNames.add("java.net.socks.password");

        networkingPropertyNames.add("java.net.useSystemProxies");

        // Misc HTTP properties
        networkingPropertyNames.add("http.auth.ntlm.domain");
        NETWORKING_PROPERTY_NAMES = Collections.unmodifiableSet(networkingPropertyNames);

        final HashMap map = new HashMap();
        map.put("+", " ");
        map.put("%28", "(");
        map.put("%29", ")");
        map.put("%2F", "/");
        map.put("%3A", ":");
        map.put("%3B", ";");
        map.put("%5C", "\\");
        map.put("%7C", "|");
        SAFE_REPLACEMENTS = Collections.unmodifiableMap(map);
    }

    private final TestableProcessFactory processFactory;
    private final List candidateProviders;
    private final LinkedHashMap> requirementsByProvider = new LinkedHashMap>();
    private Provider provider;
    private boolean hasScannedAtLeastOnce = false;
    private String userAgentProvider = null;

    public UserAgentImpl() {
        this(new DefaultProcessFactory(), null, Provider.PROVIDERS);
    }

    UserAgentImpl(final TestableProcessFactory processFactory, final Provider provider, final List candidateProviders) {
        this.processFactory = processFactory;
        this.candidateProviders = candidateProviders;
        this.provider = provider;
    }

    @Override
    public Provider findCompatibleProvider() {
        final String userAgentProvider = System.getProperty(USER_AGENT_PROVIDER_PROPERTY_NAME);
        return findCompatibleProvider(userAgentProvider, true);
    }

    @Override
    public Provider findCompatibleProvider(final String userAgentProvider) {
        return findCompatibleProvider(userAgentProvider, true);
    }

    Provider findCompatibleProvider(final String userAgentProvider, final boolean checkOverrideIsCompatible) {
        if (provider == null || !StringHelper.equal(this.userAgentProvider, userAgentProvider)) {
            requirementsByProvider.clear();
            provider = scanProviders(userAgentProvider, candidateProviders, requirementsByProvider, checkOverrideIsCompatible);
            hasScannedAtLeastOnce = true;
            this.userAgentProvider = userAgentProvider;
        }
        return provider;
    }

    @Override
    public Map> getUnmetProviderRequirements() {
        if (!hasScannedAtLeastOnce) {
            findCompatibleProvider();
        }

        final Map> copy = new LinkedHashMap>(requirementsByProvider);
        final Map> result = Collections.unmodifiableMap(copy);
        return result;
    }

    @Override
    public boolean hasCompatibleProvider() {
        findCompatibleProvider();
        return provider != null;
    }

    @Override
    public boolean hasCompatibleProvider(final String userAgentProvider) {
        findCompatibleProvider(userAgentProvider);
        return provider != null;
    }

    @Override
    public AuthorizationResponse requestAuthorizationCode(final URI authorizationEndpoint, final URI redirectUri)
            throws AuthorizationException {
        return encode(REQUEST_AUTHORIZATION_CODE, authorizationEndpoint.toString(), redirectUri.toString());
    }

    AuthorizationResponse encode(final String methodName, final String... parameters)
            throws AuthorizationException {
        final ArrayList command = new ArrayList();
        final ArrayList classPath = new ArrayList();
        // TODO: should we append ".exe" on Windows?
        command.add(new File(JAVA_HOME, "bin/java").getAbsolutePath());
        final String userAgentProvider = System.getProperty(USER_AGENT_PROVIDER_PROPERTY_NAME);
        findCompatibleProvider(userAgentProvider, false);
        if (provider == null) {
            throwUnsupported(requirementsByProvider);
        }
        provider.augmentProcessParameters(command, classPath);

        relayProperties(System.getProperties(), NETWORKING_PROPERTY_NAMES, command);

        // Locate our class to add it to the classPath
        final PackageLocator locator = new PackageLocator();
        final File classPathEntryFile = locator.locatePackage(UserAgentImpl.class);
        final String classPathEntry = classPathEntryFile.getAbsolutePath();
        classPath.add(classPathEntry);

        addClassPathToCommand(classPath, command, PATH_SEPARATOR);
        command.add("com.microsoft.alm.oauth2.useragent." + provider.getClassName());
        command.add(methodName);
        //noinspection ToArrayCallWithZeroLengthArrayArgument
        final String[] args = command.toArray(EMPTY_STRING_ARRAY);

        try {
            final TestableProcess process = processFactory.create(args);
            final ProcessCoordinator coordinator = new ProcessCoordinator(process);
            for (final String parameter : parameters) {
                coordinator.println(parameter);
            }
            coordinator.waitFor();

            final String response = coordinator.getStdOut();
            final String errorContents = coordinator.getStdErr();
            return AuthorizationResponse.fromString(response, errorContents);
        }
        catch (final IOException e) {
            throw new AuthorizationException("io_exception", e.getMessage(), null, e);
        }
        catch (final InterruptedException e) {
            throw new AuthorizationException("interrupted_exception", e.getMessage(), null, e);
        }
    }

    static void relayProperties(final Properties properties, final Set propertyNames, final List destinationCommand) {
        for (final String propertyName : propertyNames) {
            final String propertyValue = properties.getProperty(propertyName);
            if (propertyValue != null) {
                destinationCommand.add("-D" + propertyName + "=" + propertyValue);
            }
        }
    }

    void addClassPathToCommand(final List classPath, final List command, final String pathSeparator) {
        command.add("-classpath");
        //noinspection ToArrayCallWithZeroLengthArrayArgument
        final String[] classPathComponents = classPath.toArray(EMPTY_STRING_ARRAY);
        final String classPathString = StringHelper.join(pathSeparator, classPathComponents);
        command.add(classPathString);
    }

    static void throwUnsupported(final Map> unmetRequirements) {
        final StringBuilder sb = buildUnsupportedMessage(unmetRequirements);

        throw new IllegalStateException(sb.toString());
    }

    public static StringBuilder buildUnsupportedMessage(Map> unmetRequirements) {
        final StringBuilder sb = new StringBuilder("I don't support your platform yet.");
        describeUnmetRequirements(unmetRequirements, sb);
        sb.append(NEW_LINE);
        sb.append("Please send details about your operating system version, Java version, 32- vs. 64-bit, etc.");
        sb.append(NEW_LINE);
        sb.append("The following System Properties and Environment Variables would be very useful.");
        sb.append(NEW_LINE);

        final Properties properties = System.getProperties();
        appendProperties(properties, sb);
        sb.append(NEW_LINE);

        final Map variables = System.getenv();
        appendVariables(variables, sb);
        return sb;
    }

    static Provider scanProviders(final String userAgentProvider, final List providers, final Map> destinationUnmetRequirements, final boolean checkOverrideIsCompatible) {

        Provider result = null;

        if (userAgentProvider != null) {
            for (final Provider provider : providers) {
                if (provider.getClassName().equals(userAgentProvider)) {
                    if (checkOverrideIsCompatible) {
                        final List requirements = provider.checkRequirements();
                        if (requirements == null || requirements.size() == 0) {
                            result = provider;
                        }
                    }
                    else {
                        result = provider;
                    }
                    break;
                }
            }
        }

        for (final Provider provider : providers) {
            final List requirements = provider.checkRequirements();
            if (requirements == null || requirements.size() == 0) {
                if (result == null) {
                    result = provider;
                }
            }
            else {
                destinationUnmetRequirements.put(provider, requirements);
            }
        }

        return result;
    }

    public static void describeUnmetRequirements(final Map> unmetRequirements, final StringBuilder destination) {
        for (final Map.Entry> pair: unmetRequirements.entrySet()) {
            final Provider provider = pair.getKey();
            final List requirements = pair.getValue();
            if (requirements != null && requirements.size() > 0) {
                destination.append(NEW_LINE);
                destination.append("Unmet requirements for the '").append(provider.getClassName()).append("' provider:").append(NEW_LINE);
                for (final String requirement : requirements) {
                    destination.append(" - ").append(requirement).append(NEW_LINE);
                }
            }
        }
    }

    static void appendProperties(final Properties properties, final StringBuilder destination) {
        final String header = "# --- BEGIN SYSTEM PROPERTIES ---";
        final String footer = "# ---- END SYSTEM PROPERTIES ----";
        final Set keys = properties.stringPropertyNames();

        appendPairs(keys, properties, destination, header, footer);
    }

    static void appendVariables(final Map variables, final StringBuilder destination) {
        final String header = "# --- BEGIN ENVIRONMENT VARIABLES ---";
        final String footer = "# ---- END ENVIRONMENT VARIABLES ----";
        final Set keys = variables.keySet();

        appendPairs(keys, variables, destination, header, footer);
    }

    static void appendPairs(final Set keys, final Map pairs, final StringBuilder destination, final String header, final String footer) {
        destination.append(header).append(NEW_LINE).append(NEW_LINE);
        final String[] keyArray = new String[keys.size()];
        keys.toArray(keyArray);
        Arrays.sort(keyArray);
        for (final String key : keyArray) {
            final String encodedKey = sortOfUrlEncode(key);

            final String value = (String) pairs.get(key);
            final String encodedValue = value == null ? "" : sortOfUrlEncode(value);

            destination.append(encodedKey).append('=').append(encodedValue).append(NEW_LINE);
        }
        destination.append(NEW_LINE).append(footer).append(NEW_LINE);
    }

    static String sortOfUrlEncode(final String s) {
        try {
            //noinspection UnnecessaryLocalVariable
            final String encoded = URLEncoder.encode(s, UTF_8);
            // encode() goes too far for our purposes, so undo some common ones for easier raw inspection
            String result = encoded;
            for (final Map.Entry entry : SAFE_REPLACEMENTS.entrySet()) {
                result = result.replace(entry.getKey(), entry.getValue());
            }
            return result;
        }
        catch (final UnsupportedEncodingException e) {
            throw new Error(e);
        }
    }

    static void decode(final UserAgent target, final String[] args, final InputStream inputStream, final OutputStream outputStream) {
        final PrintStream printStream = new PrintStream(outputStream);
        final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        try {
            final String methodName = args[0];

            if (REQUEST_AUTHORIZATION_CODE.equals(methodName)) {
                final URI authorizationEndpoint = new URI(bufferedReader.readLine());
                final URI redirectUri = new URI(bufferedReader.readLine());

                final AuthorizationResponse result = target.requestAuthorizationCode(authorizationEndpoint, redirectUri);

                printStream.println(result.toString());
            }
        }
        catch (final AuthorizationException e) {
            printStream.println(AuthorizationException.toString(e.getCode(), e.getDescription(), e.getUri()));
        }
        catch (final IOException e) {
            printStream.println(AuthorizationException.toString("io_exception", e.getMessage(), null));
        }
        catch (final URISyntaxException e) {
            printStream.println(AuthorizationException.toString("uri_syntax_exception", e.getMessage(), null));
        }
        printStream.flush();
    }

    static String extractResponseFromRedirectUri(final String redirectedUri) {
        final URI uri = URI.create(redirectedUri);
        return uri.getQuery();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy