Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 1c0eade

Browse files
authored
Hide PII from doctor validators for GitHub template (#96250)
1 parent 4df5508 commit 1c0eade

File tree

9 files changed

+201
-32
lines changed

9 files changed

+201
-32
lines changed

packages/flutter_tools/lib/runner.dart

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,18 @@ Future<int> _handleToolError(
149149
globals.printError('Oops; flutter has exited unexpectedly: "$error".');
150150

151151
try {
152+
final BufferLogger logger = BufferLogger(
153+
terminal: globals.terminal,
154+
outputPreferences: globals.outputPreferences,
155+
);
156+
157+
final DoctorText doctorText = DoctorText(logger);
158+
152159
final CrashDetails details = CrashDetails(
153160
command: _crashCommand(args),
154161
error: error,
155162
stackTrace: stackTrace,
156-
doctorText: await _doctorText(),
163+
doctorText: doctorText,
157164
);
158165
final File file = await _createLocalCrashReport(details);
159166
await globals.crashReporter.informUser(details, file);
@@ -199,7 +206,7 @@ Future<File> _createLocalCrashReport(CrashDetails details) async {
199206
buffer.writeln('```\n${details.stackTrace}```\n');
200207

201208
buffer.writeln('## flutter doctor\n');
202-
buffer.writeln('```\n${details.doctorText}```');
209+
buffer.writeln('```\n${await details.doctorText.text}```');
203210

204211
try {
205212
crashFile.writeAsStringSync(buffer.toString());
@@ -221,22 +228,6 @@ Future<File> _createLocalCrashReport(CrashDetails details) async {
221228
return crashFile;
222229
}
223230

224-
Future<String> _doctorText() async {
225-
try {
226-
final BufferLogger logger = BufferLogger(
227-
terminal: globals.terminal,
228-
outputPreferences: globals.outputPreferences,
229-
);
230-
231-
final Doctor doctor = Doctor(logger: logger);
232-
await doctor.diagnose(showColor: false);
233-
234-
return logger.statusText;
235-
} on Exception catch (error, trace) {
236-
return 'encountered exception: $error\n\n${trace.toString().trim()}\n';
237-
}
238-
}
239-
240231
Future<int> _exit(int code) async {
241232
// Prints the welcome message if needed.
242233
globals.flutterUsage.printWelcome();

packages/flutter_tools/lib/src/doctor.dart

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:meta/meta.dart';
56
import 'package:process/process.dart';
67

78
import 'android/android_studio_validator.dart';
@@ -290,11 +291,17 @@ class Doctor {
290291
}
291292

292293
/// Print information about the state of installed tooling.
294+
///
295+
/// To exclude personally identifiable information like device names and
296+
/// paths, set [showPii] to false.
293297
Future<bool> diagnose({
294298
bool androidLicenses = false,
295299
bool verbose = true,
296300
bool showColor = true,
297301
AndroidLicenseValidator? androidLicenseValidator,
302+
bool showPii = true,
303+
List<ValidatorTask>? startedValidatorTasks,
304+
bool sendEvent = true,
298305
}) async {
299306
if (androidLicenses && androidLicenseValidator != null) {
300307
return androidLicenseValidator.runLicenseManager();
@@ -306,7 +313,7 @@ class Doctor {
306313
bool doctorResult = true;
307314
int issues = 0;
308315

309-
for (final ValidatorTask validatorTask in startValidatorTasks()) {
316+
for (final ValidatorTask validatorTask in startedValidatorTasks ?? startValidatorTasks()) {
310317
final DoctorValidator validator = validatorTask.validator;
311318
final Status status = _logger.startSpinner();
312319
ValidationResult result;
@@ -334,8 +341,9 @@ class Doctor {
334341
case ValidationType.installed:
335342
break;
336343
}
337-
338-
DoctorResultEvent(validator: validator, result: result).send();
344+
if (sendEvent) {
345+
DoctorResultEvent(validator: validator, result: result).send();
346+
}
339347

340348
final String leadingBox = showColor ? result.coloredLeadingBox : result.leadingBox;
341349
if (result.statusInfo != null) {
@@ -351,7 +359,7 @@ class Doctor {
351359
int hangingIndent = 2;
352360
int indent = 4;
353361
final String indicator = showColor ? message.coloredIndicator : message.indicator;
354-
for (final String line in '$indicator ${message.message}'.split('\n')) {
362+
for (final String line in '$indicator ${showPii ? message.message : message.piiStrippedMessage}'.split('\n')) {
355363
_logger.printStatus(line, hangingIndent: hangingIndent, indent: indent, emphasis: true);
356364
// Only do hanging indent for the first line.
357365
hangingIndent = 0;
@@ -558,3 +566,34 @@ class DeviceValidator extends DoctorValidator {
558566
}
559567
}
560568
}
569+
570+
/// Wrapper for doctor to run multiple times with PII and without, running the validators only once.
571+
class DoctorText {
572+
DoctorText(
573+
BufferLogger logger, {
574+
@visibleForTesting Doctor? doctor,
575+
}) : _doctor = doctor ?? Doctor(logger: logger), _logger = logger;
576+
577+
final BufferLogger _logger;
578+
final Doctor _doctor;
579+
bool _sendDoctorEvent = true;
580+
581+
late final Future<String> text = _runDiagnosis(true);
582+
late final Future<String> piiStrippedText = _runDiagnosis(false);
583+
584+
// Start the validator tasks only once.
585+
late final List<ValidatorTask> _validatorTasks = _doctor.startValidatorTasks();
586+
587+
Future<String> _runDiagnosis(bool showPii) async {
588+
try {
589+
await _doctor.diagnose(showColor: false, startedValidatorTasks: _validatorTasks, showPii: showPii, sendEvent: _sendDoctorEvent);
590+
// Do not send the doctor event a second time.
591+
_sendDoctorEvent = false;
592+
final String text = _logger.statusText;
593+
_logger.clear();
594+
return text;
595+
} on Exception catch (error, trace) {
596+
return 'encountered exception: $error\n\n${trace.toString().trim()}\n';
597+
}
598+
}
599+
}

packages/flutter_tools/lib/src/doctor_validator.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,22 +221,27 @@ class ValidationMessage {
221221
///
222222
/// The [contextUrl] may be supplied to link to external resources. This
223223
/// is displayed after the informative message in verbose modes.
224-
const ValidationMessage(this.message, {this.contextUrl}) : type = ValidationMessageType.information;
224+
const ValidationMessage(this.message, { this.contextUrl, String? piiStrippedMessage })
225+
: type = ValidationMessageType.information, piiStrippedMessage = piiStrippedMessage ?? message;
225226

226227
/// Create a validation message with information for a failing validator.
227-
const ValidationMessage.error(this.message)
228+
const ValidationMessage.error(this.message, { String? piiStrippedMessage })
228229
: type = ValidationMessageType.error,
230+
piiStrippedMessage = piiStrippedMessage ?? message,
229231
contextUrl = null;
230232

231233
/// Create a validation message with information for a partially failing
232234
/// validator.
233-
const ValidationMessage.hint(this.message)
235+
const ValidationMessage.hint(this.message, { String? piiStrippedMessage })
234236
: type = ValidationMessageType.hint,
237+
piiStrippedMessage = piiStrippedMessage ?? message,
235238
contextUrl = null;
236239

237240
final ValidationMessageType type;
238241
final String? contextUrl;
239242
final String message;
243+
/// Optional message with PII stripped, to show instead of [message].
244+
final String piiStrippedMessage;
240245

241246
bool get isError => type == ValidationMessageType.error;
242247

packages/flutter_tools/lib/src/reporting/crash_reporting.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import '../base/io.dart';
1212
import '../base/logger.dart';
1313
import '../base/os.dart';
1414
import '../base/platform.dart';
15+
import '../doctor.dart';
1516
import '../project.dart';
1617
import 'github_template.dart';
1718
import 'reporting.dart';
@@ -49,7 +50,7 @@ class CrashDetails {
4950
final String command;
5051
final Object error;
5152
final StackTrace stackTrace;
52-
final String doctorText;
53+
final DoctorText doctorText;
5354
}
5455

5556
/// Reports information about the crash to the user.
@@ -92,7 +93,7 @@ class CrashReporter {
9293
details.command,
9394
details.error,
9495
details.stackTrace,
95-
details.doctorText,
96+
await details.doctorText.piiStrippedText,
9697
);
9798
_logger.printStatus('$gitHubTemplateURL\n', wrap: false);
9899
}

packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,14 @@ void main() {
313313
}, overrides: <Type, Generator>{
314314
Usage: () => testUsage,
315315
});
316+
317+
testUsingContext('sending events can be skipped', () async {
318+
await FakePassingDoctor(logger).diagnose(verbose: false, sendEvent: false);
319+
320+
expect(testUsage.events, isEmpty);
321+
}, overrides: <Type, Generator>{
322+
Usage: () => testUsage,
323+
});
316324
});
317325

318326
group('doctor with fake validators', () {
@@ -456,6 +464,78 @@ void main() {
456464
'! Doctor found issues in 4 categories.\n'
457465
));
458466
});
467+
468+
testUsingContext('validate PII can be hidden', () async {
469+
expect(await FakePiiDoctor(logger).diagnose(showPii: false), isTrue);
470+
expect(logger.statusText, equals(
471+
'[✓] PII Validator\n'
472+
' • Does not contain PII\n'
473+
'\n'
474+
'• No issues found!\n'
475+
));
476+
logger.clear();
477+
// PII shown.
478+
expect(await FakePiiDoctor(logger).diagnose(), isTrue);
479+
expect(logger.statusText, equals(
480+
'[✓] PII Validator\n'
481+
' • Contains PII path/to/username\n'
482+
'\n'
483+
'• No issues found!\n'
484+
));
485+
});
486+
});
487+
488+
group('doctor diagnosis wrapper', () {
489+
TestUsage testUsage;
490+
BufferLogger logger;
491+
492+
setUp(() {
493+
testUsage = TestUsage();
494+
logger = BufferLogger.test();
495+
});
496+
497+
testUsingContext('PII separated, events only sent once', () async {
498+
final Doctor fakeDoctor = FakePiiDoctor(logger);
499+
final DoctorText doctorText = DoctorText(logger, doctor: fakeDoctor);
500+
const String expectedPiiText = '[✓] PII Validator\n'
501+
' • Contains PII path/to/username\n'
502+
'\n'
503+
'• No issues found!\n';
504+
const String expectedPiiStrippedText =
505+
'[✓] PII Validator\n'
506+
' • Does not contain PII\n'
507+
'\n'
508+
'• No issues found!\n';
509+
510+
// Run each multiple times to make sure the logger buffer is being cleared,
511+
// and that events are only sent once.
512+
expect(await doctorText.text, expectedPiiText);
513+
expect(await doctorText.text, expectedPiiText);
514+
515+
expect(await doctorText.piiStrippedText, expectedPiiStrippedText);
516+
expect(await doctorText.piiStrippedText, expectedPiiStrippedText);
517+
518+
// Only one event sent.
519+
expect(testUsage.events, <TestUsageEvent>[
520+
const TestUsageEvent(
521+
'doctor-result',
522+
'PiiValidator',
523+
label: 'installed',
524+
),
525+
]);
526+
}, overrides: <Type, Generator>{
527+
Usage: () => testUsage,
528+
});
529+
530+
testUsingContext('without PII has same text and PII-stripped text', () async {
531+
final Doctor fakeDoctor = FakePassingDoctor(logger);
532+
final DoctorText doctorText = DoctorText(logger, doctor: fakeDoctor);
533+
final String piiText = await doctorText.text;
534+
expect(piiText, isNotEmpty);
535+
expect(piiText, await doctorText.piiStrippedText);
536+
}, overrides: <Type, Generator>{
537+
Usage: () => testUsage,
538+
});
459539
});
460540

461541
testUsingContext('validate non-verbose output wrapping', () async {
@@ -535,7 +615,6 @@ void main() {
535615
));
536616
});
537617

538-
539618
group('doctor with grouped validators', () {
540619
testUsingContext('validate diagnose combines validator output', () async {
541620
expect(await FakeGroupedDoctor(logger).diagnose(), isTrue);
@@ -666,6 +745,9 @@ class NoOpDoctor implements Doctor {
666745
bool verbose = true,
667746
bool showColor = true,
668747
AndroidLicenseValidator androidLicenseValidator,
748+
bool showPii = true,
749+
List<ValidatorTask> startedValidatorTasks,
750+
bool sendEvent = true,
669751
}) async => true;
670752

671753
@override
@@ -694,6 +776,18 @@ class PassingValidator extends DoctorValidator {
694776
}
695777
}
696778

779+
class PiiValidator extends DoctorValidator {
780+
PiiValidator() : super('PII Validator');
781+
782+
@override
783+
Future<ValidationResult> validate() async {
784+
const List<ValidationMessage> messages = <ValidationMessage>[
785+
ValidationMessage('Contains PII path/to/username', piiStrippedMessage: 'Does not contain PII'),
786+
];
787+
return const ValidationResult(ValidationType.installed, messages);
788+
}
789+
}
790+
697791
class MissingValidator extends DoctorValidator {
698792
MissingValidator() : super('Missing Validator');
699793

@@ -840,6 +934,19 @@ class FakeQuietDoctor extends Doctor {
840934
}
841935
}
842936

937+
/// A doctor that passes and contains PII that can be hidden.
938+
class FakePiiDoctor extends Doctor {
939+
FakePiiDoctor(Logger logger) : super(logger: logger);
940+
941+
List<DoctorValidator> _validators;
942+
@override
943+
List<DoctorValidator> get validators {
944+
return _validators ??= <DoctorValidator>[
945+
PiiValidator(),
946+
];
947+
}
948+
}
949+
843950
/// A doctor with a validator that throws an exception.
844951
class FakeCrashingDoctor extends Doctor {
845952
FakeCrashingDoctor(Logger logger) : super(logger: logger);

packages/flutter_tools/test/general.shard/analytics_test.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import 'package:flutter_tools/src/commands/build.dart';
1717
import 'package:flutter_tools/src/commands/config.dart';
1818
import 'package:flutter_tools/src/commands/doctor.dart';
1919
import 'package:flutter_tools/src/doctor.dart';
20+
import 'package:flutter_tools/src/doctor_validator.dart';
2021
import 'package:flutter_tools/src/features.dart';
2122
import 'package:flutter_tools/src/globals.dart' as globals;
2223
import 'package:flutter_tools/src/reporting/reporting.dart';
@@ -368,6 +369,9 @@ class FakeDoctor extends Fake implements Doctor {
368369
bool verbose = true,
369370
bool showColor = true,
370371
AndroidLicenseValidator androidLicenseValidator,
372+
bool showPii = true,
373+
List<ValidatorTask> startedValidatorTasks,
374+
bool sendEvent = true,
371375
}) async {
372376
return diagnoseSucceeds;
373377
}

0 commit comments

Comments
 (0)