From: Dominik Csapak <d.csapak@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH pve-flutter-frontend 1/5] console: use flutter inappwebview as webview
Date: Mon, 15 Apr 2024 12:30:23 +0200 [thread overview]
Message-ID: <20240415103027.3000412-5-d.csapak@proxmox.com> (raw)
In-Reply-To: <20240415103027.3000412-1-d.csapak@proxmox.com>
instead of flutter webview. This new dependency does not have the
limitations of the "official" flutter webview. We now can ignore
https errors and even get some info on the certificate.
We use that to now show a 'do you want to trust this cert' dialog with
the fingerprint of the server, which the user can then either trust or
abort.
When pressing yes, we save that fingerprint in the shared preferences so
that we don't have to ask the next time.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
this depends on the login-manager patch to add the 'trustedFingerprints' list
| 128 ++++++++++++++++++++---
pubspec.lock | 42 ++------
pubspec.yaml | 3 +-
3 files changed, 126 insertions(+), 47 deletions(-)
--git a/lib/widgets/pve_console_menu_widget.dart b/lib/widgets/pve_console_menu_widget.dart
index 767a51c..243baf1 100644
--- a/lib/widgets/pve_console_menu_widget.dart
+++ b/lib/widgets/pve_console_menu_widget.dart
@@ -6,7 +6,9 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart';
-import 'package:webview_flutter/webview_flutter.dart';
+import 'package:proxmox_login_manager/proxmox_general_settings_model.dart';
+import 'package:flutter_inappwebview/flutter_inappwebview.dart';
+import 'package:crypto/crypto.dart';
class PveConsoleMenu extends StatelessWidget {
static const platform =
@@ -104,8 +106,7 @@ class PveConsoleMenu extends StatelessWidget {
"noVNC Console", // xterm.js doesn't work that well on mobile
style: TextStyle(fontWeight: FontWeight.bold),
),
- subtitle: const Text(
- "Open console view (requires trusted SSL certificate)"),
+ subtitle: const Text("Open console view"),
onTap: () async {
if (Platform.isAndroid) {
if (['qemu', 'lxc'].contains(type)) {
@@ -209,6 +210,8 @@ class PVEWebConsole extends StatefulWidget {
}
class PVEWebConsoleState extends State<PVEWebConsole> {
+ InAppWebViewController? webViewController;
+
@override
Widget build(BuildContext context) {
final ticket = widget.apiClient.credentials.ticket!;
@@ -222,24 +225,123 @@ class PVEWebConsoleState extends State<PVEWebConsole> {
} else {
consoleUrl += "&console=shell";
}
-
- final controller = WebViewController()
- ..setJavaScriptMode(JavaScriptMode.unrestricted)
- ..setBackgroundColor(Theme.of(context).colorScheme.background);
+ WidgetsFlutterBinding.ensureInitialized();
return FutureBuilder(
- future: WebViewCookieManager().setCookie(WebViewCookie(
+ future: CookieManager.instance().setCookie(
+ url: Uri.parse(consoleUrl),
name: 'PVEAuthCookie',
value: ticket,
- domain: baseUrl.origin,
- )),
+ ),
builder: (context, snapshot) {
- controller.loadRequest(Uri.parse(consoleUrl));
return SafeArea(
- child: WebViewWidget(
- controller: controller,
+ child: InAppWebView(
+ onReceivedServerTrustAuthRequest: (controller, challenge) async {
+ final cert = challenge.protectionSpace.sslCertificate;
+ final certBytes = cert?.x509Certificate?.encoded;
+ final sslError = challenge.protectionSpace.sslError?.message;
+
+ String? issuedTo = cert?.issuedTo?.CName.toString();
+ String? hash = certBytes != null
+ ? sha256.convert(certBytes).toString()
+ : null;
+
+ final settings =
+ await ProxmoxGeneralSettingsModel.fromLocalStorage();
+
+ bool trust = false;
+ if (hash != null && settings.trustedFingerprints != null) {
+ trust = settings.trustedFingerprints!.contains(hash);
+ }
+
+ if (!trust) {
+ // format hash to '01:23:...' format
+ String? formattedHash = hash?.toUpperCase().replaceAllMapped(
+ RegExp(r"[a-zA-Z0-9]{2}"),
+ (match) => "${match.group(0)}:");
+ formattedHash = formattedHash?.substring(
+ 0, formattedHash.length - 1); // remove last ':'
+
+ if (context.mounted) {
+ trust = await showTLSWarning(
+ context,
+ sslError ?? 'An unknown TLS error has occurred',
+ issuedTo ?? 'unknown',
+ formattedHash ?? 'unknown');
+ }
+ }
+
+ // save Fingerprint
+ if (trust && hash != null) {
+ await settings
+ .rebuild((b) => b..trustedFingerprints.add(hash))
+ .toLocalStorage();
+ print(settings.toJson());
+ }
+
+ final action = trust
+ ? ServerTrustAuthResponseAction.PROCEED
+ : ServerTrustAuthResponseAction.CANCEL;
+ return ServerTrustAuthResponse(action: action);
+ },
+ onWebViewCreated: (controller) {
+ webViewController = controller;
+ controller.loadUrl(
+ urlRequest: URLRequest(url: Uri.parse(consoleUrl)));
+ },
),
);
});
}
+
+ Future<bool> showTLSWarning(BuildContext context, String sslError,
+ String issuedTo, String hash) async {
+ return await showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (BuildContext context) {
+ return PopScope(
+ // prevent back button from canceling this callback
+ canPop: false,
+ child: AlertDialog(
+ title: const Text('An TLS error has occurred:'),
+ content: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ ListTile(
+ title: const Text('Error Message:'),
+ subtitle: Text(sslError),
+ ),
+ const ListTile(
+ title: Text('Certificate Information:'),
+ ),
+ ListTile(
+ title: const Text('Issued to:'),
+ subtitle: Text(issuedTo),
+ ),
+ ListTile(
+ title: const Text('Fingerprint:'),
+ subtitle: Text(hash),
+ ),
+ const Text(''), // spacer
+ const Text('Do you want to continue?'),
+ ],
+ ),
+ actions: <Widget>[
+ TextButton(
+ onPressed: () {
+ Navigator.of(context).pop(false);
+ Navigator.of(context).pop();
+ },
+ child: const Text('No')),
+ TextButton(
+ onPressed: () {
+ Navigator.of(context).pop(true);
+ },
+ child: const Text('Yes'))
+ ]),
+ );
+ });
+ }
}
diff --git a/pubspec.lock b/pubspec.lock
index 7e39fba..94c0656 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -170,7 +170,7 @@ packages:
source: hosted
version: "3.1.1"
crypto:
- dependency: transitive
+ dependency: "direct main"
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
@@ -230,6 +230,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_inappwebview:
+ dependency: "direct main"
+ description:
+ name: flutter_inappwebview
+ sha256: d198297060d116b94048301ee6749cd2e7d03c1f2689783f52d210a6b7aba350
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.8.0"
flutter_lints:
dependency: "direct dev"
description:
@@ -819,38 +827,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.5"
- webview_flutter:
- dependency: "direct main"
- description:
- name: webview_flutter
- sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932"
- url: "https://pub.dev"
- source: hosted
- version: "4.7.0"
- webview_flutter_android:
- dependency: transitive
- description:
- name: webview_flutter_android
- sha256: f038ee2fae73b509dde1bc9d2c5a50ca92054282de17631a9a3d515883740934
- url: "https://pub.dev"
- source: hosted
- version: "3.16.0"
- webview_flutter_platform_interface:
- dependency: transitive
- description:
- name: webview_flutter_platform_interface
- sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d
- url: "https://pub.dev"
- source: hosted
- version: "2.10.0"
- webview_flutter_wkwebview:
- dependency: transitive
- description:
- name: webview_flutter_wkwebview
- sha256: f12f8d8a99784b863e8b85e4a9a5e3cf1839d6803d2c0c3e0533a8f3c5a992a7
- url: "https://pub.dev"
- source: hosted
- version: "3.13.0"
win32:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 065749c..976dbb1 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -34,7 +34,6 @@ dependencies:
url_launcher: ^6.0.17
intl: ^0.19.0
path_provider: ^2.0.8
- webview_flutter: ^4.2.0
proxmox_dart_api_client:
path: ../proxmox_dart_api_client
proxmox_login_manager:
@@ -42,6 +41,8 @@ dependencies:
collection: ^1.15.0-nullsafety.4
shared_preferences: any
+ flutter_inappwebview: ^5.8.0
+ crypto: ^3.0.3
dev_dependencies:
flutter_test:
sdk: flutter
--
2.39.2
next prev parent reply other threads:[~2024-04-15 10:30 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-04-15 10:30 [pve-devel] [PATCH flutter-repositories] some improvements/fixes for the app Dominik Csapak
2024-04-15 10:30 ` [pve-devel] [PATCH proxmox-dart-api-client 1/1] client: correctly set parameter for node actions Dominik Csapak
2024-04-16 15:07 ` [pve-devel] applied: " Thomas Lamprecht
2024-04-15 10:30 ` [pve-devel] [PATCH proxmox-login-manager 1/2] login: show custom alert dialog for password saving errors Dominik Csapak
2024-04-16 15:08 ` [pve-devel] applied: " Thomas Lamprecht
2024-04-15 10:30 ` [pve-devel] [PATCH proxmox-login-manager 2/2] general settings: add trustedFingerprints list Dominik Csapak
2024-04-15 10:30 ` Dominik Csapak [this message]
2024-04-15 10:30 ` [pve-devel] [PATCH pve-flutter-frontend 2/5] console: wrap console with appbar Dominik Csapak
2024-04-15 10:30 ` [pve-devel] [PATCH pve-flutter-frontend 3/5] node overview: use correct color for rrd icons Dominik Csapak
2024-04-15 10:30 ` [pve-devel] [PATCH pve-flutter-frontend 4/5] nove overview: add power settings menu Dominik Csapak
2024-04-16 15:11 ` Thomas Lamprecht
2024-04-15 10:30 ` [pve-devel] [PATCH pve-flutter-frontend 5/5] improve back button behavior Dominik Csapak
2024-04-16 15:11 ` [pve-devel] applied: [PATCH flutter-repositories] some improvements/fixes for the app Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240415103027.3000412-5-d.csapak@proxmox.com \
--to=d.csapak@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox