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

com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemetryClient Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 */

package com.microsoft.azure.toolkit.lib.common.telemetry;

import com.microsoft.applicationinsights.TelemetryClient;
import com.microsoft.azure.toolkit.lib.Azure;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.microsoft.azure.toolkit.lib.common.action.Action.RESOURCE_TYPE;

@Getter
public class AzureTelemetryClient {
    public static final String ARCH_KEY = "arch";
    public static final String JDK_KEY = "jdk";

    private static final String[] SYSTEM_PROPERTIES = new String[]{RESOURCE_TYPE};
    // refers https://github.com/microsoft/vscode-extension-telemetry/blob/main/src/telemetryReporter.ts
    private static final String FILE_PATH_REGEX =
        "(file://)?([a-zA-Z]:(\\\\\\\\|\\\\|/)|(\\\\\\\\|\\\\|/))?([\\w-._]+(\\\\\\\\|\\\\|/))+[\\w-._]*";
    private static final Pattern FILE_PATH_PATTERN = Pattern.compile(FILE_PATH_REGEX);
    // refers https://github.com/microsoft/vscode-extension-telemetry/blob/v0.6.2/src/common/baseTelemetryReporter.ts#L241
    private static final Pattern GOOGLE_API_KEY = Pattern.compile("AIza[a-zA-Z0-9_\\\\-]{35}");
    private static final Pattern EMAIL_PATTERN = Pattern.compile("@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-]+");
    private static final Pattern SECRET_PATTERN = Pattern.compile("(key|token|sig|secret|signature|password|passwd|pwd|android:value)[^a-zA-Z0-9]", Pattern.CASE_INSENSITIVE);
    private static final Pattern TOKEN_REGEX = Pattern.compile("xox[pbar]-[a-zA-Z0-9]", Pattern.CASE_INSENSITIVE);
    private static final Pattern GITHUB_TOKEN_REGEX = Pattern.compile("(gh[psuro]_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59})");
    private static final Pattern CLI_CREDENTIALS_REGEX = Pattern.compile("((login|psexec|(certutil|psexec)\\.exe).{1,50}(\\s-u(ser(name)?)?\\s+.{3,100})?\\s-(admin|user|vm|root)?p(ass(word)?)?\\s+[\"']?[^$\\-/\\s]|(^|[\\s\\r\\n\\\\])net(\\.exe)?.{1,5}(user\\s+|share\\s+/user:| user -? secrets ? set) \\s + [^ $\\s/])");


    private static final Map PATTERN_MAP = new HashMap() {{
        put(EMAIL_PATTERN, "");
        put(SECRET_PATTERN, "");
        put(TOKEN_REGEX, "");
        put(GOOGLE_API_KEY, "");
        put(GITHUB_TOKEN_REGEX, "");
        put(CLI_CREDENTIALS_REGEX, "");
    }};

    @Getter
    @Setter(AccessLevel.PACKAGE)
    private String eventNamePrefix;
    @Nonnull
    private final Map defaultProperties = new HashMap() {
        {
            put(ARCH_KEY, System.getProperty("os.arch"));
            put(JDK_KEY, System.getProperty("java.version"));
        }
    };

    public AzureTelemetryClient() {
        final AzureTelemetryConfigProvider provider = loadConfigProvider();
        if (Objects.nonNull(provider)) {
            this.defaultProperties.putAll(provider.getCommonProperties());
            this.eventNamePrefix = provider.getEventNamePrefix();
        } else {
            this.eventNamePrefix = "AzurePlugin";
        }
    }

    @Nullable
    private static AzureTelemetryConfigProvider loadConfigProvider() {
        final ClassLoader current = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(AzureTelemetryClient.class.getClassLoader());
            final ServiceLoader loader = ServiceLoader.load(AzureTelemetryConfigProvider.class, AzureTelemetryClient.class.getClassLoader());
            final Iterator iterator = loader.iterator();
            if (iterator.hasNext()) {
                return iterator.next();
            }
            return null;
        } finally {
            Thread.currentThread().setContextClassLoader(current);
        }
    }

    public void addDefaultProperty(@Nonnull String key, @Nonnull String value) {
        if (StringUtils.isEmpty(key)) {
            return;
        }
        defaultProperties.put(key, value);
    }

    public void addDefaultProperties(@Nonnull Map properties) {
        defaultProperties.putAll(properties);
    }

    public boolean isEnabled() {
        return BooleanUtils.isNotFalse(Azure.az().config().getTelemetryEnabled());
    }

    public void trackEvent(@Nonnull final String eventName) {
        trackEvent(eventName, null, null, false);
    }

    public void trackEvent(@Nonnull final String eventName, @Nullable final Map customProperties) {
        trackEvent(eventName, customProperties, null, false);
    }

    public void trackEvent(@Nonnull final String eventName, @Nullable final Map customProperties, @Nullable final Map metrics) {
        trackEvent(eventName, customProperties, metrics, false);
    }

    public void trackEvent(@Nonnull final String eventName, @Nullable final Map customProperties, @Nullable final Map metrics,
                           final boolean overrideDefaultProperties) {
        if (!isEnabled()) {
            return;
        }

        final Map properties = mergeProperties(getDefaultProperties(), customProperties, overrideDefaultProperties);
        properties.entrySet().removeIf(stringStringEntry -> StringUtils.isEmpty(stringStringEntry.getValue())); // filter out null values
        anonymizePersonallyIdentifiableInformation(properties);
        Optional.ofNullable(getClient()).ifPresent(client -> {
            client.trackEvent(eventName, properties, metrics);
            client.flush();
        });
    }

    protected Map mergeProperties(Map defaultProperties,
                                                  Map customProperties,
                                                  boolean overrideDefaultProperties) {
        if (customProperties == null) {
            return defaultProperties;
        }
        final Map merged = new HashMap<>();
        if (overrideDefaultProperties) {
            merged.putAll(defaultProperties);
            merged.putAll(customProperties);
        } else {
            merged.putAll(customProperties);
            merged.putAll(defaultProperties);
        }
        return merged;
    }

    protected static void anonymizePersonallyIdentifiableInformation(final Map properties) {
        properties.replaceAll((key, value) -> {
            if (StringUtils.isBlank(value) || StringUtils.equalsAnyIgnoreCase(key, SYSTEM_PROPERTIES)) {
                return value;
            }
            return Arrays.stream(value.split("\\r?\\n"))
                .map(AzureTelemetryClient::anonymizePiiData).collect(Collectors.joining(StringUtils.LF));
        });
    }

    public static String anonymizePiiData(@Nonnull final String input) {
        final String result = FILE_PATH_PATTERN.matcher(input).replaceAll("");
        for (final Pattern pattern : PATTERN_MAP.keySet()) {
            if (pattern.matcher(result).find()) {
                return PATTERN_MAP.get(pattern);
            }
        }
        return result;
    }

    @Nullable
    private TelemetryClient getClient() {
        return isEnabled() ? ClientHolder.client : null;
    }

    private static class ClientHolder {
        private static final TelemetryClient client = new TelemetryClient();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy