[dtd] Add support for '--disable-service-auth-codes'
See https://github.com/dart-lang/sdk/issues/54932
Change-Id: Ie85eb76eb5e8c01dda957d038286ae498748ca70
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373340
Reviewed-by: Dan Chevalier <danchevalier@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/dartdev/lib/src/commands/tooling_daemon.dart b/pkg/dartdev/lib/src/commands/tooling_daemon.dart
index 44e6f14..d1c3b22 100644
--- a/pkg/dartdev/lib/src/commands/tooling_daemon.dart
+++ b/pkg/dartdev/lib/src/commands/tooling_daemon.dart
@@ -4,7 +4,6 @@
import 'dart:async';
-import 'package:args/args.dart';
import 'package:dtd_impl/dart_tooling_daemon.dart' as dtd
show DartToolingDaemonOptions;
@@ -23,12 +22,10 @@
commandDescription,
verbose,
hidden: !verbose,
- );
-
- @override
- ArgParser createArgParser() {
- return dtd.DartToolingDaemonOptions.createArgParser(
- usageLineLength: dartdevUsageLineLength,
+ ) {
+ dtd.DartToolingDaemonOptions.populateArgOptions(
+ argParser,
+ verbose: verbose,
);
}
diff --git a/pkg/dtd_impl/lib/dart_tooling_daemon.dart b/pkg/dtd_impl/lib/dart_tooling_daemon.dart
index 4b6be36..d83e5e2 100644
--- a/pkg/dtd_impl/lib/dart_tooling_daemon.dart
+++ b/pkg/dtd_impl/lib/dart_tooling_daemon.dart
@@ -26,69 +26,91 @@
enum DartToolingDaemonOptions {
// Used when executing a training run while generating an AppJIT snapshot as
// part of an SDK build.
- train.flag(negatable: false, hide: true),
+ train.flag('train', negatable: false, hide: true),
machine.flag(
+ 'machine',
negatable: false,
help: 'Sets output format to JSON for consumption in tools.',
),
port.option(
+ 'port',
defaultsTo: '0',
help: 'Sets the port to bind DTD to (0 for automatic port).',
),
unrestricted.flag(
+ 'unrestricted',
negatable: false,
help: 'Disables restrictions on services registered by DTD.',
),
+ disableServiceAuthCodes.flag(
+ 'disable-service-auth-codes',
+ negatable: false,
+ // This text mirrors what's in dartdev/commands/run for VM Service.
+ help: 'Disables the requirement for an authentication code to '
+ 'communicate with DTD. Authentication codes help '
+ 'protect against CSRF attacks, so it is not recommended to '
+ 'disable them unless behind a firewall on a secure device.',
+ verbose: true,
+ ),
fakeAnalytics.flag(
+ 'fakeAnalytics',
negatable: false,
help: 'Uses fake analytics instances for the UnifiedAnalytics service.',
hide: true,
);
- const DartToolingDaemonOptions.flag({
+ const DartToolingDaemonOptions.flag(
+ this.name, {
this.negatable = true,
+ this.verbose = false,
this.hide = false,
this.help,
}) : _kind = _DartToolingDaemonOptionKind.flag,
defaultsTo = null;
- const DartToolingDaemonOptions.option({
+ const DartToolingDaemonOptions.option(
+ this.name, {
this.defaultsTo,
this.help,
}) : _kind = _DartToolingDaemonOptionKind.option,
negatable = false,
+ verbose = false,
hide = false;
+ final String name;
final _DartToolingDaemonOptionKind _kind;
final String? defaultsTo;
final bool negatable;
final bool hide;
final String? help;
- /// Returns an argument parser that can be used to configure the daemon.
- static ArgParser createArgParser({
- int? usageLineLength,
+ /// Show in help only when --verbose passed.
+ final bool verbose;
+
+ /// Populates an argument parser that can be used to configure the daemon.
+ static void populateArgOptions(
+ ArgParser argParser, {
+ bool verbose = false,
}) {
- final argParser = ArgParser(usageLineLength: usageLineLength);
for (final entry in DartToolingDaemonOptions.values) {
+ final hide = entry.hide || (entry.verbose && !verbose);
switch (entry._kind) {
case _DartToolingDaemonOptionKind.flag:
argParser.addFlag(
entry.name,
negatable: entry.negatable,
- hide: entry.hide,
+ hide: hide,
help: entry.help,
);
case _DartToolingDaemonOptionKind.option:
argParser.addOption(
entry.name,
- hide: entry.hide,
+ hide: hide,
help: entry.help,
defaultsTo: entry.defaultsTo,
);
}
}
- return argParser;
}
}
@@ -105,10 +127,12 @@
DartToolingDaemon._({
required this.secret,
required bool unrestrictedMode,
+ bool disableServiceAuthCodes = false,
bool ipv6 = false,
bool shouldLogRequests = false,
bool useFakeAnalytics = false,
}) : _ipv6 = ipv6,
+ _uriAuthCode = disableServiceAuthCodes ? null : _generateSecret(),
_shouldLogRequests = shouldLogRequests {
streamManager = DTDStreamManager(this);
clientManager = DTDClientManager();
@@ -135,12 +159,12 @@
final String secret;
- /// Any requests to DTD must have this token as the first element of the
- /// uri path.
+ /// If non-null, any requests to DTD must have this code as the first element
+ /// of the uri path.
///
/// This provides an obfuscation step to prevent bad actors from stumbling
/// onto the dtd address.
- final String _uriToken = _generateSecret();
+ final String? _uriAuthCode;
/// The uri of the current [DartToolingDaemon] service.
Uri? get uri => _uri;
@@ -187,7 +211,7 @@
scheme: 'ws',
host: host,
port: _server.port,
- path: '/$_uriToken',
+ path: _uriAuthCode != null ? '/$_uriAuthCode' : '',
);
}
@@ -205,7 +229,8 @@
bool shouldLogRequests = false,
SendPort? sendPort,
}) async {
- final argParser = DartToolingDaemonOptions.createArgParser();
+ final argParser = ArgParser();
+ DartToolingDaemonOptions.populateArgOptions(argParser);
final parsedArgs = argParser.parse(args);
if (parsedArgs.wasParsed(DartToolingDaemonOptions.train.name)) {
return null;
@@ -213,6 +238,8 @@
final machineMode = parsedArgs[DartToolingDaemonOptions.machine.name];
final unrestrictedMode =
parsedArgs[DartToolingDaemonOptions.unrestricted.name];
+ final disableServiceAuthCodes =
+ parsedArgs[DartToolingDaemonOptions.disableServiceAuthCodes.name];
final useFakeAnalytics =
parsedArgs[DartToolingDaemonOptions.fakeAnalytics.name];
final port =
@@ -222,6 +249,7 @@
final dtd = DartToolingDaemon._(
secret: secret,
unrestrictedMode: unrestrictedMode,
+ disableServiceAuthCodes: disableServiceAuthCodes,
ipv6: ipv6,
shouldLogRequests: shouldLogRequests,
useFakeAnalytics: useFakeAnalytics,
@@ -262,15 +290,17 @@
}
Handler _uriTokenHandler(Handler innerHandler) => (Request request) {
- final forbidden =
- Response.forbidden('missing or invalid authentication code');
- final pathSegments = request.url.pathSegments;
- if (pathSegments.isEmpty) {
- return forbidden;
- }
- final token = pathSegments[0];
- if (token != _uriToken) {
- return forbidden;
+ if (_uriAuthCode != null) {
+ final forbidden =
+ Response.forbidden('missing or invalid authentication code');
+ final pathSegments = request.url.pathSegments;
+ if (pathSegments.isEmpty) {
+ return forbidden;
+ }
+ final clientProvidedCode = pathSegments[0];
+ if (clientProvidedCode != _uriAuthCode) {
+ return forbidden;
+ }
}
return innerHandler(request);
};
diff --git a/pkg/dtd_impl/test/dtd_test.dart b/pkg/dtd_impl/test/dtd_test.dart
index 7857696..21d5c06 100644
--- a/pkg/dtd_impl/test/dtd_test.dart
+++ b/pkg/dtd_impl/test/dtd_test.dart
@@ -20,38 +20,57 @@
await dtd?.close();
});
- test(
- 'forbids connections where the uri token is not the first element in the path',
- () async {
- dtd = await DartToolingDaemon.startService([]);
+ group('auth tokens', () {
+ test('forbids connections where the URI auth code is invalid', () async {
+ dtd = await DartToolingDaemon.startService([]);
+ expect(dtd!.uri!.path, isNotEmpty); // Has code.
- expect(
- () async => await WebSocket.connect(
- dtd!.uri!.replace(path: 'someInvalidToken').toString(), // invalid token
- ),
- throwsA(
- predicate(
- (p0) =>
- p0 is WebSocketException &&
- RegExp("^Connection to '.*' was not upgraded to websocket\$")
- .hasMatch(p0.message),
+ expect(
+ () async => await WebSocket.connect(
+ dtd!.uri!.replace(path: 'someInvalidCode').toString(),
),
- ),
- );
+ throwsA(
+ predicate(
+ (p0) =>
+ p0 is WebSocketException &&
+ RegExp("^Connection to '.*' was not upgraded to websocket\$")
+ .hasMatch(p0.message),
+ ),
+ ),
+ );
+ });
- expect(
- () async => await WebSocket.connect(
- dtd!.uri!.replace(path: '').toString(), // no token
- ),
- throwsA(
- predicate(
- (p0) =>
- p0 is WebSocketException &&
- RegExp("^Connection to '.*' was not upgraded to websocket\$")
- .hasMatch(p0.message),
+ test('forbids connections where the URI auth code is missing', () async {
+ dtd = await DartToolingDaemon.startService([]);
+
+ expect(
+ () async => await WebSocket.connect(
+ dtd!.uri!.replace(path: '').toString(),
),
- ),
- );
+ throwsA(
+ predicate(
+ (p0) =>
+ p0 is WebSocketException &&
+ RegExp("^Connection to '.*' was not upgraded to websocket\$")
+ .hasMatch(p0.message),
+ ),
+ ),
+ );
+ });
+
+ test(
+ 'allows connections with no URI auth code if started with --disable-service-auth-codes',
+ () async {
+ dtd = await DartToolingDaemon.startService([
+ '--disable-service-auth-codes',
+ ]);
+
+ expect(dtd!.uri!.path, isEmpty); // No code.
+
+ // Expect no exception.
+ final ws = await WebSocket.connect(dtd!.uri!.toString());
+ await ws.close();
+ });
});
group('dtd', () {