Skip to content

Commit

Permalink
feat: add validator to check for macOS app network entitlement (#2676)
Browse files Browse the repository at this point in the history
Co-authored-by: Felix Angelov <[email protected]>
  • Loading branch information
bryanoltman and felangel authored Dec 16, 2024
1 parent 8eebf95 commit 9b87b30
Show file tree
Hide file tree
Showing 5 changed files with 371 additions and 5 deletions.
4 changes: 4 additions & 0 deletions packages/shorebird_cli/lib/src/archive_analysis/plist.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,8 @@ class Plist {
? releaseVersion
: '$releaseVersion+$buildNumber';
}

@override
String toString() =>
PropertyListSerialization.stringWithPropertyList(properties);
}
13 changes: 8 additions & 5 deletions packages/shorebird_cli/lib/src/doctor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ class Doctor {

/// Validators that verify shorebird will work on macOS.
final List<Validator> macosCommandValidators = [
// TODO(bryanoltman): ensure app has network capabilities
MacosNetworkEntitlementValidator(),
];

/// Validators that should run on all commands.
List<Validator> generalValidators = [
ShorebirdVersionValidator(),
ShorebirdFlutterValidator(),
AndroidInternetPermissionValidator(),
MacosNetworkEntitlementValidator(),
ShorebirdYamlAssetValidator(),
];

Expand All @@ -46,7 +47,7 @@ class Doctor {
final allIssues = <ValidationIssue>[];
final allFixableIssues = <ValidationIssue>[];

var numIssuesFixed = 0;
var totalIssuesFixed = 0;
for (final validator in validators) {
if (!validator.canRunInCurrentContext()) {
continue;
Expand Down Expand Up @@ -76,13 +77,15 @@ class Doctor {
// Re-run the validator to see if there are any remaining issues that
// we couldn't fix.
unresolvedIssues = await validator.validate();
if (unresolvedIssues.isEmpty) {
numIssuesFixed += issues.length - unresolvedIssues.length;
final numIssuesFixed = issues.length - unresolvedIssues.length;
if (numIssuesFixed > 0) {
totalIssuesFixed += numIssuesFixed;
final fixAppliedMessage =
'''($numIssuesFixed fix${numIssuesFixed == 1 ? '' : 'es'} applied)''';
validatorProgress.complete(
'''${validator.description} ${green.wrap(fixAppliedMessage)}''',
);

continue;
}
} else {
Expand Down Expand Up @@ -125,7 +128,7 @@ class Doctor {
allIssues.addAll(unresolvedIssues);
}

if (numIssuesFixed > 0) {
if (totalIssuesFixed > 0) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'dart:io';

import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/archive_analysis/plist.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/validators/validators.dart';

/// Checks that the macOS app has the network client entitlement. Without this
/// entitlement, the app will not be able to make network requests, and
/// Shorebird will not be able to check for patches.
class MacosNetworkEntitlementValidator extends Validator {
/// The plist key for the Outgoing Connections (Client) entitlement, which
/// allows macOS apps to make network requests.
static const networkClientEntitlementKey =
'com.apple.security.network.client';

Directory? get _macosDirectory {
final projectRoot = shorebirdEnv.getFlutterProjectRoot();
if (projectRoot == null) {
return null;
}

return Directory(p.join(projectRoot.path, 'macos'));
}

/// The entitlements plist file for the release build. This lives in
/// project_root/macos/Runner/Release.entitlements, where "Runner" may have
/// been renamed.
File? get _releaseEntitlementsPlist {
final entitlementParentCandidateDirectories =
_macosDirectory!.listSync().whereType<Directory>();

for (final appDir in entitlementParentCandidateDirectories) {
final entitlementsPlist = File(
p.join(appDir.path, 'Release.entitlements'),
);

if (entitlementsPlist.existsSync()) {
return entitlementsPlist;
}
}

return null;
}

@override
String get description => 'macOS app has Outgoing Connections entitlement';

@override
bool canRunInCurrentContext() =>
_macosDirectory != null && _macosDirectory!.existsSync();

@override
Future<List<ValidationIssue>> validate() async {
if (_releaseEntitlementsPlist == null) {
return [
const ValidationIssue(
severity: ValidationIssueSeverity.error,
message: 'Unable to find a Release.entitlements file',
),
];
}

if (!hasNetworkClientEntitlement(plistFile: _releaseEntitlementsPlist!)) {
return [
ValidationIssue(
severity: ValidationIssueSeverity.error,
message:
'''${_releaseEntitlementsPlist!.path} is missing the Outgoing Connections ($networkClientEntitlementKey) entitlement.''',
fix: () => addNetworkEntitlementToPlist(_releaseEntitlementsPlist!),
),
];
}

return [];
}

/// Whether the given entitlements plist file has the network client
/// entitlement.
@visibleForTesting
static bool hasNetworkClientEntitlement({required File plistFile}) =>
Plist(file: plistFile).properties[networkClientEntitlementKey] == true;

/// Adds the network client entitlement to the given entitlements plist file.
@visibleForTesting
static void addNetworkEntitlementToPlist(File entitlementsPlist) {
final plist = Plist(file: entitlementsPlist);
plist.properties[networkClientEntitlementKey] = true;
entitlementsPlist.writeAsStringSync(plist.toString());
}
}
1 change: 1 addition & 0 deletions packages/shorebird_cli/lib/src/validators/validators.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:shorebird_cli/src/shorebird_process.dart';

export 'android_internet_permission_validator.dart';
export 'flavor_validator.dart';
export 'macos_network_entitlement_validator.dart';
export 'shorebird_flutter_validator.dart';
export 'shorebird_version_validator.dart';
export 'shorebird_yaml_asset_validator.dart';
Expand Down
Loading

0 comments on commit 9b87b30

Please sign in to comment.