com.facebook.buck.apple.toolchain.AbstractProvisioningProfileStore.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.apple.toolchain.AbstractProvisioningProfileStore.java

Source

/*
 * Copyright 2015-present Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.facebook.buck.apple.toolchain;

import com.dd.plist.NSArray;
import com.dd.plist.NSDictionary;
import com.dd.plist.NSObject;
import com.facebook.buck.core.rulekey.AddToRuleKey;
import com.facebook.buck.core.rulekey.AddsToRuleKey;
import com.facebook.buck.core.toolchain.Toolchain;
import com.facebook.buck.core.util.immutables.BuckStyleImmutable;
import com.facebook.buck.core.util.log.Logger;
import com.facebook.buck.util.types.Pair;
import com.google.common.base.Joiner;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.HashCode;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import org.immutables.value.Value;

/** A collection of provisioning profiles. */
@Value.Immutable(builder = false, copy = false)
@BuckStyleImmutable
public abstract class AbstractProvisioningProfileStore implements AddsToRuleKey, Toolchain {
    public static final Optional<ImmutableMap<String, NSObject>> MATCH_ANY_ENTITLEMENT = Optional.empty();
    public static final Optional<ImmutableList<CodeSignIdentity>> MATCH_ANY_IDENTITY = Optional.empty();

    public static final String DEFAULT_NAME = "apple-provisioning-profiles";

    private static final Logger LOG = Logger.get(ProvisioningProfileStore.class);

    private static final ImmutableSet<String> FORCE_INCLUDE_ENTITLEMENTS = ImmutableSet.of("keychain-access-groups",
            "application-identifier", "com.apple.developer.associated-domains",
            "com.apple.developer.icloud-container-development-container-identifiers",
            "com.apple.developer.icloud-container-environment", "com.apple.developer.icloud-container-identifiers",
            "com.apple.developer.icloud-services", "com.apple.developer.ubiquity-container-identifiers",
            "com.apple.developer.ubiquity-kvstore-identifier");

    @Value.Parameter
    public abstract Supplier<ImmutableList<ProvisioningProfileMetadata>> getProvisioningProfilesSupplier();

    @AddToRuleKey
    public ImmutableList<ProvisioningProfileMetadata> getProvisioningProfiles() {
        return getProvisioningProfilesSupplier().get();
    }

    public Optional<ProvisioningProfileMetadata> getProvisioningProfileByUUID(String provisioningProfileUUID) {
        for (ProvisioningProfileMetadata profile : getProvisioningProfiles()) {
            if (profile.getUUID().equals(provisioningProfileUUID)) {
                return Optional.of(profile);
            }
        }
        return Optional.empty();
    }

    private static boolean matchesOrArrayIsSubsetOf(@Nullable NSObject lhs, @Nullable NSObject rhs) {
        if (lhs == null) {
            return (rhs == null);
        }

        if (lhs instanceof NSArray && rhs instanceof NSArray) {
            List<NSObject> lhsList = Arrays.asList(((NSArray) lhs).getArray());
            List<NSObject> rhsList = Arrays.asList(((NSArray) rhs).getArray());
            return rhsList.containsAll(lhsList);
        }

        return lhs.equals(rhs);
    }

    private String getStringFromNSObject(@Nullable NSObject obj) {
        if (obj == null) {
            return "(not set)" + System.lineSeparator();
        } else if (obj instanceof NSArray) {
            return ((NSArray) obj).toASCIIPropertyList();
        } else if (obj instanceof NSDictionary) {
            return ((NSDictionary) obj).toASCIIPropertyList();
        } else {
            return obj.toString() + System.lineSeparator();
        }
    }

