Unverified Commit 3764cb85 authored by Ben Konyi's avatar Ben Konyi Committed by GitHub

Added support for authentication codes for the VM service. (#30857)

* Added support for authentication codes for the VM service.

Previously, a valid web socket connection would use the following URI:

`ws://127.0.0.1/ws`

Now, by default, the VM service requires a connection to be made with a
URI similar to the following:

`ws://127.0.0.1:8181/Ug_U0QVsqFs=/ws`

where `Ug_U0QVsqFs` is an authentication code generated and shared by
the
service.

This behavior can be disabled with the `--disable-service-auth-codes`
flag.
parent 086fd993
4b9966f5cb412a73fa50462b3aee9082f436a62a
ca31a7c57bada458fa7f5c0d3f36bc1af4ccbc79
......@@ -26,7 +26,7 @@ void main() {
print('run: starting...');
final Process run = await startProcess(
path.join(flutterDirectory.path, 'bin', 'flutter'),
<String>['run', '--verbose', '-d', device.deviceId, 'lib/commands.dart'],
<String>['run', '--verbose', '--disable-service-auth-codes', '-d', device.deviceId, 'lib/commands.dart'],
);
final StreamController<String> stdout = StreamController<String>.broadcast();
run.stdout
......
......@@ -21,7 +21,8 @@ void main() {
section('Compile and run the tester app');
Completer<void> firstNameFound = Completer<void>();
Completer<void> secondNameFound = Completer<void>();
final Process runProcess = await _run(device: device, command: <String>['run'], stdoutListener: (String line) {
final Process runProcess = await _run(device: device, command:
<String>['run', '--disable-service-auth-codes'], stdoutListener: (String line) {
if (line.contains(_kFirstIsolateName)) {
firstNameFound.complete();
} else if (line.contains(_kSecondIsolateName)) {
......
......@@ -34,7 +34,7 @@ void main() {
print('run: starting...');
final Process run = await startProcess(
path.join(flutterDirectory.path, 'bin', 'flutter'),
<String>['run', '--verbose', '-d', device.deviceId, '--route', '/smuggle-it', 'lib/route.dart'],
<String>['run', '--verbose', '--disable-service-auth-codes', '-d', device.deviceId, '--route', '/smuggle-it', 'lib/route.dart'],
);
run.stdout
.transform<String>(utf8.decoder)
......
......@@ -26,7 +26,7 @@ void main() {
print('run: starting...');
final Process run = await startProcess(
path.join(flutterDirectory.path, 'bin', 'flutter'),
<String>['run', '--verbose', '-d', device.deviceId, 'lib/main.dart'],
<String>['run', '--verbose', '--disable-service-auth-codes', '-d', device.deviceId, 'lib/main.dart'],
);
run.stdout
.transform<String>(utf8.decoder)
......
......@@ -41,16 +41,16 @@ Future<Map<String, dynamic>> runTask(String taskName, { bool silent = false }) a
runnerFinished = true;
});
final Completer<int> port = Completer<int>();
final Completer<Uri> uri = Completer<Uri>();
final StreamSubscription<String> stdoutSub = runner.stdout
.transform<String>(const Utf8Decoder())
.transform<String>(const LineSplitter())
.listen((String line) {
if (!port.isCompleted) {
final int portValue = parseServicePort(line, prefix: 'Observatory listening on ');
if (portValue != null)
port.complete(portValue);
if (!uri.isCompleted) {
final Uri serviceUri = parseServiceUri(line, prefix: 'Observatory listening on ');
if (serviceUri != null)
uri.complete(serviceUri);
}
if (!silent) {
stdout.writeln('[$taskName] [STDOUT] $line');
......@@ -66,7 +66,7 @@ Future<Map<String, dynamic>> runTask(String taskName, { bool silent = false }) a
String waitingFor = 'connection';
try {
final VMIsolateRef isolate = await _connectToRunnerIsolate(await port.future);
final VMIsolateRef isolate = await _connectToRunnerIsolate(await uri.future);
waitingFor = 'task completion';
final Map<String, dynamic> taskResult =
await isolate.invokeExtension('ext.cocoonRunTask').timeout(taskTimeoutWithGracePeriod);
......@@ -88,8 +88,15 @@ Future<Map<String, dynamic>> runTask(String taskName, { bool silent = false }) a
}
}
Future<VMIsolateRef> _connectToRunnerIsolate(int vmServicePort) async {
final String url = 'ws://localhost:$vmServicePort/ws';
Future<VMIsolateRef> _connectToRunnerIsolate(Uri vmServiceUri) async {
final List<String> pathSegments = <String>[];
if (vmServiceUri.pathSegments.isNotEmpty) {
// Add authentication code.
pathSegments.add(vmServiceUri.pathSegments[0]);
}
pathSegments.add('ws');
final String url = vmServiceUri.replace(scheme: 'ws', pathSegments:
pathSegments).toString();
final DateTime started = DateTime.now();
// TODO(yjbanov): due to lack of imagination at the moment the handshake with
......@@ -163,4 +170,4 @@ Future<void> cleanupSystem() async {
} else {
print('Could not determine JAVA_HOME; not shutting down Gradle.');
}
}
\ No newline at end of file
}
......@@ -529,19 +529,43 @@ String extractCloudAuthTokenArg(List<String> rawArgs) {
return token;
}
final RegExp _obsRegExp =
RegExp('An Observatory debugger .* is available at: ');
final RegExp _obsPortRegExp = RegExp('(\\S+:(\\d+)/\\S*)\$');
final RegExp _obsUriRegExp = RegExp('((http|\/\/)[a-zA-Z0-9:/=_\\-\.\\[\\]]+)');
/// Tries to extract a port from the string.
///
/// The `prefix`, if specified, is a regular expression pattern and must not contain groups.
///
/// The `multiLine` flag should be set to true if `line` is actually a buffer of many lines.
/// `prefix` defaults to the RegExp: `An Observatory debugger .* is available at: `.
int parseServicePort(String line, {
String prefix = 'An Observatory debugger .* is available at: ',
bool multiLine = false,
Pattern prefix,
}) {
// e.g. "An Observatory debugger and profiler on ... is available at: http://127.0.0.1:8100/"
final RegExp pattern = RegExp('$prefix(\\S+:(\\d+)/\\S*)\$', multiLine: multiLine);
final Match match = pattern.firstMatch(line);
return match == null ? null : int.parse(match.group(2));
prefix ??= _obsRegExp;
final Match prefixMatch = prefix.matchAsPrefix(line);
if (prefixMatch == null) {
return null;
}
final List<Match> matches =
_obsPortRegExp.allMatches(line, prefixMatch.end).toList();
return matches.isEmpty ? null : int.parse(matches[0].group(2));
}
/// Tries to extract a Uri from the string.
///
/// The `prefix`, if specified, is a regular expression pattern and must not contain groups.
/// `prefix` defaults to the RegExp: `An Observatory debugger .* is available at: `.
Uri parseServiceUri(String line, {
Pattern prefix,
}) {
prefix ??= _obsRegExp;
final Match prefixMatch = prefix.matchAsPrefix(line);
if (prefixMatch == null) {
return null;
}
final List<Match> matches =
_obsUriRegExp.allMatches(line, prefixMatch.end).toList();
return matches.isEmpty ? null : Uri.parse(matches[0].group(0));
}
/// If FLUTTER_ENGINE environment variable is set then we need to pass
......
......@@ -16,4 +16,21 @@ void main() {
expect(grep(RegExp('^b'), from: 'ab\nba'), <String>['ba']);
});
});
group('parse service', () {
const String badOutput = 'No uri here';
const String sampleOutput = 'An Observatory debugger and profiler on '
'Pixel 3 XL is available at: http://127.0.0.1:9090/LpjUpsdEjqI=/';
test('uri', () {
expect(parseServiceUri(sampleOutput),
Uri.parse('http://127.0.0.1:9090/LpjUpsdEjqI=/'));
expect(parseServiceUri(badOutput), null);
});
test('port', () {
expect(parseServicePort(sampleOutput), 9090);
expect(parseServicePort(badOutput), null);
});
});
}
......@@ -438,6 +438,8 @@ class AndroidDevice extends Device {
}
if (debuggingOptions.startPaused)
cmd.addAll(<String>['--ez', 'start-paused', 'true']);
if (debuggingOptions.disableServiceAuthCodes)
cmd.addAll(<String>['--ez', 'disable-service-auth-codes', 'true']);
if (debuggingOptions.useTestFonts)
cmd.addAll(<String>['--ez', 'use-test-fonts', 'true']);
if (debuggingOptions.verboseSystemLogs) {
......
......@@ -169,6 +169,11 @@ class RunCommand extends RunCommandBase {
'results out to "refresh_benchmark.json", and exit. This flag is '
'intended for use in generating automated flutter benchmarks.',
)
..addFlag('disable-service-auth-codes',
negatable: false,
hide: !verboseHelp,
help: 'No longer require an authentication code to connect to the VM '
'service (not recommended).')
..addOption(FlutterOptions.kExtraFrontEndOptions, hide: true)
..addOption(FlutterOptions.kExtraGenSnapshotOptions, hide: true)
..addMultiOption(FlutterOptions.kEnableExperiment,
......@@ -262,6 +267,7 @@ class RunCommand extends RunCommandBase {
return DebuggingOptions.enabled(
buildInfo,
startPaused: argResults['start-paused'],
disableServiceAuthCodes: argResults['disable-service-auth-codes'],
useTestFonts: argResults['use-test-fonts'],
enableSoftwareRendering: argResults['enable-software-rendering'],
skiaDeterministicRendering: argResults['skia-deterministic-rendering'],
......
......@@ -43,6 +43,13 @@ class TestCommand extends FastFlutterCommand {
'Instructions for connecting with a debugger and printed to the '
'console once the test has started.',
)
..addFlag('disable-service-auth-codes',
hide: !verboseHelp,
defaultsTo: false,
negatable: false,
help: 'No longer require an authentication code to connect to the VM '
'service (not recommended).'
)
..addFlag('coverage',
defaultsTo: false,
negatable: false,
......@@ -194,6 +201,9 @@ class TestCommand extends FastFlutterCommand {
}
}
final bool disableServiceAuthCodes =
argResults['disable-service-auth-codes'];
final int result = await runTests(
files,
workDir: workDir,
......@@ -202,6 +212,7 @@ class TestCommand extends FastFlutterCommand {
watcher: watcher,
enableObservatory: collector != null || startPaused,
startPaused: startPaused,
disableServiceAuthCodes: disableServiceAuthCodes,
ipv6: argResults['ipv6'],
machine: machine,
trackWidgetCreation: argResults['track-widget-creation'],
......
......@@ -367,6 +367,7 @@ class DebuggingOptions {
DebuggingOptions.enabled(
this.buildInfo, {
this.startPaused = false,
this.disableServiceAuthCodes = false,
this.enableSoftwareRendering = false,
this.skiaDeterministicRendering = false,
this.traceSkia = false,
......@@ -381,6 +382,7 @@ class DebuggingOptions {
: debuggingEnabled = false,
useTestFonts = false,
startPaused = false,
disableServiceAuthCodes = false,
enableSoftwareRendering = false,
skiaDeterministicRendering = false,
traceSkia = false,
......@@ -393,6 +395,7 @@ class DebuggingOptions {
final BuildInfo buildInfo;
final bool startPaused;
final bool disableServiceAuthCodes;
final bool enableSoftwareRendering;
final bool skiaDeterministicRendering;
final bool traceSkia;
......
......@@ -279,6 +279,9 @@ class IOSDevice extends Device {
if (debuggingOptions.startPaused)
launchArguments.add('--start-paused');
if (debuggingOptions.disableServiceAuthCodes)
launchArguments.add('--disable-service-auth-codes');
if (debuggingOptions.useTestFonts)
launchArguments.add('--use-test-fonts');
......
......@@ -325,6 +325,8 @@ class IOSSimulator extends Device {
]);
if (debuggingOptions.startPaused)
args.add('--start-paused');
if (debuggingOptions.disableServiceAuthCodes)
args.add('--disable-service-auth-codes');
if (debuggingOptions.skiaDeterministicRendering)
args.add('--skia-deterministic-rendering');
if (debuggingOptions.useTestFonts)
......
......@@ -59,8 +59,7 @@ class ProtocolDiscovery {
void _handleLine(String line) {
Uri uri;
final RegExp r = RegExp('${RegExp.escape(serviceName)} listening on ((http|\/\/)[a-zA-Z0-9:/=\.\\[\\]]+)');
final RegExp r = RegExp('${RegExp.escape(serviceName)} listening on ((http|\/\/)[a-zA-Z0-9:/=_\\-\.\\[\\]]+)');
final Match match = r.firstMatch(line);
if (match != null) {
......
......@@ -83,6 +83,7 @@ void installHook({
bool enableObservatory = false,
bool machine = false,
bool startPaused = false,
bool disableServiceAuthCodes = false,
int port = 0,
String precompiledDillPath,
Map<String, String> precompiledDillFiles,
......@@ -104,6 +105,7 @@ void installHook({
machine: machine,
enableObservatory: enableObservatory,
startPaused: startPaused,
disableServiceAuthCodes: disableServiceAuthCodes,
explicitObservatoryPort: observatoryPort,
host: _kHosts[serverType],
port: port,
......@@ -385,6 +387,7 @@ class _FlutterPlatform extends PlatformPlugin {
this.enableObservatory,
this.machine,
this.startPaused,
this.disableServiceAuthCodes,
this.explicitObservatoryPort,
this.host,
this.port,
......@@ -403,6 +406,7 @@ class _FlutterPlatform extends PlatformPlugin {
final bool enableObservatory;
final bool machine;
final bool startPaused;
final bool disableServiceAuthCodes;
final int explicitObservatoryPort;
final InternetAddress host;
final int port;
......@@ -585,6 +589,7 @@ class _FlutterPlatform extends PlatformPlugin {
packages: PackageMap.globalPackagesPath,
enableObservatory: enableObservatory,
startPaused: startPaused,
disableServiceAuthCodes: disableServiceAuthCodes,
observatoryPort: explicitObservatoryPort,
serverPort: server.port,
);
......@@ -932,6 +937,7 @@ class _FlutterPlatform extends PlatformPlugin {
String packages,
bool enableObservatory = false,
bool startPaused = false,
bool disableServiceAuthCodes = false,
int observatoryPort,
int serverPort,
}) {
......@@ -953,6 +959,9 @@ class _FlutterPlatform extends PlatformPlugin {
if (startPaused) {
command.add('--start-paused');
}
if (disableServiceAuthCodes) {
command.add('--disable-service-auth-codes');
}
} else {
command.add('--disable-observatory');
}
......
......@@ -27,6 +27,7 @@ Future<int> runTests(
List<String> plainNames = const <String>[],
bool enableObservatory = false,
bool startPaused = false,
bool disableServiceAuthCodes = false,
bool ipv6 = false,
bool machine = false,
String precompiledDillPath,
......@@ -79,6 +80,7 @@ Future<int> runTests(
enableObservatory: enableObservatory,
machine: machine,
startPaused: startPaused,
disableServiceAuthCodes: disableServiceAuthCodes,
serverType: serverType,
precompiledDillPath: precompiledDillPath,
precompiledDillFiles: precompiledDillFiles,
......
......@@ -120,6 +120,8 @@ class FlutterTesterDevice extends Device {
if (debuggingOptions.debuggingEnabled) {
if (debuggingOptions.startPaused)
command.add('--start-paused');
if (debuggingOptions.disableServiceAuthCodes)
command.add('--disable-service-auth-codes');
if (debuggingOptions.hasObservatoryPort)
command.add('--observatory-port=${debuggingOptions.observatoryPort}');
}
......
......@@ -178,7 +178,7 @@ void main() {
await expectLater(
createTestCommandRunner(command).run(<String>['attach', '--ipv6']),
throwsToolExit(
message: 'When the --debug-port is unknown, this command determines '
message: 'When the --debug-port or --debug-uri is unknown, this command determines '
'the value of --ipv6 on its own.',
),
);
......@@ -193,7 +193,7 @@ void main() {
await expectLater(
createTestCommandRunner(command).run(<String>['attach', '--observatory-port', '100']),
throwsToolExit(
message: 'When the --debug-port is unknown, this command does not use '
message: 'When the --debug-port or --debug-uri is unknown, this command does not use '
'the value of --observatory-port.',
),
);
......@@ -438,7 +438,7 @@ void main() {
final MDnsClient client = MockMDnsClient();
when(client.lookup<PtrResourceRecord>(
ResourceRecordQuery.serverPointer(MDnsObservatoryPortDiscovery.dartObservatoryName),
ResourceRecordQuery.serverPointer(MDnsObservatoryDiscovery.dartObservatoryName),
)).thenAnswer((_) => Stream<PtrResourceRecord>.fromIterable(ptrRecords));
for (final MapEntry<String, List<SrvResourceRecord>> entry in srvResponse.entries) {
......@@ -452,8 +452,8 @@ void main() {
testUsingContext('No ports available', () async {
final MDnsClient client = getMockClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
final MDnsObservatoryPortDiscovery portDiscovery = MDnsObservatoryPortDiscovery(mdnsClient: client);
final int port = await portDiscovery.queryForPort();
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
final int port = (await portDiscovery.query())?.port;
expect(port, isNull);
});
......@@ -469,8 +469,8 @@ void main() {
},
);
final MDnsObservatoryPortDiscovery portDiscovery = MDnsObservatoryPortDiscovery(mdnsClient: client);
final int port = await portDiscovery.queryForPort();
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
final int port = (await portDiscovery.query())?.port;
expect(port, 123);
});
......@@ -490,8 +490,8 @@ void main() {
},
);
final MDnsObservatoryPortDiscovery portDiscovery = MDnsObservatoryPortDiscovery(mdnsClient: client);
expect(() => portDiscovery.queryForPort(), throwsToolExit());
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
expect(() => portDiscovery.query(), throwsToolExit());
});
testUsingContext('Multiple ports available, with appId', () async {
......@@ -510,8 +510,8 @@ void main() {
},
);
final MDnsObservatoryPortDiscovery portDiscovery = MDnsObservatoryPortDiscovery(mdnsClient: client);
final int port = await portDiscovery.queryForPort(applicationId: 'fiz');
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
final int port = (await portDiscovery.query(applicationId: 'fiz'))?.port;
expect(port, 321);
});
......@@ -533,8 +533,8 @@ void main() {
},
);
final MDnsObservatoryPortDiscovery portDiscovery = MDnsObservatoryPortDiscovery(mdnsClient: client);
final int port = await portDiscovery.queryForPort(applicationId: 'bar');
final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(mdnsClient: client);
final int port = (await portDiscovery.query(applicationId: 'bar'))?.port;
expect(port, 1234);
});
});
......
......@@ -417,6 +417,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
await _setupProcess(
<String>[
'run',
'--disable-service-auth-codes',
'--machine',
'-d',
'flutter-tester',
......@@ -607,6 +608,7 @@ class FlutterTestTestDriver extends FlutterTestDriver {
}) async {
await _setupProcess(<String>[
'test',
'--disable-service-auth-codes',
'--machine',
'-d',
'flutter-tester',
......
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