Skip to content

Commit

Permalink
[msbuild] Validate entitlements in CompileEntitlements.
Browse files Browse the repository at this point in the history
A very common problem customers run into is when they build for release, and
don't update the entitlements their app requests correctly.

In particular this happens often with the `aps-environment` entitlement, which
must be "development" for local development, and "production" when publishing
to the App Store. Fortunately the error the App Store is fairly actionable,
but the reverse isn't true: using "production" for local development will just
fail with a generic error about not being able to install the app on the
device.

So add validation for the entitlements. We currently only validate the
`aps-environment` entitlement, but this can easily be expanded to more
entitlements in the future.

Additionally we now log all the entitlements a provisioning profile grants,
which will help with debugging customer problems.
  • Loading branch information
rolfbjarne committed Dec 20, 2024
1 parent 874ee22 commit 4c870a9
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 9 deletions.
14 changes: 14 additions & 0 deletions docs/building-apps/build-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,20 @@ Whether the native http handler should be the default http handler or not.

Default: true for all platforms except macOS.

## ValidateEntitlements

Choose whether entitlements the app requests should be validated.

Valid values for this property:

* `disable`: Validation is disabled.
* `warn`: Any validation failures are shown as warnings.
* `error`: Any validation failures are shown as errors. This is the default.

The validation process may not validate every entitlement, nor is it guaranteed to not be overeager.

If the validation fails for entitlements that actually work, please file a new issue.

## XamMacResourcePrefix

The directory where resources are stored (this prefix will be removed when copying resources to the app bundle).
Expand Down
30 changes: 30 additions & 0 deletions msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1662,4 +1662,34 @@
<data name="E7136" xml:space="preserve">
<value>Unknown resource type: {1}.</value>
</data>

<data name="E7137" xml:space="preserve">
<value>The app requests the entitlement '{0}' with the value '{1}', but the provisioning profile '{2}' grants it for the value '{3}'.</value>
<comment>
This may be either a warning or an error, depending on build configuration.
</comment>
</data>

<data name="E7138" xml:space="preserve">
<value>Invalid value '{0}' for the 'ValidateEntitlements' property. Valid values are: 'disable', 'warn' or 'error'.</value>
<comment>
The following are literal names and should not be translated: ValidateEntitlements, disable, warn, error
</comment>
</data>

<data name="E7139" xml:space="preserve">
<value>The app requests the entitlement '{0}', but no provisioning profile has been specified. Please specify the name of the provisioning profile to use with the 'CodesignProvision' property in the project file.</value>
<comment>
This may be either a warning or an error, depending on build configuration.
The following are literal names and should not be translated: CodesignProvision
</comment>
</data>

<data name="E7140" xml:space="preserve">
<value>The app requests the entitlement '{0}', but the provisioning profile '{1}' does not contain this entitlement.</value>
<comment>
This may be either a warning or an error, depending on build configuration.
</comment>
</data>

</root>
6 changes: 4 additions & 2 deletions msbuild/Xamarin.MacDev.Tasks/LoggingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

#nullable enable