    // If multiple valid ones, find the one which matches the most specifically.  I.e.,
    // XXXXXXXXXX.com.example.* will match over XXXXXXXXXX.* for com.example.TestApp
    public Optional<ProvisioningProfileMetadata> getBestProvisioningProfile(String bundleID, ApplePlatform platform,
            Optional<ImmutableMap<String, NSObject>> entitlements,
            Optional<? extends Iterable<CodeSignIdentity>> identities, StringBuffer diagnosticsBuffer) {
        Optional<String> prefix;
        ImmutableList.Builder<String> lines = ImmutableList.builder();
        if (entitlements.isPresent()) {
            prefix = ProvisioningProfileMetadata.prefixFromEntitlements(entitlements.get());
        } else {
            prefix = Optional.empty();
        }

        int bestMatchLength = -1;
        Optional<ProvisioningProfileMetadata> bestMatch = Optional.empty();

        lines.add(String.format("Looking for a provisioning profile for bundle ID %s", bundleID));

        boolean atLeastOneMatch = false;
        for (ProvisioningProfileMetadata profile : getProvisioningProfiles()) {
            Pair<String, String> appID = profile.getAppID();

            LOG.debug("Looking at provisioning profile " + profile.getUUID() + "," + appID);

            if (!prefix.isPresent() || prefix.get().equals(appID.getFirst())) {
                String profileBundleID = appID.getSecond();
                boolean match;
                if (profileBundleID.endsWith("*")) {
                    // Chop the ending * if wildcard.
                    profileBundleID = profileBundleID.substring(0, profileBundleID.length() - 1);
                    match = bundleID.startsWith(profileBundleID);
                } else {
                    match = (bundleID.equals(profileBundleID));
                }

                if (!match) {
                    LOG.debug("Ignoring non-matching ID for profile " + profile.getUUID() + ".  Expected: "
                            + profileBundleID + ", actual: " + bundleID);
                    continue;
                }

                atLeastOneMatch = true;
                if (!profile.getExpirationDate().after(new Date())) {
                    String message = "Ignoring expired profile " + profile.getUUID() + ": "
                            + profile.getExpirationDate();
                    LOG.debug(message);
                    lines.add(message);
                    continue;
                }

                Optional<String> platformName = platform.getProvisioningProfileName();
                if (platformName.isPresent() && !profile.getPlatforms().contains(platformName.get())) {
                    String message = "Ignoring incompatible platform " + platformName.get() + " for profile "
                            + profile.getUUID();
                    LOG.debug(message);
                    lines.add(message);
                    continue;
                }

                // Match against other keys of the entitlements.  Otherwise, we could potentially select
                // a profile that doesn't have all the needed entitlements, causing a error when
                // installing to device.
                //
                // For example: get-task-allow, aps-environment, etc.
                if (entitlements.isPresent()) {
                    ImmutableMap<String, NSObject> entitlementsDict = entitlements.get();
                    ImmutableMap<String, NSObject> profileEntitlements = profile.getEntitlements();
                    for (Entry<String, NSObject> entry : entitlementsDict.entrySet()) {
                        NSObject profileEntitlement = profileEntitlements.get(entry.getKey());
                        if (!(FORCE_INCLUDE_ENTITLEMENTS.contains(entry.getKey())
                                || matchesOrArrayIsSubsetOf(entry.getValue(), profileEntitlement))) {
                            match = false;
                            String profileEntitlementString = getStringFromNSObject(profileEntitlement);
                            String entryValueString = getStringFromNSObject(entry.getValue());
                            String message = "Profile " + profile.getProfilePath().getFileName() + " ("
                                    + profile.getUUID() + ") with bundleID " + profile.getAppID().getSecond()
                                    + " correctly matches. However there is a mismatched entitlement "
                                    + entry.getKey() + ";" + System.lineSeparator() + "value is: "
                                    + profileEntitlementString + "but expected: " + entryValueString;
                            LOG.debug(message);
                            lines.add(message);
                        }
                    }
                }

                // Reject any certificate which we know we can't sign with the supplied identities.
                ImmutableSet<HashCode> validFingerprints = profile.getDeveloperCertificateFingerprints();
                if (match && identities.isPresent() && !validFingerprints.isEmpty()) {
                    match = false;
                    for (CodeSignIdentity identity : identities.get()) {
                        Optional<HashCode> fingerprint = identity.getFingerprint();
                        if (fingerprint.isPresent() && validFingerprints.contains(fingerprint.get())) {
                            match = true;
                            break;
                        }
                    }

                    if (!match) {
                        String message = "Ignoring profile " + profile.getUUID()
                                + " because it can't be signed with any valid identity in the current keychain.";
                        LOG.debug(message);
                        lines.add(message);
                        continue;
                    }
                }

                if (match && profileBundleID.length() > bestMatchLength) {
                    bestMatchLength = profileBundleID.length();
                    bestMatch = Optional.of(profile);
                }
            }
        }

        if (!atLeastOneMatch) {
            lines.add(String.format("No provisioning profile matching the bundle ID %s was found", bundleID));
        }

        LOG.debug("Found provisioning profile " + bestMatch);
        ImmutableList<String> diagnostics = lines.build();
        diagnosticsBuffer.append(Joiner.on("\n").join(diagnostics));
        return bestMatch;
    }

    public static ProvisioningProfileStore empty() {
        return ProvisioningProfileStore.of(Suppliers.ofInstance(ImmutableList.of()));
    }

    @Override
    public String getName() {
        return DEFAULT_NAME;
    }
}