Add isAdmin to RpcState.

This commit is contained in:
Dain Nilsson 2022-03-16 14:55:21 +01:00
parent 6b43fb3799
commit a864787329
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
8 changed files with 98 additions and 21 deletions

View File

@ -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,
),
],
),
);

View File

@ -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,
),
];

View File

@ -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<String, dynamic> json) =>
_$RpcStateFromJson(json);
}

View File

@ -651,15 +651,24 @@ abstract class RpcError implements RpcResponse {
throw _privateConstructorUsedError;
}
RpcState _$RpcStateFromJson(Map<String, dynamic> 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<String, Object?> 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<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$RpcStateCopyWith<RpcState> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> json) = _$_RpcState.fromJson;
@override
String get version;
@override
bool get isAdmin;
@override
@JsonKey(ignore: true)
_$RpcStateCopyWith<_RpcState> get copyWith =>
throw _privateConstructorUsedError;

View File

@ -42,3 +42,14 @@ Map<String, dynamic> _$$RpcErrorToJson(_$RpcError instance) =>
'body': instance.body,
'kind': instance.$type,
};
_$_RpcState _$$_RpcStateFromJson(Map<String, dynamic> json) => _$_RpcState(
json['version'] as String,
json['is_admin'] as bool,
);
Map<String, dynamic> _$$_RpcStateToJson(_$_RpcState instance) =>
<String, dynamic>{
'version': instance.version,
'is_admin': instance.isAdmin,
};

View File

@ -31,14 +31,14 @@ final rpcStateProvider = StateNotifierProvider<_RpcStateNotifier, RpcState>(
class _RpcStateNotifier extends StateNotifier<RpcState> {
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']);
}
}
}

View File

@ -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}'),

View File

@ -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):