2018-10-31 22:05:07 +03:00
|
|
|
using System;
|
2016-04-21 16:40:25 +03:00
|
|
|
using System.IO;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
using Microsoft.Build.Framework;
|
|
|
|
|
2020-03-14 00:46:28 +03:00
|
|
|
using Xamarin.Localization.MSBuild;
|
2016-04-21 16:40:25 +03:00
|
|
|
|
|
|
|
namespace Xamarin.MacDev.Tasks
|
|
|
|
{
|
2020-05-13 16:44:59 +03:00
|
|
|
public abstract class CompileEntitlementsTaskBase : XamarinTask
|
2016-04-21 16:40:25 +03:00
|
|
|
{
|
|
|
|
bool warnedTeamIdentifierPrefix;
|
|
|
|
bool warnedAppIdentifierPrefix;
|
|
|
|
|
|
|
|
#region Inputs
|
|
|
|
|
|
|
|
[Required]
|
|
|
|
public string AppBundleDir { get; set; }
|
|
|
|
|
|
|
|
[Required]
|
|
|
|
public string AppIdentifier { get; set; }
|
|
|
|
|
|
|
|
[Required]
|
|
|
|
public string BundleIdentifier { get; set; }
|
|
|
|
|
|
|
|
[Required]
|
2018-10-31 22:05:07 +03:00
|
|
|
public ITaskItem CompiledEntitlements { get; set; }
|
2016-04-21 16:40:25 +03:00
|
|
|
|
2020-08-10 14:55:54 +03:00
|
|
|
public bool Debug { get; set; }
|
|
|
|
|
2016-04-21 16:40:25 +03:00
|
|
|
public string Entitlements { get; set; }
|
|
|
|
|
|
|
|
[Required]
|
|
|
|
public bool IsAppExtension { get; set; }
|
|
|
|
|
|
|
|
public string ProvisioningProfile { get; set; }
|
|
|
|
|
2020-08-10 14:55:54 +03:00
|
|
|
[Required]
|
|
|
|
public string SdkDevPath { get; set; }
|
|
|
|
|
|
|
|
public bool SdkIsSimulator { get; set; }
|
|
|
|
|
2018-01-17 23:18:34 +03:00
|
|
|
[Required]
|
|
|
|
public string SdkPlatform { get; set; }
|
|
|
|
|
2016-04-21 16:40:25 +03:00
|
|
|
[Required]
|
|
|
|
public string SdkVersion { get; set; }
|
|
|
|
|
2020-08-10 14:55:54 +03:00
|
|
|
[Output]
|
|
|
|
public ITaskItem EntitlementsInExecutable { get; set; }
|
|
|
|
|
|
|
|
[Output]
|
|
|
|
public ITaskItem EntitlementsInSignature { get; set; }
|
|
|
|
|
2016-04-21 16:40:25 +03:00
|
|
|
#endregion
|
|
|
|
|
|
|
|
protected abstract string ApplicationIdentifierKey { get; }
|
|
|
|
|
|
|
|
protected abstract string DefaultEntitlementsPath { get; }
|
|
|
|
|
|
|
|
protected abstract HashSet<string> AllowedProvisioningKeys { get; }
|
|
|
|
|
|
|
|
protected abstract string EntitlementBundlePath { get; }
|
|
|
|
|
|
|
|
protected virtual bool MergeProfileEntitlements {
|
|
|
|
get { return true; }
|
|
|
|
}
|
|
|
|
|
2017-06-08 21:01:22 +03:00
|
|
|
PString MergeEntitlementString (PString pstr, MobileProvision profile, bool expandWildcards)
|
2016-04-21 16:40:25 +03:00
|
|
|
{
|
|
|
|
string TeamIdentifierPrefix;
|
|
|
|
string AppIdentifierPrefix;
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty (pstr.Value))
|
|
|
|
return (PString) pstr.Clone ();
|
|
|
|
|
|
|
|
if (profile == null) {
|
|
|
|
if (!warnedTeamIdentifierPrefix && pstr.Value.Contains ("$(TeamIdentifierPrefix)")) {
|
2020-03-14 00:46:28 +03:00
|
|
|
Log.LogWarning (null, null, null, Entitlements, 0, 0, 0, 0, MSBStrings.W0108);
|
2016-04-21 16:40:25 +03:00
|
|
|
warnedTeamIdentifierPrefix = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!warnedAppIdentifierPrefix && pstr.Value.Contains ("$(AppIdentifierPrefix)")) {
|
2020-03-14 00:46:28 +03:00
|
|
|
Log.LogWarning (null, null, null, Entitlements, 0, 0, 0, 0, MSBStrings.W0109);
|
2016-04-21 16:40:25 +03:00
|
|
|
warnedAppIdentifierPrefix = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (profile != null && profile.ApplicationIdentifierPrefix.Count > 0)
|
|
|
|
AppIdentifierPrefix = profile.ApplicationIdentifierPrefix[0] + ".";
|
|
|
|
else
|
|
|
|
AppIdentifierPrefix = string.Empty;
|
|
|
|
|
|
|
|
if (profile != null && profile.TeamIdentifierPrefix.Count > 0)
|
|
|
|
TeamIdentifierPrefix = profile.TeamIdentifierPrefix[0] + ".";
|
|
|
|
else
|
|
|
|
TeamIdentifierPrefix = AppIdentifierPrefix;
|
|
|
|
|
|
|
|
var customTags = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase) {
|
|
|
|
{ "TeamIdentifierPrefix", TeamIdentifierPrefix },
|
|
|
|
{ "AppIdentifierPrefix", AppIdentifierPrefix },
|
|
|
|
{ "CFBundleIdentifier", BundleIdentifier },
|
|
|
|
};
|
|
|
|
|
|
|
|
var expanded = StringParserService.Parse (pstr.Value, customTags);
|
|
|
|
|
2017-06-08 21:01:22 +03:00
|
|
|
if (expandWildcards && expanded.IndexOf ('*') != -1) {
|
|
|
|
int asterisk = expanded.IndexOf ('*');
|
|
|
|
string prefix;
|
|
|
|
|
|
|
|
if (expanded.StartsWith (TeamIdentifierPrefix, StringComparison.Ordinal))
|
|
|
|
prefix = TeamIdentifierPrefix;
|
|
|
|
else if (expanded.StartsWith (AppIdentifierPrefix, StringComparison.Ordinal))
|
|
|
|
prefix = AppIdentifierPrefix;
|
|
|
|
else
|
|
|
|
prefix = string.Empty;
|
|
|
|
|
|
|
|
var baseBundleIdentifier = expanded.Substring (prefix.Length, asterisk - prefix.Length);
|
|
|
|
|
|
|
|
if (!BundleIdentifier.StartsWith (baseBundleIdentifier, StringComparison.Ordinal))
|
|
|
|
expanded = expanded.Replace ("*", BundleIdentifier);
|
|
|
|
else
|
|
|
|
expanded = prefix + BundleIdentifier;
|
|
|
|
}
|
|
|
|
|
2016-04-21 16:40:25 +03:00
|
|
|
return new PString (expanded);
|
|
|
|
}
|
|
|
|
|
|
|
|
PArray MergeEntitlementArray (PArray array, MobileProvision profile)
|
|
|
|
{
|
|
|
|
var result = new PArray ();
|
|
|
|
|
|
|
|
foreach (var item in array) {
|
|
|
|
PObject value;
|
|
|
|
|
|
|
|
if (item is PDictionary)
|
|
|
|
value = MergeEntitlementDictionary ((PDictionary) item, profile);
|
|
|
|
else if (item is PString)
|
2017-06-08 21:01:22 +03:00
|
|
|
value = MergeEntitlementString ((PString) item, profile, false);
|
2016-04-21 16:40:25 +03:00
|
|
|
else if (item is PArray)
|
|
|
|
value = MergeEntitlementArray ((PArray) item, profile);
|
|
|
|
else
|
|
|
|
value = item.Clone ();
|
|
|
|
|
|
|
|
if (value != null)
|
|
|
|
result.Add (value);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result.Count > 0)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
PDictionary MergeEntitlementDictionary (PDictionary dict, MobileProvision profile)
|
|
|
|
{
|
|
|
|
var result = new PDictionary ();
|
|
|
|
|
|
|
|
foreach (var item in dict) {
|
|
|
|
PObject value = item.Value;
|
|
|
|
|
|
|
|
if (value is PDictionary)
|
|
|
|
value = MergeEntitlementDictionary ((PDictionary) value, profile);
|
|
|
|
else if (value is PString)
|
2017-06-08 21:01:22 +03:00
|
|
|
value = MergeEntitlementString ((PString) value, profile, false);
|
2016-04-21 16:40:25 +03:00
|
|
|
else if (value is PArray)
|
|
|
|
value = MergeEntitlementArray ((PArray) value, profile);
|
|
|
|
else
|
|
|
|
value = value.Clone ();
|
|
|
|
|
|
|
|
if (value != null)
|
|
|
|
result.Add (item.Key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-12-15 23:59:55 +03:00
|
|
|
static bool AreEqual (byte[] x, byte[] y)
|
|
|
|
{
|
|
|
|
if (x.Length != y.Length)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (int i = 0; i < x.Length; i++) {
|
|
|
|
if (x[i] != y[i])
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-04-21 16:40:25 +03:00
|
|
|
static void WriteXcent (PObject doc, string path)
|
|
|
|
{
|
|
|
|
var buf = doc.ToByteArray (false);
|
|
|
|
|
2016-12-15 23:59:55 +03:00
|
|
|
using (var stream = new MemoryStream ()) {
|
2016-04-21 16:40:25 +03:00
|
|
|
stream.Write (buf, 0, buf.Length);
|
2016-12-15 23:59:55 +03:00
|
|
|
|
|
|
|
var src = stream.ToArray ();
|
|
|
|
bool save;
|
|
|
|
|
|
|
|
// Note: if the destination file already exists, only re-write it if the content will change
|
|
|
|
if (File.Exists (path)) {
|
|
|
|
var dest = File.ReadAllBytes (path);
|
|
|
|
|
|
|
|
save = !AreEqual (src, dest);
|
|
|
|
} else {
|
|
|
|
save = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (save)
|
|
|
|
File.WriteAllBytes (path, src);
|
2016-04-21 16:40:25 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual PDictionary GetCompiledEntitlements (MobileProvision profile, PDictionary template)
|
|
|
|
{
|
|
|
|
var entitlements = new PDictionary ();
|
|
|
|
|
|
|
|
if (profile != null && MergeProfileEntitlements) {
|
|
|
|
// start off with the settings from the provisioning profile
|
|
|
|
foreach (var item in profile.Entitlements) {
|
|
|
|
if (!AllowedProvisioningKeys.Contains (item.Key))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
var value = item.Value;
|
|
|
|
|
|
|
|
if (item.Key == "com.apple.developer.icloud-container-environment")
|
|
|
|
value = new PString ("Development");
|
|
|
|
else if (value is PDictionary)
|
|
|
|
value = MergeEntitlementDictionary ((PDictionary) value, profile);
|
|
|
|
else if (value is PString)
|
2017-06-08 21:01:22 +03:00
|
|
|
value = MergeEntitlementString ((PString) value, profile, item.Key == ApplicationIdentifierKey);
|
2016-04-21 16:40:25 +03:00
|
|
|
else if (value is PArray)
|
|
|
|
value = MergeEntitlementArray ((PArray) value, profile);
|
|
|
|
else
|
|
|
|
value = value.Clone ();
|
|
|
|
|
|
|
|
if (value != null)
|
|
|
|
entitlements.Add (item.Key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// merge in the user's values
|
|
|
|
foreach (var item in template) {
|
|
|
|
var value = item.Value;
|
|
|
|
|
|
|
|
if (item.Key == "com.apple.developer.ubiquity-container-identifiers" ||
|
|
|
|
item.Key == "com.apple.developer.icloud-container-identifiers" ||
|
|
|
|
item.Key == "com.apple.developer.icloud-container-environment" ||
|
|
|
|
item.Key == "com.apple.developer.icloud-services") {
|
2016-05-13 12:19:49 +03:00
|
|
|
if (profile == null)
|
2020-03-14 00:46:28 +03:00
|
|
|
Log.LogWarning (null, null, null, Entitlements, 0, 0, 0, 0, MSBStrings.W0110, item.Key);
|
2016-05-13 12:19:49 +03:00
|
|
|
else if (!profile.Entitlements.ContainsKey (item.Key))
|
2020-03-14 00:46:28 +03:00
|
|
|
Log.LogWarning (null, null, null, Entitlements, 0, 0, 0, 0, MSBStrings.W0111, item.Key);
|
2016-04-21 16:40:25 +03:00
|
|
|
} else if (item.Key == ApplicationIdentifierKey) {
|
|
|
|
var str = value as PString;
|
|
|
|
|
|
|
|
// Ignore ONLY if it is empty, otherwise take the user's value
|
|
|
|
if (str == null || string.IsNullOrEmpty (str.Value))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (value is PDictionary)
|
|
|
|
value = MergeEntitlementDictionary ((PDictionary) value, profile);
|
|
|
|
else if (value is PString)
|
2017-06-08 21:01:22 +03:00
|
|
|
value = MergeEntitlementString ((PString) value, profile, item.Key == ApplicationIdentifierKey);
|
2016-04-21 16:40:25 +03:00
|
|
|
else if (value is PArray)
|
|
|
|
value = MergeEntitlementArray ((PArray) value, profile);
|
|
|
|
else
|
|
|
|
value = value.Clone ();
|
|
|
|
|
|
|
|
if (value != null)
|
|
|
|
entitlements[item.Key] = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
return entitlements;
|
|
|
|
}
|
|
|
|
|
|
|
|
static PDictionary GetArchivedExpandedEntitlements (PDictionary template, PDictionary compiled)
|
|
|
|
{
|
|
|
|
var allowed = new HashSet<string> ();
|
|
|
|
|
|
|
|
// the template (user-supplied Entitlements.plist file) is used to create a whitelist of keys
|
|
|
|
allowed.Add ("com.apple.developer.icloud-container-environment");
|
|
|
|
foreach (var item in template)
|
|
|
|
allowed.Add (item.Key);
|
|
|
|
|
|
|
|
// now we duplicate the allowed keys from the compiled xcent file
|
|
|
|
var archived = new PDictionary ();
|
|
|
|
|
|
|
|
foreach (var item in compiled) {
|
|
|
|
if (allowed.Contains (item.Key))
|
|
|
|
archived.Add (item.Key, item.Value.Clone ());
|
|
|
|
}
|
|
|
|
|
|
|
|
return archived;
|
|
|
|
}
|
|
|
|
|
2016-12-15 22:39:24 +03:00
|
|
|
protected virtual MobileProvision GetMobileProvision (MobileProvisionPlatform platform, string name)
|
2016-04-21 16:40:25 +03:00
|
|
|
{
|
2016-12-15 22:39:24 +03:00
|
|
|
return MobileProvisionIndex.GetMobileProvision (platform, name);
|
2016-04-21 16:40:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
public override bool Execute ()
|
|
|
|
{
|
2018-01-17 23:18:34 +03:00
|
|
|
MobileProvisionPlatform platform;
|
2016-04-21 16:40:25 +03:00
|
|
|
MobileProvision profile;
|
|
|
|
PDictionary template;
|
|
|
|
PDictionary compiled;
|
|
|
|
PDictionary archived;
|
|
|
|
string path;
|
2016-12-15 23:59:55 +03:00
|
|
|
bool save;
|
2016-04-21 16:40:25 +03:00
|
|
|
|
2018-01-17 23:18:34 +03:00
|
|
|
switch (SdkPlatform) {
|
|
|
|
case "AppleTVSimulator":
|
|
|
|
case "AppleTVOS":
|
|
|
|
platform = MobileProvisionPlatform.tvOS;
|
|
|
|
break;
|
|
|
|
case "iPhoneSimulator":
|
|
|
|
case "WatchSimulator":
|
|
|
|
case "iPhoneOS":
|
|
|
|
case "WatchOS":
|
|
|
|
platform = MobileProvisionPlatform.iOS;
|
|
|
|
break;
|
|
|
|
case "MacOSX":
|
|
|
|
platform = MobileProvisionPlatform.MacOS;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
Log.LogError ("Unknown SDK platform: {0}", SdkPlatform);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-04-21 16:40:25 +03:00
|
|
|
if (!string.IsNullOrEmpty (ProvisioningProfile)) {
|
2018-01-17 23:18:34 +03:00
|
|
|
if ((profile = GetMobileProvision (platform, ProvisioningProfile)) == null) {
|
2020-03-14 00:46:28 +03:00
|
|
|
Log.LogError (MSBStrings.E0049, ProvisioningProfile);
|
2016-04-21 16:40:25 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
profile = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty (Entitlements)) {
|
|
|
|
if (!File.Exists (Entitlements)) {
|
2020-03-14 00:46:28 +03:00
|
|
|
Log.LogError (MSBStrings.E0112, Entitlements);
|
2016-04-21 16:40:25 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
path = Entitlements;
|
|
|
|
} else {
|
|
|
|
path = DefaultEntitlementsPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
template = PDictionary.FromFile (path);
|
|
|
|
} catch (Exception ex) {
|
2020-03-14 00:46:28 +03:00
|
|
|
Log.LogError (MSBStrings.E0113, path, ex.Message);
|
2016-04-21 16:40:25 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
compiled = GetCompiledEntitlements (profile, template);
|
|
|
|
archived = GetArchivedExpandedEntitlements (template, compiled);
|
|
|
|
|
|
|
|
try {
|
2018-10-31 22:05:07 +03:00
|
|
|
Directory.CreateDirectory (Path.GetDirectoryName (CompiledEntitlements.ItemSpec));
|
|
|
|
WriteXcent (compiled, CompiledEntitlements.ItemSpec);
|
2016-04-21 16:40:25 +03:00
|
|
|
} catch (Exception ex) {
|
2020-03-14 00:46:28 +03:00
|
|
|
Log.LogError (MSBStrings.E0114, CompiledEntitlements, ex.Message);
|
2016-04-21 16:40:25 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-12-15 23:59:55 +03:00
|
|
|
path = Path.Combine (EntitlementBundlePath, "archived-expanded-entitlements.xcent");
|
|
|
|
|
|
|
|
if (File.Exists (path)) {
|
|
|
|
var plist = PDictionary.FromFile (path);
|
|
|
|
var src = archived.ToXml ();
|
|
|
|
var dest = plist.ToXml ();
|
|
|
|
|
|
|
|
save = src != dest;
|
|
|
|
} else {
|
|
|
|
save = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (save) {
|
|
|
|
try {
|
|
|
|
archived.Save (path, true);
|
|
|
|
} catch (Exception ex) {
|
2020-03-14 00:46:28 +03:00
|
|
|
Log.LogError (MSBStrings.E0115, ex.Message);
|
2016-12-15 23:59:55 +03:00
|
|
|
return false;
|
|
|
|
}
|
2016-04-21 16:40:25 +03:00
|
|
|
}
|
|
|
|
|
2020-08-10 14:55:54 +03:00
|
|
|
if (SdkIsSimulator) {
|
|
|
|
if (compiled.Count > 0) {
|
|
|
|
EntitlementsInExecutable = CompiledEntitlements;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
EntitlementsInSignature = CompiledEntitlements;
|
|
|
|
}
|
|
|
|
|
2016-04-21 16:40:25 +03:00
|
|
|
return !Log.HasLoggedErrors;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|