Unverified Commit 5d87b3ef authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Migrate code_signing to null safety (#79342)

parent 2cdd5190
......@@ -2,19 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import '../application_package.dart';
import '../base/common.dart';
import '../base/config.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../base/terminal.dart';
import '../convert.dart' show utf8;
import '../globals.dart' as globals;
/// User message when no development certificates are found in the keychain.
///
......@@ -95,13 +92,13 @@ final RegExp _certificateOrganizationalUnitExtractionPattern = RegExp(r'OU=([a-z
///
/// Will return null if none are found, if the user cancels or if the Xcode
/// project has a development team set in the project's build settings.
Future<Map<String, String>> getCodeSigningIdentityDevelopmentTeam({
@required BuildableIOSApp iosApp,
@required ProcessManager processManager,
@required Logger logger,
@required BuildInfo buildInfo,
Future<Map<String, String>?> getCodeSigningIdentityDevelopmentTeam({
required Map<String, String>? buildSettings,
required ProcessManager processManager,
required Logger logger,
required Config config,
required Terminal terminal,
}) async {
final Map<String, String> buildSettings = await iosApp.project.buildSettingsForBuildInfo(buildInfo);
if (buildSettings == null) {
return null;
}
......@@ -144,16 +141,17 @@ Future<Map<String, String>> getCodeSigningIdentityDevelopmentTeam({
final List<String> validCodeSigningIdentities = findIdentityStdout
.split('\n')
.map<String>((String outputLine) {
.map<String?>((String outputLine) {
return _securityFindIdentityDeveloperIdentityExtractionPattern
.firstMatch(outputLine)
?.group(1);
})
.where(_isNotEmpty)
.whereType<String>()
.toSet() // Unique.
.toList();
final String signingIdentity = await _chooseSigningIdentity(validCodeSigningIdentities, logger);
final String? signingIdentity = await _chooseSigningIdentity(validCodeSigningIdentities, logger, config, terminal);
// If none are chosen, return null.
if (signingIdentity == null) {
......@@ -162,7 +160,7 @@ Future<Map<String, String>> getCodeSigningIdentityDevelopmentTeam({
logger.printStatus('Signing iOS app for device deployment using developer identity: "$signingIdentity"');
final String signingCertificateId =
final String? signingCertificateId =
_securityFindIdentityCertificateCnExtractionPattern
.firstMatch(signingIdentity)
?.group(1);
......@@ -196,14 +194,24 @@ Future<Map<String, String>> getCodeSigningIdentityDevelopmentTeam({
return null;
}
return <String, String>{
'DEVELOPMENT_TEAM': _certificateOrganizationalUnitExtractionPattern
final String? developmentTeam = _certificateOrganizationalUnitExtractionPattern
.firstMatch(opensslOutput)
?.group(1),
?.group(1);
if (developmentTeam == null) {
return null;
}
return <String, String>{
'DEVELOPMENT_TEAM': developmentTeam,
};
}
Future<String> _chooseSigningIdentity(List<String> validCodeSigningIdentities, Logger logger) async {
Future<String?> _chooseSigningIdentity(
List<String> validCodeSigningIdentities,
Logger logger,
Config config,
Terminal terminal,
) async {
// The user has no valid code signing identities.
if (validCodeSigningIdentities.isEmpty) {
logger.printError(noCertificatesInstruction, emphasis: true);
......@@ -215,7 +223,7 @@ Future<String> _chooseSigningIdentity(List<String> validCodeSigningIdentities, L
}
if (validCodeSigningIdentities.length > 1) {
final String savedCertChoice = globals.config.getValue('ios-signing-cert') as String;
final String savedCertChoice = config.getValue('ios-signing-cert') as String;
if (savedCertChoice != null) {
if (validCodeSigningIdentities.contains(savedCertChoice)) {
......@@ -228,7 +236,7 @@ Future<String> _chooseSigningIdentity(List<String> validCodeSigningIdentities, L
// If terminal UI can't be used, just attempt with the first valid certificate
// since we can't ask the user.
if (!globals.terminal.usesTerminalUi) {
if (!terminal.usesTerminalUi) {
return validCodeSigningIdentities.first;
}
......@@ -242,7 +250,7 @@ Future<String> _chooseSigningIdentity(List<String> validCodeSigningIdentities, L
}
logger.printStatus(' a) Abort', emphasis: true);
final String choice = await globals.terminal.promptForCharInput(
final String choice = await terminal.promptForCharInput(
List<String>.generate(count, (int number) => '${number + 1}')
..add('a'),
prompt: 'Please select a certificate for code signing',
......@@ -256,7 +264,7 @@ Future<String> _chooseSigningIdentity(List<String> validCodeSigningIdentities, L
} else {
final String selectedCert = validCodeSigningIdentities[int.parse(choice) - 1];
logger.printStatus('Certificate choice "$selectedCert" saved');
globals.config.setValue('ios-signing-cert', selectedCert);
config.setValue('ios-signing-cert', selectedCert);
return selectedCert;
}
}
......@@ -265,4 +273,4 @@ Future<String> _chooseSigningIdentity(List<String> validCodeSigningIdentities, L
}
/// Returns true if s is a not empty string.
bool _isNotEmpty(String s) => s != null && s.isNotEmpty;
bool _isNotEmpty(String? s) => s != null && s.isNotEmpty;
......@@ -177,10 +177,11 @@ Future<XcodeBuildResult> buildXcodeProject({
Map<String, String> autoSigningConfigs;
if (codesign && buildForDevice) {
autoSigningConfigs = await getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: await app.project.buildSettingsForBuildInfo(buildInfo),
processManager: globals.processManager,
logger: globals.logger,
buildInfo: buildInfo,
config: globals.config,
terminal: globals.terminal,
);
}
......
......@@ -7,10 +7,7 @@
import 'dart:convert';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/config.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/terminal.dart';
......@@ -19,14 +16,11 @@ import 'package:flutter_tools/src/globals.dart' as globals;
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart';
void main() {
group('Auto signing', () {
ProcessManager mockProcessManager;
Config testConfig;
IosProject mockIosProject;
BuildableIOSApp app;
AnsiTerminal testTerminal;
BufferLogger logger;
......@@ -36,67 +30,53 @@ void main() {
// Assume all binaries exist and are executable
when(mockProcessManager.canRun(any)).thenReturn(true);
testConfig = Config.test();
mockIosProject = MockIosProject();
when(mockIosProject.buildSettingsForBuildInfo(any)).thenAnswer((_) {
return Future<Map<String, String>>.value(<String, String>{
'For our purposes': 'a non-empty build settings map is valid',
});
});
testTerminal = TestTerminal();
testTerminal.usesTerminalUi = true;
app = await BuildableIOSApp.fromProject(mockIosProject, null);
});
testWithoutContext('No auto-sign if Xcode project settings are not available', () async {
const BuildInfo buildInfo = BuildInfo.debug;
when(mockIosProject.buildSettingsForBuildInfo(any)).thenReturn(null);
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: null,
processManager: mockProcessManager,
logger: logger,
buildInfo: buildInfo,
config: testConfig,
terminal: testTerminal,
);
expect(signingConfigs, isNull);
verify(mockIosProject.buildSettingsForBuildInfo(buildInfo));
});
testWithoutContext('No discovery if development team specified in Xcode project', () async {
const BuildInfo buildInfo = BuildInfo.debug;
when(mockIosProject.buildSettingsForBuildInfo(any)).thenAnswer((_) {
return Future<Map<String, String>>.value(<String, String>{
'DEVELOPMENT_TEAM': 'abc',
});
});
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: <String, String>{
'DEVELOPMENT_TEAM': 'abc',
},
processManager: mockProcessManager,
logger: logger,
buildInfo: buildInfo,
config: testConfig,
terminal: testTerminal,
);
expect(signingConfigs, isNull);
expect(logger.statusText, equals(
'Automatically signing iOS for device deployment using specified development team in Xcode project: abc\n'
));
verify(mockIosProject.buildSettingsForBuildInfo(buildInfo));
});
testWithoutContext('No auto-sign if security or openssl not available', () async {
when(mockProcessManager.run(<String>['which', 'security']))
.thenAnswer((_) => Future<ProcessResult>.value(exitsFail));
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: <String, String>{
'bogus': 'bogus',
},
processManager: mockProcessManager,
logger: logger,
buildInfo: null,
config: testConfig,
terminal: testTerminal,
);
expect(signingConfigs, isNull);
});
testUsingContext('No valid code signing certificates shows instructions', () async {
const BuildInfo buildInfo = BuildInfo.debug;
when(mockIosProject.buildSettingsForBuildInfo(any)).thenAnswer((_) {
return Future<Map<String, String>>.value(<String, String>{});
});
testWithoutContext('No valid code signing certificates shows instructions', () async {
when(mockProcessManager.run(
<String>['which', 'security'],
workingDirectory: anyNamed('workingDirectory'),
......@@ -114,15 +94,12 @@ void main() {
)).thenAnswer((_) => Future<ProcessResult>.value(exitsHappy));
expect(() async => getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: <String, String>{},
processManager: mockProcessManager,
logger: logger,
buildInfo: buildInfo,
config: testConfig,
terminal: testTerminal,
), throwsToolExit(message: 'No development certificates available to code sign app for device deployment'));
verify(mockIosProject.buildSettingsForBuildInfo(buildInfo));
},
overrides: <Type, Generator>{
OutputPreferences: () => OutputPreferences(wrapText: false),
});
testWithoutContext('Test single identity and certificate organization works', () async {
......@@ -180,10 +157,13 @@ void main() {
when(mockProcess.exitCode).thenAnswer((_) async => 0);
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: <String, String>{
'bogus': 'bogus',
},
processManager: mockProcessManager,
logger: logger,
buildInfo: null,
config: testConfig,
terminal: testTerminal,
);
expect(logger.statusText, contains('iPhone Developer: Profile 1 (1111AAAA11)'));
......@@ -249,10 +229,13 @@ void main() {
Map<String, String> signingConfigs;
try {
signingConfigs = await getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: <String, String>{
'bogus': 'bogus',
},
processManager: mockProcessManager,
logger: logger,
buildInfo: null,
config: testConfig,
terminal: testTerminal,
);
} on Exception catch (e) {
// This should not throw
......@@ -265,7 +248,7 @@ void main() {
expect(signingConfigs, <String, String>{'DEVELOPMENT_TEAM': '3333CCCC33'});
});
testUsingContext('Test multiple identity and certificate organization works', () async {
testWithoutContext('Test multiple identity and certificate organization works', () async {
when(mockProcessManager.run(
<String>['which', 'security'],
workingDirectory: anyNamed('workingDirectory'),
......@@ -324,10 +307,13 @@ void main() {
when(mockOpenSslProcess.exitCode).thenAnswer((_) => Future<int>.value(0));
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: <String, String>{
'bogus': 'bogus',
},
processManager: mockProcessManager,
logger: logger,
buildInfo: null,
config: testConfig,
terminal: testTerminal,
);
expect(
......@@ -343,14 +329,9 @@ void main() {
expect(signingConfigs, <String, String>{'DEVELOPMENT_TEAM': '4444DDDD44'});
expect(testConfig.getValue('ios-signing-cert'), 'iPhone Developer: Profile 3 (3333CCCC33)');
},
overrides: <Type, Generator>{
Config: () => testConfig,
AnsiTerminal: () => testTerminal,
OutputPreferences: () => OutputPreferences(wrapText: false),
});
testUsingContext('Test multiple identity in machine mode works', () async {
testWithoutContext('Test multiple identity in machine mode works', () async {
testTerminal.usesTerminalUi = false;
when(mockProcessManager.run(
<String>['which', 'security'],
......@@ -410,10 +391,13 @@ void main() {
when(mockOpenSslProcess.exitCode).thenAnswer((_) => Future<int>.value(0));
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: <String, String>{
'bogus': 'bogus',
},
processManager: mockProcessManager,
logger: logger,
buildInfo: null,
config: testConfig,
terminal: testTerminal,
);
expect(
......@@ -423,14 +407,9 @@ void main() {
expect(logger.errorText, isEmpty);
verify(mockOpenSslStdIn.write('This is a mock certificate'));
expect(signingConfigs, <String, String>{'DEVELOPMENT_TEAM': '5555EEEE55'});
},
overrides: <Type, Generator>{
Config: () => testConfig,
AnsiTerminal: () => testTerminal,
OutputPreferences: () => OutputPreferences(wrapText: false),
});
testUsingContext('Test saved certificate used', () async {
testWithoutContext('Test saved certificate used', () async {
when(mockProcessManager.run(
<String>['which', 'security'],
workingDirectory: anyNamed('workingDirectory'),
......@@ -488,10 +467,13 @@ void main() {
testConfig.setValue('ios-signing-cert', 'iPhone Developer: Profile 3 (3333CCCC33)');
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: <String, String>{
'bogus': 'bogus',
},
processManager: mockProcessManager,
logger: logger,
buildInfo: null,
config: testConfig,
terminal: testTerminal,
);
expect(
......@@ -505,13 +487,9 @@ void main() {
expect(logger.errorText, isEmpty);
verify(mockOpenSslStdIn.write('This is a mock certificate'));
expect(signingConfigs, <String, String>{'DEVELOPMENT_TEAM': '4444DDDD44'});
},
overrides: <Type, Generator>{
Config: () => testConfig,
OutputPreferences: () => OutputPreferences(wrapText: false),
});
testUsingContext('Test invalid saved certificate shows error and prompts again', () async {
testWithoutContext('Test invalid saved certificate shows error and prompts again', () async {
when(mockProcessManager.run(
<String>['which', 'security'],
workingDirectory: anyNamed('workingDirectory'),
......@@ -572,10 +550,13 @@ void main() {
testConfig.setValue('ios-signing-cert', 'iPhone Developer: Invalid Profile');
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: <String, String>{
'bogus': 'bogus',
},
processManager: mockProcessManager,
logger: logger,
buildInfo: null,
config: testConfig,
terminal: testTerminal,
);
expect(
......@@ -588,10 +569,6 @@ void main() {
);
expect(signingConfigs, <String, String>{'DEVELOPMENT_TEAM': '4444DDDD44'});
expect(testConfig.getValue('ios-signing-cert'), 'iPhone Developer: Profile 3 (3333CCCC33)');
},
overrides: <Type, Generator>{
Config: () => testConfig,
AnsiTerminal: () => testTerminal,
});
testWithoutContext('find-identity failure', () async {
......@@ -614,15 +591,18 @@ void main() {
));
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: <String, String>{
'bogus': 'bogus',
},
processManager: mockProcessManager,
logger: logger,
buildInfo: null,
config: testConfig,
terminal: testTerminal,
);
expect(signingConfigs, isNull);
});
testUsingContext('find-certificate failure', () async {
testWithoutContext('find-certificate failure', () async {
when(mockProcessManager.run(
<String>['which', 'security'],
workingDirectory: anyNamed('workingDirectory'),
......@@ -658,16 +638,15 @@ void main() {
);
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(
iosApp: app,
buildSettings: <String, String>{
'bogus': 'bogus',
},
processManager: mockProcessManager,
logger: logger,
buildInfo: null,
config: testConfig,
terminal: testTerminal,
);
expect(signingConfigs, isNull);
},
overrides: <Type, Generator>{
Config: () => testConfig,
AnsiTerminal: () => testTerminal,
});
});
}
......
......@@ -24,7 +24,6 @@ import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fakes.dart';
import '../../src/mocks.dart';
final Platform macosPlatform = FakePlatform(
operatingSystem: 'macos',
......@@ -482,13 +481,13 @@ void main() {
group('log reader', () {
FakeProcessManager fakeProcessManager;
MockIosProject mockIosProject;
FakeIosProject mockIosProject;
MockSimControl mockSimControl;
Xcode xcode;
setUp(() {
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[]);
mockIosProject = MockIosProject();
mockIosProject = FakeIosProject();
mockSimControl = MockSimControl();
xcode = Xcode.test(processManager: FakeProcessManager.any());
});
......@@ -986,3 +985,11 @@ flutter:
});
});
}
class FakeIosProject extends Fake implements IosProject {
@override
Future<String> productBundleIdentifier(BuildInfo buildInfo) async => 'com.example.test';
@override
Future<String> hostAppBundleName(BuildInfo buildInfo) async => 'My Super Awesome App.app';
}
......@@ -174,17 +174,6 @@ Process createMockProcess({ int exitCode = 0, String stdout = '', String stderr
class _MockBasicProcess extends Mock implements Process {}
class MockIosProject extends Mock implements IosProject {
static const String bundleId = 'com.example.test';
static const String appBundleName = 'My Super Awesome App.app';
@override
Future<String> productBundleIdentifier(BuildInfo buildInfo) async => bundleId;
@override
Future<String> hostAppBundleName(BuildInfo buildInfo) async => appBundleName;
}
class MockAndroidDevice extends Mock implements AndroidDevice {
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.android_arm;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment