From a864787329fdca5cc2748ca65471f9f3ce651cf9 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 16 Mar 2022 14:55:21 +0100 Subject: [PATCH] Add isAdmin to RpcState. --- lib/app/views/app_failure_screen.dart | 7 +++- lib/app/views/no_device_screen.dart | 10 +++-- lib/desktop/models.dart | 5 ++- lib/desktop/models.freezed.dart | 58 ++++++++++++++++++++++----- lib/desktop/models.g.dart | 11 +++++ lib/desktop/state.dart | 4 +- lib/fido/views/fido_screen.dart | 14 ++++++- ykman-rpc/rpc/device.py | 10 ++++- 8 files changed, 98 insertions(+), 21 deletions(-) diff --git a/lib/app/views/app_failure_screen.dart b/lib/app/views/app_failure_screen.dart index 358105ae..eb0cefb0 100755 --- a/lib/app/views/app_failure_screen.dart +++ b/lib/app/views/app_failure_screen.dart @@ -10,8 +10,11 @@ class AppFailureScreen extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, - children: const [ - Text('Failed to connect'), + children: [ + Text( + reason, + textAlign: TextAlign.center, + ), ], ), ); diff --git a/lib/app/views/no_device_screen.dart b/lib/app/views/no_device_screen.dart index a44b8192..9cf0d9d9 100755 --- a/lib/app/views/no_device_screen.dart +++ b/lib/app/views/no_device_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/models.dart'; +import '../../desktop/state.dart'; import '../models.dart'; import 'device_avatar.dart'; @@ -11,12 +12,13 @@ class NoDeviceScreen extends ConsumerWidget { final DeviceNode? node; const NoDeviceScreen(this.node, {Key? key}) : super(key: key); - String _getErrorMessage(UsbPid pid) { + String _getErrorMessage(WidgetRef ref, UsbPid pid) { // TODO: Handle more cases if (pid.usbInterfaces == UsbInterface.fido.value) { if (Platform.isWindows) { - // TODO: Only when not admin! - return 'WebAuthn management requires elevated privileges.\nRestart this app as administrator.'; + if (!ref.watch(rpcStateProvider.select((state) => state.isAdmin))) { + return 'WebAuthn management requires elevated privileges.\nRestart this app as administrator.'; + } } } return 'This YubiKey cannot be accessed'; @@ -31,7 +33,7 @@ class NoDeviceScreen extends ConsumerWidget { return [ const DeviceAvatar(child: Icon(Icons.usb_off)), Text( - _getErrorMessage(node.pid), + _getErrorMessage(ref, node.pid), textAlign: TextAlign.center, ), ]; diff --git a/lib/desktop/models.dart b/lib/desktop/models.dart index d96326a3..82677dca 100755 --- a/lib/desktop/models.dart +++ b/lib/desktop/models.dart @@ -16,5 +16,8 @@ class RpcResponse with _$RpcResponse { @freezed class RpcState with _$RpcState { - const factory RpcState(String version) = _RpcState; + const factory RpcState(String version, bool isAdmin) = _RpcState; + + factory RpcState.fromJson(Map json) => + _$RpcStateFromJson(json); } diff --git a/lib/desktop/models.freezed.dart b/lib/desktop/models.freezed.dart index 0cf9a8ab..33fe3bc9 100755 --- a/lib/desktop/models.freezed.dart +++ b/lib/desktop/models.freezed.dart @@ -651,15 +651,24 @@ abstract class RpcError implements RpcResponse { throw _privateConstructorUsedError; } +RpcState _$RpcStateFromJson(Map json) { + return _RpcState.fromJson(json); +} + /// @nodoc class _$RpcStateTearOff { const _$RpcStateTearOff(); - _RpcState call(String version) { + _RpcState call(String version, bool isAdmin) { return _RpcState( version, + isAdmin, ); } + + RpcState fromJson(Map json) { + return RpcState.fromJson(json); + } } /// @nodoc @@ -668,7 +677,9 @@ const $RpcState = _$RpcStateTearOff(); /// @nodoc mixin _$RpcState { String get version => throw _privateConstructorUsedError; + bool get isAdmin => throw _privateConstructorUsedError; + Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) $RpcStateCopyWith get copyWith => throw _privateConstructorUsedError; @@ -678,7 +689,7 @@ mixin _$RpcState { abstract class $RpcStateCopyWith<$Res> { factory $RpcStateCopyWith(RpcState value, $Res Function(RpcState) then) = _$RpcStateCopyWithImpl<$Res>; - $Res call({String version}); + $Res call({String version, bool isAdmin}); } /// @nodoc @@ -692,12 +703,17 @@ class _$RpcStateCopyWithImpl<$Res> implements $RpcStateCopyWith<$Res> { @override $Res call({ Object? version = freezed, + Object? isAdmin = freezed, }) { return _then(_value.copyWith( version: version == freezed ? _value.version : version // ignore: cast_nullable_to_non_nullable as String, + isAdmin: isAdmin == freezed + ? _value.isAdmin + : isAdmin // ignore: cast_nullable_to_non_nullable + as bool, )); } } @@ -707,7 +723,7 @@ abstract class _$RpcStateCopyWith<$Res> implements $RpcStateCopyWith<$Res> { factory _$RpcStateCopyWith(_RpcState value, $Res Function(_RpcState) then) = __$RpcStateCopyWithImpl<$Res>; @override - $Res call({String version}); + $Res call({String version, bool isAdmin}); } /// @nodoc @@ -722,27 +738,37 @@ class __$RpcStateCopyWithImpl<$Res> extends _$RpcStateCopyWithImpl<$Res> @override $Res call({ Object? version = freezed, + Object? isAdmin = freezed, }) { return _then(_RpcState( version == freezed ? _value.version : version // ignore: cast_nullable_to_non_nullable as String, + isAdmin == freezed + ? _value.isAdmin + : isAdmin // ignore: cast_nullable_to_non_nullable + as bool, )); } } /// @nodoc - +@JsonSerializable() class _$_RpcState implements _RpcState { - const _$_RpcState(this.version); + const _$_RpcState(this.version, this.isAdmin); + + factory _$_RpcState.fromJson(Map json) => + _$$_RpcStateFromJson(json); @override final String version; + @override + final bool isAdmin; @override String toString() { - return 'RpcState(version: $version)'; + return 'RpcState(version: $version, isAdmin: $isAdmin)'; } @override @@ -750,25 +776,37 @@ class _$_RpcState implements _RpcState { return identical(this, other) || (other.runtimeType == runtimeType && other is _RpcState && - const DeepCollectionEquality().equals(other.version, version)); + const DeepCollectionEquality().equals(other.version, version) && + const DeepCollectionEquality().equals(other.isAdmin, isAdmin)); } @override - int get hashCode => - Object.hash(runtimeType, const DeepCollectionEquality().hash(version)); + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(version), + const DeepCollectionEquality().hash(isAdmin)); @JsonKey(ignore: true) @override _$RpcStateCopyWith<_RpcState> get copyWith => __$RpcStateCopyWithImpl<_RpcState>(this, _$identity); + + @override + Map toJson() { + return _$$_RpcStateToJson(this); + } } abstract class _RpcState implements RpcState { - const factory _RpcState(String version) = _$_RpcState; + const factory _RpcState(String version, bool isAdmin) = _$_RpcState; + + factory _RpcState.fromJson(Map json) = _$_RpcState.fromJson; @override String get version; @override + bool get isAdmin; + @override @JsonKey(ignore: true) _$RpcStateCopyWith<_RpcState> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/desktop/models.g.dart b/lib/desktop/models.g.dart index f261f315..d5e0a84b 100755 --- a/lib/desktop/models.g.dart +++ b/lib/desktop/models.g.dart @@ -42,3 +42,14 @@ Map _$$RpcErrorToJson(_$RpcError instance) => 'body': instance.body, 'kind': instance.$type, }; + +_$_RpcState _$$_RpcStateFromJson(Map json) => _$_RpcState( + json['version'] as String, + json['is_admin'] as bool, + ); + +Map _$$_RpcStateToJson(_$_RpcState instance) => + { + 'version': instance.version, + 'is_admin': instance.isAdmin, + }; diff --git a/lib/desktop/state.dart b/lib/desktop/state.dart index e59c28ca..a1c56940 100755 --- a/lib/desktop/state.dart +++ b/lib/desktop/state.dart @@ -31,14 +31,14 @@ final rpcStateProvider = StateNotifierProvider<_RpcStateNotifier, RpcState>( class _RpcStateNotifier extends StateNotifier { final RpcSession rpc; - _RpcStateNotifier(this.rpc) : super(const RpcState('unknown')) { + _RpcStateNotifier(this.rpc) : super(const RpcState('unknown', false)) { _init(); } _init() async { final response = await rpc.command('get', []); if (mounted) { - state = state.copyWith(version: response['data']['version']); + state = RpcState.fromJson(response['data']); } } } diff --git a/lib/fido/views/fido_screen.dart b/lib/fido/views/fido_screen.dart index 8e0e8e20..73fa963e 100755 --- a/lib/fido/views/fido_screen.dart +++ b/lib/fido/views/fido_screen.dart @@ -1,5 +1,8 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:yubico_authenticator/desktop/state.dart'; import '../../app/models.dart'; import '../../app/views/app_failure_screen.dart'; @@ -14,7 +17,16 @@ class FidoScreen extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) => ref.watch(fidoStateProvider(deviceData.node.path)).when( none: () => const AppLoadingScreen(), - failure: (reason) => AppFailureScreen(reason), + failure: (reason) { + if (Platform.isWindows) { + if (!ref + .watch(rpcStateProvider.select((state) => state.isAdmin))) { + return const AppFailureScreen( + 'WebAuthn management requires elevated privileges.\nRestart this app as administrator.'); + } + } + return AppFailureScreen(reason); + }, success: (state) => ListView( children: [ Text('${state.info}'), diff --git a/ykman-rpc/rpc/device.py b/ykman-rpc/rpc/device.py index d94f3c56..81a5bbd3 100644 --- a/ykman-rpc/rpc/device.py +++ b/ykman-rpc/rpc/device.py @@ -55,11 +55,19 @@ from dataclasses import asdict from typing import Mapping, Tuple import os +import sys +import ctypes import logging logger = logging.getLogger(__name__) +def _is_admin(): + if sys.platform == "win32": + return bool(ctypes.windll.shell32.IsUserAnAdmin()) + return os.getuid() == 0 + + class RootNode(RpcNode): def __init__(self): super().__init__() @@ -78,7 +86,7 @@ class RootNode(RpcNode): return self._child def get_data(self): - return dict(version=ykman_version) + return dict(version=ykman_version, is_admin=_is_admin()) @child def usb(self):