2022-01-27 14:34:29 +03:00
|
|
|
import 'dart:async';
|
2022-06-05 20:22:00 +03:00
|
|
|
import 'dart:convert';
|
2022-01-27 14:34:29 +03:00
|
|
|
import 'dart:io';
|
|
|
|
|
2022-06-05 20:22:00 +03:00
|
|
|
import 'package:flutter/foundation.dart';
|
2022-03-25 10:36:29 +03:00
|
|
|
import 'package:flutter/material.dart';
|
2022-06-05 20:22:00 +03:00
|
|
|
import 'package:flutter/services.dart';
|
2022-01-27 14:34:29 +03:00
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import 'package:logging/logging.dart';
|
2022-02-24 16:05:30 +03:00
|
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
2022-01-27 14:34:29 +03:00
|
|
|
import 'package:window_manager/window_manager.dart';
|
2022-05-03 12:24:25 +03:00
|
|
|
import 'package:yubico_authenticator/app/logging.dart';
|
2022-01-27 14:34:29 +03:00
|
|
|
|
2022-03-25 10:36:29 +03:00
|
|
|
import '../app/app.dart';
|
|
|
|
import '../app/views/main_page.dart';
|
|
|
|
import '../core/state.dart';
|
2022-03-15 19:16:14 +03:00
|
|
|
import '../fido/state.dart';
|
2022-02-22 13:15:15 +03:00
|
|
|
import '../oath/state.dart';
|
2022-03-14 13:48:39 +03:00
|
|
|
import '../app/models.dart';
|
2022-01-27 14:34:29 +03:00
|
|
|
import '../app/state.dart';
|
2022-03-14 13:48:39 +03:00
|
|
|
import '../management/state.dart';
|
2022-03-15 19:16:14 +03:00
|
|
|
import 'fido/state.dart';
|
2022-03-04 15:42:10 +03:00
|
|
|
import 'management/state.dart';
|
2022-02-22 13:15:15 +03:00
|
|
|
import 'oath/state.dart';
|
2022-01-27 14:34:29 +03:00
|
|
|
import 'rpc.dart';
|
2022-02-22 13:15:15 +03:00
|
|
|
import 'devices.dart';
|
|
|
|
import 'qr_scanner.dart';
|
|
|
|
import 'state.dart';
|
2022-01-27 14:34:29 +03:00
|
|
|
|
2022-02-21 11:38:09 +03:00
|
|
|
final _log = Logger('desktop.init');
|
2022-02-24 16:05:30 +03:00
|
|
|
const String _keyWidth = 'DESKTOP_WINDOW_WIDTH';
|
|
|
|
const String _keyHeight = 'DESKTOP_WINDOW_HEIGHT';
|
2022-02-21 11:38:09 +03:00
|
|
|
|
2022-02-24 16:05:30 +03:00
|
|
|
class _WindowResizeListener extends WindowListener {
|
|
|
|
final SharedPreferences _prefs;
|
|
|
|
_WindowResizeListener(this._prefs);
|
|
|
|
|
|
|
|
@override
|
|
|
|
void onWindowResize() async {
|
|
|
|
final size = await windowManager.getSize();
|
|
|
|
await _prefs.setDouble(_keyWidth, size.width);
|
|
|
|
await _prefs.setDouble(_keyHeight, size.height);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-29 15:41:40 +03:00
|
|
|
Future<Widget> initialize(List<String> argv) async {
|
|
|
|
_initLogging(argv);
|
2022-03-25 10:36:29 +03:00
|
|
|
|
2022-01-27 14:34:29 +03:00
|
|
|
await windowManager.ensureInitialized();
|
2022-03-25 10:36:29 +03:00
|
|
|
final prefs = await SharedPreferences.getInstance();
|
2022-01-27 14:34:29 +03:00
|
|
|
|
|
|
|
unawaited(windowManager.waitUntilReadyToShow().then((_) async {
|
2022-02-22 15:16:14 +03:00
|
|
|
await windowManager.setMinimumSize(const Size(270, 0));
|
2022-09-26 12:59:07 +03:00
|
|
|
final width = prefs.getDouble(_keyWidth) ?? 400;
|
|
|
|
final height = prefs.getDouble(_keyHeight) ?? 720;
|
|
|
|
await windowManager.setSize(Size(width, height));
|
|
|
|
await windowManager.show();
|
|
|
|
windowManager.addListener(_WindowResizeListener(prefs));
|
2022-01-27 14:34:29 +03:00
|
|
|
}));
|
|
|
|
|
2022-05-06 11:18:46 +03:00
|
|
|
// Either use the _HELPER_PATH environment variable, or look relative to executable.
|
|
|
|
var exe = Platform.environment['_HELPER_PATH'];
|
2022-01-27 14:34:29 +03:00
|
|
|
if (exe?.isEmpty ?? true) {
|
2022-05-06 11:18:46 +03:00
|
|
|
var relativePath = 'helper/authenticator-helper';
|
2022-01-27 14:34:29 +03:00
|
|
|
if (Platform.isMacOS) {
|
2022-05-12 09:34:51 +03:00
|
|
|
relativePath = '../Resources/$relativePath';
|
2022-01-27 14:34:29 +03:00
|
|
|
} else if (Platform.isWindows) {
|
|
|
|
relativePath += '.exe';
|
|
|
|
}
|
|
|
|
exe = Uri.file(Platform.resolvedExecutable)
|
|
|
|
.resolve(relativePath)
|
|
|
|
.toFilePath();
|
2022-05-16 18:07:33 +03:00
|
|
|
|
|
|
|
if (Platform.isMacOS && Platform.version.contains('arm64')) {
|
|
|
|
// See if there is an arm64 specific helper on arm64 Mac.
|
|
|
|
final arm64exe = Uri.file(exe)
|
|
|
|
.resolve('../helper-arm64/authenticator-helper')
|
|
|
|
.toFilePath();
|
2022-05-24 12:42:13 +03:00
|
|
|
if (await File(arm64exe).exists()) {
|
2022-05-16 18:07:33 +03:00
|
|
|
exe = arm64exe;
|
|
|
|
}
|
|
|
|
}
|
2022-01-27 14:34:29 +03:00
|
|
|
}
|
|
|
|
|
2022-05-06 11:18:46 +03:00
|
|
|
_log.info('Starting Helper subprocess: $exe');
|
2022-03-30 17:45:47 +03:00
|
|
|
final rpc = RpcSession(exe!);
|
|
|
|
await rpc.initialize();
|
2022-05-06 11:18:46 +03:00
|
|
|
_log.info('Helper process started', exe);
|
2022-01-27 14:34:29 +03:00
|
|
|
rpc.setLogLevel(Logger.root.level);
|
|
|
|
|
2022-06-05 20:22:00 +03:00
|
|
|
_initLicenses();
|
|
|
|
|
2022-03-25 10:36:29 +03:00
|
|
|
return ProviderScope(
|
|
|
|
overrides: [
|
|
|
|
supportedAppsProvider.overrideWithValue([
|
|
|
|
Application.oath,
|
|
|
|
Application.fido,
|
|
|
|
Application.management,
|
|
|
|
]),
|
|
|
|
prefProvider.overrideWithValue(prefs),
|
|
|
|
rpcProvider.overrideWithValue(rpc),
|
|
|
|
windowStateProvider.overrideWithProvider(desktopWindowStateProvider),
|
|
|
|
attachedDevicesProvider.overrideWithProvider(desktopDevicesProvider),
|
2022-04-03 12:05:37 +03:00
|
|
|
currentDeviceProvider.overrideWithProvider(desktopCurrentDeviceProvider),
|
2022-03-25 10:36:29 +03:00
|
|
|
currentDeviceDataProvider.overrideWithProvider(desktopDeviceDataProvider),
|
2022-04-03 12:05:37 +03:00
|
|
|
// OATH
|
2022-03-25 10:36:29 +03:00
|
|
|
oathStateProvider.overrideWithProvider(desktopOathState),
|
|
|
|
credentialListProvider
|
|
|
|
.overrideWithProvider(desktopOathCredentialListProvider),
|
|
|
|
qrScannerProvider.overrideWithProvider(desktopQrScannerProvider),
|
2022-04-03 12:05:37 +03:00
|
|
|
// Management
|
2022-03-25 10:36:29 +03:00
|
|
|
managementStateProvider.overrideWithProvider(desktopManagementState),
|
2022-04-03 12:05:37 +03:00
|
|
|
// FIDO
|
2022-03-25 10:36:29 +03:00
|
|
|
fidoStateProvider.overrideWithProvider(desktopFidoState),
|
|
|
|
fingerprintProvider.overrideWithProvider(desktopFingerprintProvider),
|
|
|
|
credentialProvider.overrideWithProvider(desktopCredentialProvider),
|
2022-09-23 11:17:28 +03:00
|
|
|
clipboardProvider.overrideWithProvider(desktopClipboardProvider),
|
|
|
|
supportedThemesProvider.overrideWithProvider(desktopSupportedThemesProvider)
|
2022-03-25 10:36:29 +03:00
|
|
|
],
|
2022-05-25 17:10:26 +03:00
|
|
|
child: YubicoAuthenticatorApp(
|
|
|
|
page: Consumer(
|
|
|
|
builder: ((_, ref, child) {
|
|
|
|
// keep RPC log level in sync with app
|
|
|
|
ref.listen<Level>(logLevelProvider, (_, level) {
|
|
|
|
rpc.setLogLevel(level);
|
|
|
|
});
|
|
|
|
|
|
|
|
return const MainPage();
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
),
|
2022-03-25 10:36:29 +03:00
|
|
|
);
|
2022-01-27 14:34:29 +03:00
|
|
|
}
|
2022-03-25 14:23:14 +03:00
|
|
|
|
2022-04-29 15:41:40 +03:00
|
|
|
void _initLogging(List<String> argv) {
|
2022-03-25 14:23:14 +03:00
|
|
|
Logger.root.onRecord.listen((record) {
|
2022-05-30 17:04:50 +03:00
|
|
|
stderr.writeln(
|
|
|
|
'${record.time.logFormat} [${record.loggerName}] ${record.level}: ${record.message}');
|
2022-03-25 14:23:14 +03:00
|
|
|
if (record.error != null) {
|
|
|
|
stderr.writeln(record.error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-04-29 15:41:40 +03:00
|
|
|
final logLevelIndex = argv.indexOf('--log-level');
|
2022-03-25 14:23:14 +03:00
|
|
|
if (logLevelIndex != -1) {
|
|
|
|
try {
|
2022-04-29 15:41:40 +03:00
|
|
|
final levelName = argv[logLevelIndex + 1];
|
2022-05-03 12:24:25 +03:00
|
|
|
Level level = Levels.LEVELS
|
2022-03-25 14:23:14 +03:00
|
|
|
.firstWhere((level) => level.name == levelName.toUpperCase());
|
|
|
|
Logger.root.level = level;
|
|
|
|
_log.info('Log level initialized from command line argument');
|
|
|
|
} catch (error) {
|
2022-05-03 12:24:25 +03:00
|
|
|
_log.error('Failed to set log level', error);
|
2022-03-25 14:23:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_log.info('Logging initialized, outputting to stderr');
|
|
|
|
}
|
2022-06-05 20:22:00 +03:00
|
|
|
|
|
|
|
void _initLicenses() async {
|
|
|
|
LicenseRegistry.addLicense(() async* {
|
|
|
|
final python = await rootBundle.loadString('assets/licenses/python.txt');
|
|
|
|
yield LicenseEntryWithLineBreaks(['Python'], python);
|
|
|
|
|
|
|
|
final helper = await rootBundle.loadStructuredData<List>(
|
|
|
|
'assets/licenses/helper.json',
|
|
|
|
(value) async => jsonDecode(value),
|
|
|
|
);
|
|
|
|
|
|
|
|
for (final e in helper) {
|
|
|
|
yield LicenseEntryWithLineBreaks([e['Name']], e['LicenseText']);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|