// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:args/command_runner.dart'; import 'package:dev_tools/codesign.dart'; import 'package:dev_tools/globals.dart'; import 'package:dev_tools/repository.dart'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:meta/meta.dart'; import 'package:platform/platform.dart'; import '../../../packages/flutter_tools/test/src/fake_process_manager.dart'; import './common.dart'; void main() { group('codesign command', () { const String flutterRoot = '/flutter'; const String checkoutsParentDirectory = '$flutterRoot/dev/tools/'; const String flutterCache = '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache'; const String flutterBin = '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/flutter'; const String revision = 'abcd1234'; CommandRunner runner; Checkouts checkouts; MemoryFileSystem fileSystem; FakePlatform platform; TestStdio stdio; FakeProcessManager processManager; const List binariesWithEntitlements = [ '$flutterCache/dart-sdk/bin/dart', '$flutterCache/dart-sdk/bin/dartaotruntime', ]; const List binariesWithoutEntitlements = [ '$flutterCache/engine/darwin-x64/font-subset', ]; const List allBinaries = [ ...binariesWithEntitlements, ...binariesWithoutEntitlements, ]; void createRunner({ String operatingSystem = 'macos', List commands, }) { stdio = TestStdio(); fileSystem = MemoryFileSystem.test(); platform = FakePlatform(operatingSystem: operatingSystem); processManager = FakeProcessManager.list(commands ?? []); checkouts = Checkouts( fileSystem: fileSystem, parentDirectory: fileSystem.directory(checkoutsParentDirectory), platform: platform, processManager: processManager, stdio: stdio, ); final FakeCodesignCommand command = FakeCodesignCommand( checkouts: checkouts, binariesWithEntitlements: binariesWithEntitlements, binariesWithoutEntitlements: binariesWithoutEntitlements, flutterRoot: fileSystem.directory(flutterRoot), ); runner = CommandRunner('codesign-test', '') ..addCommand(command); } test('throws exception if not run from macos', () async { createRunner(operatingSystem: 'linux'); expect( () async => await runner.run(['codesign']), throwsExceptionWith('Error! Expected operating system "macos"'), ); }); test('throws exception if verify flag is not provided', () async { createRunner(); expect( () async => await runner.run(['codesign']), throwsExceptionWith( 'Sorry, but codesigning is not implemented yet. Please pass the --$kVerify flag to verify signatures'), ); }); test('succeeds if every binary is codesigned and has correct entitlements', () async { final List codesignCheckCommands = []; for (final String bin in binariesWithEntitlements) { codesignCheckCommands.add( FakeCommand( command: ['codesign', '-vvv', bin], ), ); codesignCheckCommands.add( FakeCommand( command: ['codesign', '--display', '--entitlements', ':-', bin], stdout: expectedEntitlements.join('\n'), ), ); } for (final String bin in binariesWithoutEntitlements) { codesignCheckCommands.add( FakeCommand( command: ['codesign', '-vvv', bin], ), ); } createRunner(commands: [ const FakeCommand(command: [ 'git', 'clone', '--', 'file://$flutterRoot/', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', ]), const FakeCommand(command: [ 'git', 'rev-parse', 'HEAD', ], stdout: revision), const FakeCommand(command: [ 'git', 'checkout', revision, ]), const FakeCommand(command: [ flutterBin, 'help', ]), const FakeCommand(command: [ flutterBin, 'help', ]), const FakeCommand(command: [ flutterBin, 'precache', '--ios', '--macos', ]), FakeCommand( command: const [ 'find', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache', '-type', 'f', ], stdout: allBinaries.join('\n'), ), for (String bin in allBinaries) FakeCommand( command: ['file', '--mime-type', '-b', bin], stdout: 'application/x-mach-binary', ), ...codesignCheckCommands, ]); await runner.run(['codesign', '--$kVerify', '--$kRevision', revision]); expect(processManager.hasRemainingExpectations, false); }); test('fails if a single binary is not codesigned', () async { final List codesignCheckCommands = []; codesignCheckCommands.add( const FakeCommand( command: ['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dart'], ), ); codesignCheckCommands.add( FakeCommand( command: const [ 'codesign', '--display', '--entitlements', ':-', '$flutterCache/dart-sdk/bin/dart', ], stdout: expectedEntitlements.join('\n'), ) ); // Not signed codesignCheckCommands.add( const FakeCommand( command: ['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dartaotruntime'], exitCode: 1, ), ); codesignCheckCommands.add( const FakeCommand( command: ['codesign', '-vvv', '$flutterCache/engine/darwin-x64/font-subset'], ), ); createRunner(commands: [ const FakeCommand(command: [ 'git', 'clone', '--', 'file://$flutterRoot/', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', ]), const FakeCommand(command: [ 'git', 'rev-parse', 'HEAD', ], stdout: revision), const FakeCommand(command: [ 'git', 'checkout', revision, ]), const FakeCommand(command: [ flutterBin, 'help', ]), const FakeCommand(command: [ flutterBin, 'help', ]), const FakeCommand(command: [ flutterBin, 'precache', '--ios', '--macos', ]), FakeCommand( command: const [ 'find', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache', '-type', 'f', ], stdout: allBinaries.join('\n'), ), for (String bin in allBinaries) FakeCommand( command: ['file', '--mime-type', '-b', bin], stdout: 'application/x-mach-binary', ), ...codesignCheckCommands, ]); expect( () async => await runner.run(['codesign', '--$kVerify', '--$kRevision', revision]), throwsExceptionWith('Test failed because unsigned binaries detected.'), ); expect(processManager.hasRemainingExpectations, false); }); test('fails if a single binary has the wrong entitlements', () async { final List codesignCheckCommands = []; codesignCheckCommands.add( const FakeCommand( command: ['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dart'], ), ); codesignCheckCommands.add( FakeCommand( command: const ['codesign', '--display', '--entitlements', ':-', '$flutterCache/dart-sdk/bin/dart'], stdout: expectedEntitlements.join('\n'), ) ); codesignCheckCommands.add( const FakeCommand( command: ['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dartaotruntime'], ), ); // No entitlements codesignCheckCommands.add( const FakeCommand( command: ['codesign', '--display', '--entitlements', ':-', '$flutterCache/dart-sdk/bin/dartaotruntime'], ) ); codesignCheckCommands.add( const FakeCommand( command: ['codesign', '-vvv', '$flutterCache/engine/darwin-x64/font-subset'], ), ); createRunner(commands: [ const FakeCommand(command: [ 'git', 'clone', '--', 'file://$flutterRoot/', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', ]), const FakeCommand(command: [ 'git', 'rev-parse', 'HEAD', ], stdout: revision), const FakeCommand(command: [ 'git', 'checkout', revision, ]), const FakeCommand(command: [ flutterBin, 'help', ]), const FakeCommand(command: [ flutterBin, 'help', ]), const FakeCommand(command: [ flutterBin, 'precache', '--ios', '--macos', ]), FakeCommand( command: const [ 'find', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache', '-type', 'f', ], stdout: allBinaries.join('\n'), ), for (String bin in allBinaries) FakeCommand( command: ['file', '--mime-type', '-b', bin], stdout: 'application/x-mach-binary', ), ...codesignCheckCommands, ]); expect( () async => await runner.run(['codesign', '--$kVerify', '--$kRevision', revision]), throwsExceptionWith('Test failed because files found with the wrong entitlements'), ); expect(processManager.hasRemainingExpectations, false); }); test('does not check signatures or entitlements if --no-$kSignatures specified', () async { createRunner(commands: [ const FakeCommand(command: [ 'git', 'clone', '--', 'file://$flutterRoot/', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', ]), const FakeCommand(command: [ 'git', 'rev-parse', 'HEAD', ], stdout: revision), const FakeCommand(command: [ 'git', 'checkout', revision, ]), const FakeCommand(command: [ flutterBin, 'help', ]), const FakeCommand(command: [ flutterBin, 'help', ]), const FakeCommand(command: [ flutterBin, 'precache', '--ios', '--macos', ]), FakeCommand( command: const [ 'find', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache', '-type', 'f', ], stdout: allBinaries.join('\n'), ), for (String bin in allBinaries) FakeCommand( command: ['file', '--mime-type', '-b', bin], stdout: 'application/x-mach-binary', ), ]); try { await runner.run([ 'codesign', '--$kVerify', '--no-$kSignatures', '--$kRevision', revision, ]); } on ConductorException { //print(stdio.error); rethrow; } expect( processManager.hasRemainingExpectations, false, ); }); }); } class FakeCodesignCommand extends CodesignCommand { FakeCodesignCommand({ @required Checkouts checkouts, @required this.binariesWithEntitlements, @required this.binariesWithoutEntitlements, @required Directory flutterRoot, }) : super(checkouts: checkouts, flutterRoot: flutterRoot); @override final List binariesWithEntitlements; @override final List binariesWithoutEntitlements; }