namespace Xamarin.MacDev.Tasks {
public static class LoggingExtensions {
const MessageImportance TaskPropertyImportance = MessageImportance.Normal;
Expand Down Expand Up @@ -79,12 +81,12 @@ public static void LogTaskProperty (this TaskLoggingHelper log, string propertyN
/// <param name="errorCode">In the 7xxx range for MSBuild error.</param>
/// <param name="message">The error's message to be displayed in the error pad.</param>
/// <param name="fileName">Path to the known guilty file or null.</param>
public static void LogError (this TaskLoggingHelper log, int errorCode, string? fileName, string message, params object [] args)
public static void LogError (this TaskLoggingHelper log, int errorCode, string? fileName, string message, params object? [] args)
{
log.LogError (null, $"{ErrorPrefix}{errorCode}", null, fileName ?? "MSBuild", 0, 0, 0, 0, message, args);
}

public static void LogWarning (this TaskLoggingHelper log, int errorCode, string? fileName, string message, params object [] args)
public static void LogWarning (this TaskLoggingHelper log, int errorCode, string? fileName, string message, params object? [] args)
{
log.LogWarning (null, $"{ErrorPrefix}{errorCode}", null, fileName ?? "MSBuild", 0, 0, 0, 0, message, args);
}
Expand Down
61 changes: 61 additions & 0 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/CompileEntitlements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public class CompileEntitlements : XamarinTask, ITaskCallback, ICancelableTask {
[Output]
public ITaskItem? EntitlementsInSignature { get; set; }

public string ValidateEntitlements { get; set; } = string.Empty;
#endregion

protected string ApplicationIdentifierKey {
Expand Down Expand Up @@ -510,6 +511,9 @@ public override bool Execute ()
}

compiled = GetCompiledEntitlements (profile, template);

ValidateAppEntitlements (profile, compiled);

archived = GetArchivedExpandedEntitlements (template, compiled);

try {
Expand Down Expand Up @@ -568,6 +572,63 @@ bool SaveArchivedExpandedEntitlements (PDictionary archived)
return true;
}

void ValidateAppEntitlements (MobileProvision? profile, PDictionary requestedEntitlements)
{
var onlyWarn = false;
switch (ValidateEntitlements?.ToLowerInvariant ()) {
case "disable":
return;
case "warn":
onlyWarn = true;
break;
case null: // default to 'error'
case "":
case "error":
onlyWarn = false;
break;
default:
Log.LogError (7138, null, MSBStrings.E7138, ValidateEntitlements); // Invalid value '{0}' for the 'ValidateEntitlements' property. Valid values are: 'disable', 'warn' or 'error'.
return;
}

if (requestedEntitlements is null || requestedEntitlements.Count == 0) {
// Everything is OK if the app doesn't request any entitlements.
return;
}

var provisioningEntitlements = profile?.Entitlements;
var provisioningProfileName = profile?.Name;
foreach (var kvp in requestedEntitlements) {
var key = kvp.Key;
switch (key) {
case "aps-environment":
var requestedApsEnvironment = (kvp.Value as PString)?.Value;
if (profile is null) {
LogEntitlementValidationFailure (onlyWarn, 7139, MSBStrings.E7139, key); // "The app requests the entitlement '{0}', but no provisioning profile has been specified. Please specify the name of the provisioning profile to use with the 'CodesignProvision' property in the project file.
} else if (provisioningEntitlements is null || !provisioningEntitlements.TryGetValue<PString> (key, out var provisioningApsEnvironment)) {
LogEntitlementValidationFailure (onlyWarn, 7140, MSBStrings.E7140, key, provisioningProfileName); // The app requests the entitlement '{0}', but the provisioning profile '{1}' does not contain this entitlement.
} else if (requestedApsEnvironment != provisioningApsEnvironment.Value) {
LogEntitlementValidationFailure (onlyWarn, 7137, MSBStrings.E7137, key, requestedApsEnvironment, provisioningProfileName, provisioningApsEnvironment.Value); // The app requests the entitlement '{0}' with the value '{1}', but the provisioning profile '{2}' grants it for the value '{3}'."
} else {
Log.LogMessage (MessageImportance.Low, $"The app requests the entitlement '{key}' with the value '{requestedApsEnvironment}', which the provisioning profile '{provisioningProfileName}' grants.");
}
break;
default:
Log.LogMessage (MessageImportance.Low, $"The app requests entitlement '{key}', but no validation has been implemented for this entitlement. Assuming everything is OK.");
break;
}
}
}

void LogEntitlementValidationFailure (bool onlyWarn, int code, string message, params object?[] args)
{
if (onlyWarn) {
Log.LogWarning (code, Entitlements, message, args);
} else {
Log.LogError (code, Entitlements, message, args);
}
}

public bool ShouldCopyToBuildServer (ITaskItem item) => true;

public bool ShouldCreateOutputFile (ITaskItem item)
Expand Down
17 changes: 15 additions & 2 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/DetectSigningIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

namespace Xamarin.MacDev.Tasks {
public class DetectSigningIdentity : XamarinTask, ITaskCallback, ICancelableTask {
CodeSignIdentity detectedIdentity;

const string AutomaticProvision = "Automatic";
const string AutomaticAdHocProvision = "Automatic:AdHoc";
const string AutomaticAppStoreProvision = "Automatic:AppStore";
Expand Down Expand Up @@ -284,8 +286,17 @@ void ReportDetectedCodesignInfo ()
Log.LogMessage (MessageImportance.High, MSBStrings.M0125);
if (codesignCommonName is not null || !string.IsNullOrEmpty (DetectedCodeSigningKey))
Log.LogMessage (MessageImportance.High, " Code Signing Key: \"{0}\" ({1})", codesignCommonName, DetectedCodeSigningKey);
if (provisioningProfileName is not null)
Log.LogMessage (MessageImportance.High, " Provisioning Profile: \"{0}\" ({1})", provisioningProfileName, DetectedProvisioningProfile);
if (provisioningProfileName is not null) {
var profileEntitlements = detectedIdentity.Profile?.Entitlements;
var entitlements = profileEntitlements?.ToXml ().TrimEnd ().Replace ("\n", "\n ");
if (string.IsNullOrEmpty (entitlements)) {
Log.LogMessage (MessageImportance.High, " Provisioning Profile: \"{0}\" ({1}) - no entitlements", provisioningProfileName, DetectedProvisioningProfile);
} else {
Log.LogMessage (MessageImportance.High, " Provisioning Profile: \"{0}\" ({1}) - {2} entitlements", provisioningProfileName, DetectedProvisioningProfile, profileEntitlements?.Count ?? 0);
Log.LogMessage (MessageImportance.Low, $" Entitlements granted by the provisioning profile:");
Log.LogMessage (MessageImportance.Low, $" {entitlements}");
}
}
Log.LogMessage (MessageImportance.High, " Bundle Id: {0}", BundleIdentifier);
Log.LogMessage (MessageImportance.High, " App Id: {0}", DetectedAppId);
}
Expand Down Expand Up @@ -547,6 +558,8 @@ bool ExecuteImpl ()
IList<X509Certificate2> certs;
List<CodeSignIdentity> pairs;

detectedIdentity = identity;

switch (SdkPlatform) {
case "AppleTVSimulator":
case "AppleTVOS":
Expand Down
1 change: 1 addition & 0 deletions msbuild/Xamarin.Shared/Xamarin.Shared.targets
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
SdkVersion="$(_SdkVersion)"
TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)"
Debug="$(_BundlerDebug)"
ValidateEntitlements="$(ValidateEntitlements)"
>

<!-- $(_CompiledEntitlements) will be passed to the native compiler, it's used to embed the entitlements in the executable -->
Expand Down
Binary file not shown.
Loading

0 comments on commit 4c870a9

Please sign in to comment.