From 473939f877712a9a51f5a6b6b2b0a8b15d124437 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jun 2022 10:24:16 +0000 Subject: [PATCH 01/38] Bump pigeon from 3.1.5 to 3.1.6 Bumps [pigeon](https://github.com/flutter/packages/tree/main/packages) from 3.1.5 to 3.1.6. - [Release notes](https://github.com/flutter/packages/releases) - [Commits](https://github.com/flutter/packages/commits/pigeon-v3.1.6/packages) --- updated-dependencies: - dependency-name: pigeon dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pubspec.lock | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) mode change 100755 => 100644 pubspec.lock mode change 100755 => 100644 pubspec.yaml diff --git a/pubspec.lock b/pubspec.lock old mode 100755 new mode 100644 index 25bd8848..c3f2952a --- a/pubspec.lock +++ b/pubspec.lock @@ -408,7 +408,7 @@ packages: name: pigeon url: "https://pub.dartlang.org" source: hosted - version: "3.1.5" + version: "3.1.6" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml old mode 100755 new mode 100644 index 6f8a5f38..a3145e88 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -70,7 +70,7 @@ dev_dependencies: build_runner: ^2.1.4 freezed: ^2.0.3 json_serializable: ^6.0.0 - pigeon: ^3.0.3 + pigeon: ^3.1.6 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 851f905c9f99fb6b8efcbde93919da05df16ea5e Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Mon, 20 Jun 2022 14:32:36 +0200 Subject: [PATCH 02/38] remove dependecy override of analyzer --- pubspec.lock | 2 +- pubspec.yaml | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index c3f2952a..74db5575 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,7 +9,7 @@ packages: source: hosted version: "38.0.0" analyzer: - dependency: "direct overridden" + dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index a3145e88..00773466 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,9 +51,6 @@ dependencies: desktop_drop: ^0.3.3 url_launcher: ^6.1.2 -dependency_overrides: - analyzer: ^3.4.1 - dev_dependencies: integration_test: sdk: flutter From 00101b21c7ae12b1eadff1a9b1d292ed45b31584 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Mon, 20 Jun 2022 14:33:16 +0200 Subject: [PATCH 03/38] regenerate pigeons --- .../app/src/main/java/com/yubico/authenticator/api/Pigeon.java | 2 +- lib/android/api/impl.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/com/yubico/authenticator/api/Pigeon.java b/android/app/src/main/java/com/yubico/authenticator/api/Pigeon.java index b718a4ed..49e70568 100644 --- a/android/app/src/main/java/com/yubico/authenticator/api/Pigeon.java +++ b/android/app/src/main/java/com/yubico/authenticator/api/Pigeon.java @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v3.0.3), do not edit directly. +// Autogenerated from Pigeon (v3.1.6), do not edit directly. // See also: https://pub.dev/packages/pigeon package com.yubico.authenticator.api; diff --git a/lib/android/api/impl.dart b/lib/android/api/impl.dart index 2f90c170..4974db18 100644 --- a/lib/android/api/impl.dart +++ b/lib/android/api/impl.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v3.0.3), do not edit directly. +// Autogenerated from Pigeon (v3.1.6), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name // @dart = 2.12 From b36c899282f109dd6f38ecddf9faf7f8ee1ddb9a Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Thu, 23 Jun 2022 14:31:36 +0200 Subject: [PATCH 04/38] mac: Create dmg in actions --- .github/workflows/macos.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index dfb13b24..811bb8eb 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -48,10 +48,27 @@ jobs: - name: Check generated files run: git diff --exit-code + - name: Create dmg + run: | + brew install create-dmg + mkdir source_folder + cp -R build/macos/Build/Products/Release/"Yubico Authenticator.app" source_folder + create-dmg \ + --volname "Yubico Authenticator" \ + --background "resources/icons/yubico-msi-background.png" \ + --window-pos 200 120 \ + --window-size 800 400 \ + --icon-size 100 \ + --icon "Yubico Authenticator.app" 200 190 \ + --app-drop-link 600 185 \ + "yubioath-desktop.dmg" \ + "source_folder/" + - name: Rename and archive app bundle run: | export REF=$(echo ${GITHUB_REF} | cut -d '/' -f 3) mkdir deploy + mv yubioath-desktop.dmg deploy tar -czf deploy/yubioath-desktop-${REF}.app.tar.gz -C build/macos/Build/Products/Release "Yubico Authenticator.app" - name: Upload artifact From de4cdc1eacc68263bddefbffebd94e41243f0d0a Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Tue, 28 Jun 2022 15:47:19 +0200 Subject: [PATCH 05/38] Move creation of dmg to script --- .github/workflows/macos.yml | 11 +---------- create-dmg.sh | 10 ++++++++++ 2 files changed, 11 insertions(+), 10 deletions(-) create mode 100644 create-dmg.sh diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 811bb8eb..2b555a68 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -53,16 +53,7 @@ jobs: brew install create-dmg mkdir source_folder cp -R build/macos/Build/Products/Release/"Yubico Authenticator.app" source_folder - create-dmg \ - --volname "Yubico Authenticator" \ - --background "resources/icons/yubico-msi-background.png" \ - --window-pos 200 120 \ - --window-size 800 400 \ - --icon-size 100 \ - --icon "Yubico Authenticator.app" 200 190 \ - --app-drop-link 600 185 \ - "yubioath-desktop.dmg" \ - "source_folder/" + sh create-dmg.sh - name: Rename and archive app bundle run: | diff --git a/create-dmg.sh b/create-dmg.sh new file mode 100644 index 00000000..d2de36f7 --- /dev/null +++ b/create-dmg.sh @@ -0,0 +1,10 @@ +create-dmg \ +--volname "Yubico Authenticator" \ +--background "resources/icons/yubico-msi-background.png" \ +--window-pos 200 120 \ +--window-size 800 400 \ +--icon-size 100 \ +--icon "Yubico Authenticator.app" 200 190 \ +--app-drop-link 600 185 \ +"yubioath-desktop.dmg" \ +"source_folder/" \ No newline at end of file From 431f7944af53d57f7a50a01fb1390d074a7c7ca3 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 28 Jun 2022 19:24:18 +0200 Subject: [PATCH 06/38] Update helper to latest ykman changes. --- helper/helper/device.py | 4 +- helper/helper/management.py | 12 ++--- helper/poetry.lock | 102 ++++++++++++++++++------------------ 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/helper/helper/device.py b/helper/helper/device.py index 12d76f31..d3be50f0 100644 --- a/helper/helper/device.py +++ b/helper/helper/device.py @@ -327,7 +327,9 @@ class ReaderDeviceNode(AbstractDeviceNode): with self._device.open_connection(SmartCardConnection) as conn: return dict(self._read_data(conn), present=True) except NoCardException: - return dict(present=False) + return dict(present=False, status="no-card") + except ValueError: + return dict(present=False, status="unknown-device") @child def ccid(self): diff --git a/helper/helper/management.py b/helper/helper/management.py index f7708768..7686427b 100644 --- a/helper/helper/management.py +++ b/helper/helper/management.py @@ -32,7 +32,7 @@ from yubikit.core.smartcard import SmartCardConnection from yubikit.core.otp import OtpConnection from yubikit.core.fido import FidoConnection from yubikit.management import ManagementSession, DeviceConfig, Mode -from ykman.device import connect_to_device +from ykman.device import list_all_devices from dataclasses import asdict from time import sleep import logging @@ -78,12 +78,10 @@ class ManagementNode(RpcNode): logger.debug("Waiting for device to re-appear...") for _ in range(10): sleep(0.2) # Always sleep initially - try: - conn = connect_to_device(serial, connection_types)[0] - conn.close() - break - except ValueError: - logger.debug("Not found, sleep...") + for dev, info in list_all_devices(connection_types): + if info.serial == serial: + return + logger.debug("Not found, sleep...") else: logger.warning("Timed out waiting for device") diff --git a/helper/poetry.lock b/helper/poetry.lock index fa41dfb2..f3d28ffb 100755 --- a/helper/poetry.lock +++ b/helper/poetry.lock @@ -60,7 +60,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "cryptography" -version = "37.0.2" +version = "37.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -101,7 +101,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "importlib-metadata" -version = "4.11.4" +version = "4.12.0" description = "Read metadata from Python packages" category = "main" optional = false @@ -113,7 +113,7 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -174,7 +174,7 @@ python-versions = ">=3.5" [[package]] name = "numpy" -version = "1.22.4" +version = "1.23.0" description = "NumPy is the fundamental package for array computing with Python." category = "main" optional = false @@ -370,7 +370,7 @@ pywin32 = {version = ">=223", markers = "sys_platform == \"win32\""} type = "git" url = "https://github.com/Yubico/yubikey-manager.git" reference = "next" -resolved_reference = "7e0c9e586e507f0dd021a9b7beacc621bfee7baa" +resolved_reference = "ef70133ebbc97809a7abc89b593078268c47189a" [[package]] name = "zipp" @@ -474,28 +474,28 @@ colorama = [ {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] cryptography = [ - {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"}, - {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"}, - {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"}, - {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"}, - {file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"}, - {file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"}, - {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"}, - {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"}, - {file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"}, + {file = "cryptography-37.0.3-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:d10413d493e98075060d3e62e5826de372912ea653ccc948f3c41b21ddca087f"}, + {file = "cryptography-37.0.3-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:cd64147ff16506632893ceb2569624b48c84daa3ba4d89695f7c7bc24188eee9"}, + {file = "cryptography-37.0.3-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:17c74f7d9e9e9bb7e84521243695c1b4bdc3a0e44ca764e6bcf8f05f3de3d0df"}, + {file = "cryptography-37.0.3-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:0713bee6c8077786c56bdec9c5d3f099d40d2c862ff3200416f6862e9dd63156"}, + {file = "cryptography-37.0.3-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9c2008417741cdfbe945ef2d16b7b7ba0790886a0b49e1de533acf93eb66ed6"}, + {file = "cryptography-37.0.3-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646905ff7a712e415bf0d0f214e0eb669dd2257c4d7a27db1e8baec5d2a1d55f"}, + {file = "cryptography-37.0.3-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:dcafadb5a06cb7a6bb49fb4c1de7414ee2f8c8e12b047606d97c3175d690f582"}, + {file = "cryptography-37.0.3-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0b4bfc5ccfe4e5c7de535670680398fed4a0bbc5dfd52b3a295baad42230abdf"}, + {file = "cryptography-37.0.3-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a03dbc0d8ce8c1146c177cd0e3a66ea106f36733fb1b997ea4d051f8a68539ff"}, + {file = "cryptography-37.0.3-cp36-abi3-win32.whl", hash = "sha256:190a24c14e91c1fa3101069aac7e77d11c5a73911c3904128367f52946bbb6fd"}, + {file = "cryptography-37.0.3-cp36-abi3-win_amd64.whl", hash = "sha256:b05c5478524deb7a019e240f2a970040c4b0f01f58f0425e6262c96b126c6a3e"}, + {file = "cryptography-37.0.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891ed8312840fd43e0696468a6520a582a033c0109f7b14b96067bfe1123226b"}, + {file = "cryptography-37.0.3-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:30d6aabf623a01affc7c0824936c3dde6590076b61f5dd299df3cc2c75fc5915"}, + {file = "cryptography-37.0.3-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:31a7c1f1c2551f013d4294d06e22848e2ccd77825f0987cba3239df6ebf7b020"}, + {file = "cryptography-37.0.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a94fd1ff80001cb97add71d07f596d8b865b716f25ef501183e0e199390e50d3"}, + {file = "cryptography-37.0.3-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:8a85dbcc770256918b40c2f40bd3ffd3b2ae45b0cf19068b561db8f8d61bf492"}, + {file = "cryptography-37.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:773d5b5f2e2bd2c7cbb1bd24902ad41283c88b9dd463a0f82adc9a2870d9d066"}, + {file = "cryptography-37.0.3-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0f9193428a55a4347af2d4fd8141a2002dedbcc26487e67fd2ae19f977ee8afc"}, + {file = "cryptography-37.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bf652c73e8f7c32a3f92f7184bf7f9106dacdf5ef59c3c3683d7dae2c4972fb"}, + {file = "cryptography-37.0.3-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:c3c8b1ad2c266fdf7adc041cc4156d6a3d14db93de2f81b26a5af97ef3f209e5"}, + {file = "cryptography-37.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2383d6c3088e863304c37c65cd2ea404b7fbb4886823eab1d74137cc27f3d2ee"}, + {file = "cryptography-37.0.3.tar.gz", hash = "sha256:ae430d51c67ac638dfbb42edf56c669ca9c74744f4d225ad11c6f3d355858187"}, ] fido2 = [ {file = "fido2-1.0.0-py3-none-any.whl", hash = "sha256:dce13d739b8e0df30505b33f5fd2868fad20f3b309acacce72e5f2d1b0c58761"}, @@ -505,8 +505,8 @@ future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, - {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, + {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, + {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -529,28 +529,28 @@ mss = [ {file = "mss-6.1.0.tar.gz", hash = "sha256:aebd069f3e05667fe9c7b9fa4b1771fe42a4710ce1058ce0236936ce06fa5394"}, ] numpy = [ - {file = "numpy-1.22.4-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:ba9ead61dfb5d971d77b6c131a9dbee62294a932bf6a356e48c75ae684e635b3"}, - {file = "numpy-1.22.4-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:1ce7ab2053e36c0a71e7a13a7475bd3b1f54750b4b433adc96313e127b870887"}, - {file = "numpy-1.22.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7228ad13744f63575b3a972d7ee4fd61815b2879998e70930d4ccf9ec721dce0"}, - {file = "numpy-1.22.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43a8ca7391b626b4c4fe20aefe79fec683279e31e7c79716863b4b25021e0e74"}, - {file = "numpy-1.22.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a911e317e8c826ea632205e63ed8507e0dc877dcdc49744584dfc363df9ca08c"}, - {file = "numpy-1.22.4-cp310-cp310-win32.whl", hash = "sha256:9ce7df0abeabe7fbd8ccbf343dc0db72f68549856b863ae3dd580255d009648e"}, - {file = "numpy-1.22.4-cp310-cp310-win_amd64.whl", hash = "sha256:3e1ffa4748168e1cc8d3cde93f006fe92b5421396221a02f2274aab6ac83b077"}, - {file = "numpy-1.22.4-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:59d55e634968b8f77d3fd674a3cf0b96e85147cd6556ec64ade018f27e9479e1"}, - {file = "numpy-1.22.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c1d937820db6e43bec43e8d016b9b3165dcb42892ea9f106c70fb13d430ffe72"}, - {file = "numpy-1.22.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4c5d5eb2ec8da0b4f50c9a843393971f31f1d60be87e0fb0917a49133d257d6"}, - {file = "numpy-1.22.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64f56fc53a2d18b1924abd15745e30d82a5782b2cab3429aceecc6875bd5add0"}, - {file = "numpy-1.22.4-cp38-cp38-win32.whl", hash = "sha256:fb7a980c81dd932381f8228a426df8aeb70d59bbcda2af075b627bbc50207cba"}, - {file = "numpy-1.22.4-cp38-cp38-win_amd64.whl", hash = "sha256:e96d7f3096a36c8754207ab89d4b3282ba7b49ea140e4973591852c77d09eb76"}, - {file = "numpy-1.22.4-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:4c6036521f11a731ce0648f10c18ae66d7143865f19f7299943c985cdc95afb5"}, - {file = "numpy-1.22.4-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b89bf9b94b3d624e7bb480344e91f68c1c6c75f026ed6755955117de00917a7c"}, - {file = "numpy-1.22.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2d487e06ecbf1dc2f18e7efce82ded4f705f4bd0cd02677ffccfb39e5c284c7e"}, - {file = "numpy-1.22.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eb268dbd5cfaffd9448113539e44e2dd1c5ca9ce25576f7c04a5453edc26fa"}, - {file = "numpy-1.22.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37431a77ceb9307c28382c9773da9f306435135fae6b80b62a11c53cfedd8802"}, - {file = "numpy-1.22.4-cp39-cp39-win32.whl", hash = "sha256:cc7f00008eb7d3f2489fca6f334ec19ca63e31371be28fd5dad955b16ec285bd"}, - {file = "numpy-1.22.4-cp39-cp39-win_amd64.whl", hash = "sha256:f0725df166cf4785c0bc4cbfb320203182b1ecd30fee6e541c8752a92df6aa32"}, - {file = "numpy-1.22.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0791fbd1e43bf74b3502133207e378901272f3c156c4df4954cad833b1380207"}, - {file = "numpy-1.22.4.zip", hash = "sha256:425b390e4619f58d8526b3dcf656dde069133ae5c240229821f01b5f44ea07af"}, + {file = "numpy-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58bfd40eb478f54ff7a5710dd61c8097e169bc36cc68333d00a9bcd8def53b38"}, + {file = "numpy-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:196cd074c3f97c4121601790955f915187736f9cf458d3ee1f1b46aff2b1ade0"}, + {file = "numpy-1.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1d88ef79e0a7fa631bb2c3dda1ea46b32b1fe614e10fedd611d3d5398447f2f"}, + {file = "numpy-1.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d54b3b828d618a19779a84c3ad952e96e2c2311b16384e973e671aa5be1f6187"}, + {file = "numpy-1.23.0-cp310-cp310-win32.whl", hash = "sha256:2b2da66582f3a69c8ce25ed7921dcd8010d05e59ac8d89d126a299be60421171"}, + {file = "numpy-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:97a76604d9b0e79f59baeca16593c711fddb44936e40310f78bfef79ee9a835f"}, + {file = "numpy-1.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8cc87bed09de55477dba9da370c1679bd534df9baa171dd01accbb09687dac3"}, + {file = "numpy-1.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f0f18804df7370571fb65db9b98bf1378172bd4e962482b857e612d1fec0f53e"}, + {file = "numpy-1.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac86f407873b952679f5f9e6c0612687e51547af0e14ddea1eedfcb22466babd"}, + {file = "numpy-1.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae8adff4172692ce56233db04b7ce5792186f179c415c37d539c25de7298d25d"}, + {file = "numpy-1.23.0-cp38-cp38-win32.whl", hash = "sha256:fe8b9683eb26d2c4d5db32cd29b38fdcf8381324ab48313b5b69088e0e355379"}, + {file = "numpy-1.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:5043bcd71fcc458dfb8a0fc5509bbc979da0131b9d08e3d5f50fb0bbb36f169a"}, + {file = "numpy-1.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1c29b44905af288b3919803aceb6ec7fec77406d8b08aaa2e8b9e63d0fe2f160"}, + {file = "numpy-1.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98e8e0d8d69ff4d3fa63e6c61e8cfe2d03c29b16b58dbef1f9baa175bbed7860"}, + {file = "numpy-1.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a506cacf2be3a74ead5467aee97b81fca00c9c4c8b3ba16dbab488cd99ba10"}, + {file = "numpy-1.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:092f5e6025813e64ad6d1b52b519165d08c730d099c114a9247c9bb635a2a450"}, + {file = "numpy-1.23.0-cp39-cp39-win32.whl", hash = "sha256:d6ca8dabe696c2785d0c8c9b0d8a9b6e5fdbe4f922bde70d57fa1a2848134f95"}, + {file = "numpy-1.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc431493df245f3c627c0c05c2bd134535e7929dbe2e602b80e42bf52ff760bc"}, + {file = "numpy-1.23.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f9c3fc2adf67762c9fe1849c859942d23f8d3e0bee7b5ed3d4a9c3eeb50a2f07"}, + {file = "numpy-1.23.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0d2094e8f4d760500394d77b383a1b06d3663e8892cdf5df3c592f55f3bff66"}, + {file = "numpy-1.23.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:94b170b4fa0168cd6be4becf37cb5b127bd12a795123984385b8cd4aca9857e5"}, + {file = "numpy-1.23.0.tar.gz", hash = "sha256:bd3fa4fe2e38533d5336e1272fc4e765cabbbde144309ccee8675509d5cd7b05"}, ] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, From 7f093d97a2644c884b8f045cd3f81ecc21d03c97 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 28 Jun 2022 19:51:58 +0200 Subject: [PATCH 07/38] Handle Non-YubiKey NFC devices- This changes the currentDeviceDataNotifier to use an AsyncValue instead of a nullable YubiKeyData to allow for error states, and handles such a state for NFC readers when a non-YubiKey is present. --- integration_test/app_test.dart | 4 +- lib/android/command_providers.dart | 22 ++-- lib/android/state.dart | 11 +- lib/app/state.dart | 6 +- lib/app/views/device_button.dart | 26 ++-- ...e_screen.dart => device_error_screen.dart} | 28 +++-- lib/app/views/device_picker_dialog.dart | 90 +++++++------- lib/app/views/main_drawer.dart | 3 +- lib/app/views/main_page.dart | 58 +++++---- lib/desktop/devices.dart | 20 +-- lib/oath/views/account_dialog.dart | 2 +- lib/oath/views/account_mixin.dart | 116 +++++++++--------- 12 files changed, 200 insertions(+), 186 deletions(-) rename lib/app/views/{no_device_screen.dart => device_error_screen.dart} (75%) diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index 18bbf38f..2b18ce49 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:yubico_authenticator/android/init.dart' as android; -import 'package:yubico_authenticator/app/views/no_device_screen.dart'; +import 'package:yubico_authenticator/app/views/device_error_screen.dart'; import 'package:yubico_authenticator/core/state.dart'; import 'package:yubico_authenticator/desktop/init.dart' as desktop; import 'package:yubico_authenticator/oath/views/account_list.dart'; @@ -55,7 +55,7 @@ void main() { await tester.pumpWidget(initializedApp); await tester.pump(const Duration(milliseconds: 500)); - expect(find.byType(NoDeviceScreen), findsNothing, + expect(find.byType(DeviceErrorScreen), findsNothing, reason: 'No YubiKey connected'); expect(find.byType(OathScreen), findsOneWidget); diff --git a/lib/android/command_providers.dart b/lib/android/command_providers.dart index 566d3b52..5dc46168 100644 --- a/lib/android/command_providers.dart +++ b/lib/android/command_providers.dart @@ -12,11 +12,11 @@ import '../management/models.dart'; final _log = Logger('yubikeyDataCommandProvider'); final androidYubikeyProvider = - StateNotifierProvider<_YubikeyProvider, YubiKeyData?>((ref) { - return _YubikeyProvider(null, ref); + StateNotifierProvider<_YubikeyProvider, AsyncValue>((ref) { + return _YubikeyProvider(const AsyncValue.loading(), ref); }); -class _YubikeyProvider extends StateNotifier { +class _YubikeyProvider extends StateNotifier> { final Ref _ref; _YubikeyProvider(super.yubiKeyData, this._ref); @@ -24,7 +24,7 @@ class _YubikeyProvider extends StateNotifier { try { if (input.isEmpty) { _log.debug('Yubikey was detached.'); - state = null; + state = const AsyncValue.loading(); // reset other providers when YubiKey is removed _ref.refresh(androidStateProvider); @@ -49,15 +49,17 @@ class _YubikeyProvider extends StateNotifier { // reset oath providers on key change var yubiKeyData = YubiKeyData(deviceNode, name, deviceInfo); - if (state != yubiKeyData && state != null) { - _ref.refresh(androidStateProvider); - _ref.refresh(androidCredentialsProvider); - } + state.whenData((data) { + if (data != yubiKeyData) { + _ref.refresh(androidStateProvider); + _ref.refresh(androidCredentialsProvider); + } + }); - state = yubiKeyData; + state = AsyncValue.data(yubiKeyData); } on Exception catch (e) { _log.debug('Invalid data for yubikey: $input. $e'); - state = null; + state = AsyncValue.error(e); } } } diff --git a/lib/android/state.dart b/lib/android/state.dart index 121083a2..90bb7a8a 100644 --- a/lib/android/state.dart +++ b/lib/android/state.dart @@ -35,18 +35,17 @@ class _AndroidSubPageNotifier extends CurrentAppNotifier { final androidAttachedDevicesProvider = StateNotifierProvider>((ref) { var currentDeviceData = ref.watch(androidDeviceDataProvider); - if (currentDeviceData != null) { - return _AndroidAttachedDevicesNotifier([currentDeviceData.node]); - } - return _AndroidAttachedDevicesNotifier([]); + List devs = currentDeviceData.maybeWhen( + data: (data) => [data.node], orElse: () => []); + return _AndroidAttachedDevicesNotifier(devs); }); class _AndroidAttachedDevicesNotifier extends AttachedDevicesNotifier { _AndroidAttachedDevicesNotifier(super.state); } -final androidDeviceDataProvider = - Provider((ref) => ref.watch(androidYubikeyProvider)); +final androidDeviceDataProvider = Provider>( + (ref) => ref.watch(androidYubikeyProvider)); final androidCurrentDeviceProvider = StateNotifierProvider((ref) { diff --git a/lib/app/state.dart b/lib/app/state.dart index c772f7a5..e80e7a28 100755 --- a/lib/app/state.dart +++ b/lib/app/state.dart @@ -60,7 +60,7 @@ class AttachedDevicesNotifier extends StateNotifier> { } // Override with platform implementation -final currentDeviceDataProvider = Provider( +final currentDeviceDataProvider = Provider>( (ref) => throw UnimplementedError(), ); @@ -77,8 +77,8 @@ abstract class CurrentDeviceNotifier extends StateNotifier { final currentAppProvider = StateNotifierProvider((ref) { final notifier = CurrentAppNotifier(ref.watch(supportedAppsProvider)); - ref.listen(currentDeviceDataProvider, (_, data) { - notifier._notifyDeviceChanged(data); + ref.listen>(currentDeviceDataProvider, (_, data) { + notifier._notifyDeviceChanged(data.whenOrNull(data: ((data) => data))); }, fireImmediately: true); return notifier; }); diff --git a/lib/app/views/device_button.dart b/lib/app/views/device_button.dart index 50dbbb40..6d7b6378 100755 --- a/lib/app/views/device_button.dart +++ b/lib/app/views/device_button.dart @@ -12,22 +12,20 @@ class DeviceButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final deviceNode = ref.watch(currentDeviceProvider); - final deviceData = ref.watch(currentDeviceDataProvider); Widget deviceWidget; if (deviceNode != null) { - if (deviceData != null) { - deviceWidget = DeviceAvatar.yubiKeyData( - deviceData, - selected: true, - radius: radius, - ); - } else { - deviceWidget = DeviceAvatar.deviceNode( - deviceNode, - selected: true, - radius: radius, - ); - } + deviceWidget = ref.watch(currentDeviceDataProvider).maybeWhen( + data: (data) => DeviceAvatar.yubiKeyData( + data, + selected: true, + radius: radius, + ), + orElse: () => DeviceAvatar.deviceNode( + deviceNode, + selected: true, + radius: radius, + ), + ); } else { deviceWidget = DeviceAvatar( radius: radius, diff --git a/lib/app/views/no_device_screen.dart b/lib/app/views/device_error_screen.dart similarity index 75% rename from lib/app/views/no_device_screen.dart rename to lib/app/views/device_error_screen.dart index a6560e94..06cc1af1 100755 --- a/lib/app/views/no_device_screen.dart +++ b/lib/app/views/device_error_screen.dart @@ -12,9 +12,10 @@ import 'device_avatar.dart'; import 'graphics.dart'; import 'message_page.dart'; -class NoDeviceScreen extends ConsumerWidget { - final DeviceNode? node; - const NoDeviceScreen(this.node, {super.key}); +class DeviceErrorScreen extends ConsumerWidget { + final DeviceNode node; + final Object? error; + const DeviceErrorScreen(this.node, {this.error, super.key}); Widget _buildUsbPid(BuildContext context, WidgetRef ref, UsbPid pid) { if (pid.usbInterfaces == UsbInterface.fido.value) { @@ -55,12 +56,19 @@ class NoDeviceScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return node?.map( - usbYubiKey: (node) => _buildUsbPid(context, ref, node.pid), - nfcReader: (_) => const MessagePage( - message: 'Place your YubiKey on the NFC reader', - ), - ) ?? - const MessagePage(message: 'Insert your YubiKey'); + return node.map( + usbYubiKey: (node) => _buildUsbPid(context, ref, node.pid), + nfcReader: (node) { + final String message; + switch (error) { + case 'unknown-device': + message = 'Unrecognized device'; + break; + default: + message = 'Place your YubiKey on the NFC reader'; + } + return MessagePage(message: message); + }, + ); } } diff --git a/lib/app/views/device_picker_dialog.dart b/lib/app/views/device_picker_dialog.dart index 1df891a7..2a66105d 100755 --- a/lib/app/views/device_picker_dialog.dart +++ b/lib/app/views/device_picker_dialog.dart @@ -33,7 +33,7 @@ class DevicePickerDialog extends ConsumerWidget { devices.removeWhere((e) => e.path == currentNode.path); hero = _CurrentDeviceRow( currentNode, - data: ref.watch(currentDeviceDataProvider), + ref.watch(currentDeviceDataProvider), onTap: () { Navigator.of(context).pop(); }, @@ -99,65 +99,61 @@ class DevicePickerDialog extends ConsumerWidget { class _CurrentDeviceRow extends StatelessWidget { final DeviceNode node; - final YubiKeyData? data; + final AsyncValue data; final Function() onTap; const _CurrentDeviceRow( - this.node, { - this.data, + this.node, + this.data, { required this.onTap, }); @override - Widget build(BuildContext context) { - return node.when(usbYubiKey: (path, name, pid, info) { - if (info != null) { - return ListTile( - leading: DeviceAvatar.yubiKeyData( - data!, - selected: true, - ), - title: Text(name), - subtitle: Text(_getSubtitle(info)), - onTap: onTap, - ); - } else { - return ListTile( - leading: DeviceAvatar.deviceNode( - node, - selected: true, - ), - title: Text(name), - subtitle: const Text('Device inaccessible'), - onTap: onTap, - ); - } - }, nfcReader: (path, name) { - final info = data?.info; - if (info != null) { - return ListTile( - leading: DeviceAvatar.yubiKeyData( - data!, - selected: true, - ), - title: Text(data!.name), - isThreeLine: true, - subtitle: Text('$name\n${_getSubtitle(info)}'), - onTap: onTap, - ); - } else { - return ListTile( + Widget build(BuildContext context) => data.when( + data: (data) { + final isNfc = data.node is NfcReaderNode; + return ListTile( + leading: DeviceAvatar.yubiKeyData( + data, + selected: true, + ), + isThreeLine: isNfc, + title: Text(isNfc ? node.name : data.name), + subtitle: Text(isNfc + ? '${data.name}\n${_getSubtitle(data.info)}' + : _getSubtitle(data.info)), + onTap: onTap, + ); + }, + error: (error, _) { + final String message; + switch (error) { + case 'unknown-device': + message = 'Unrecognized device'; + break; + default: + message = 'No YubiKey present'; + } + return ListTile( + leading: DeviceAvatar.deviceNode( + node, + selected: true, + ), + title: Text(message), + subtitle: Text(node.name), + onTap: onTap, + ); + }, + loading: () => ListTile( leading: DeviceAvatar.deviceNode( node, selected: true, ), title: const Text('No YubiKey present'), - subtitle: Text(name), + subtitle: Text(node.name), onTap: onTap, - ); - } - }); - } + ), + ); } class _DeviceRow extends StatelessWidget { diff --git a/lib/app/views/main_drawer.dart b/lib/app/views/main_drawer.dart index d16c12a4..6a980b96 100755 --- a/lib/app/views/main_drawer.dart +++ b/lib/app/views/main_drawer.dart @@ -35,7 +35,8 @@ class MainPageDrawer extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final supportedApps = ref.watch(supportedAppsProvider); - final data = ref.watch(currentDeviceDataProvider); + final data = + ref.watch(currentDeviceDataProvider).whenOrNull(data: (data) => data); final currentApp = ref.watch(currentAppProvider); MediaQuery? mediaQuery = diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index 36f87eea..557cf9af 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -2,12 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'message_page.dart'; -import 'no_device_screen.dart'; +import 'device_error_screen.dart'; import '../models.dart'; import '../state.dart'; import '../../fido/views/fido_screen.dart'; import '../../oath/views/oath_screen.dart'; -import '../../management/views/management_screen.dart'; class MainPage extends ConsumerWidget { const MainPage({super.key}); @@ -21,7 +20,7 @@ class MainPage extends ConsumerWidget { }, ); // If the current device changes, we need to pop any open dialogs. - ref.listen(currentDeviceDataProvider, (_, __) { + ref.listen>(currentDeviceDataProvider, (_, __) { Navigator.of(context).popUntil((route) { return route.isFirst || [ @@ -32,31 +31,36 @@ class MainPage extends ConsumerWidget { ].contains(route.settings.name); }); }); - final deviceData = ref.watch(currentDeviceDataProvider); - if (deviceData == null) { - final node = ref.watch(currentDeviceProvider); - return NoDeviceScreen(node); - } - final app = ref.watch(currentAppProvider); - if (app.getAvailability(deviceData) != Availability.enabled) { - return const MessagePage( - header: 'Application disabled', - message: 'Enable the application on your YubiKey to access', - ); - } - switch (app) { - case Application.oath: - return OathScreen(deviceData.node.path); - case Application.management: - return ManagementScreen(deviceData); - case Application.fido: - return FidoScreen(deviceData); - default: - return const MessagePage( - header: 'Not implemented', - message: 'This section has not yet been implemented', - ); + final deviceNode = ref.watch(currentDeviceProvider); + if (deviceNode == null) { + return const MessagePage(message: 'Insert your YubiKey'); + } else { + return ref.watch(currentDeviceDataProvider).when( + data: (data) { + final app = ref.watch(currentAppProvider); + if (app.getAvailability(data) != Availability.enabled) { + return const MessagePage( + header: 'Application disabled', + message: 'Enable the application on your YubiKey to access', + ); + } + + switch (app) { + case Application.oath: + return OathScreen(data.node.path); + case Application.fido: + return FidoScreen(data); + default: + return const MessagePage( + header: 'Not supported', + message: 'This application is not supported', + ); + } + }, + loading: () => DeviceErrorScreen(deviceNode), + error: (error, _) => DeviceErrorScreen(deviceNode, error: error), + ); } } } diff --git a/lib/desktop/devices.dart b/lib/desktop/devices.dart index e0df68bd..ddec4c24 100755 --- a/lib/desktop/devices.dart +++ b/lib/desktop/devices.dart @@ -193,7 +193,8 @@ class _DesktopDevicesNotifier extends AttachedDevicesNotifier { } final _desktopDeviceDataProvider = - StateNotifierProvider((ref) { + StateNotifierProvider>( + (ref) { final notifier = CurrentDeviceDataNotifier( ref.watch(rpcProvider), ref.watch(currentDeviceProvider), @@ -207,23 +208,24 @@ final _desktopDeviceDataProvider = return notifier; }); -final desktopDeviceDataProvider = Provider( +final desktopDeviceDataProvider = Provider>( (ref) { return ref.watch(_desktopDeviceDataProvider); }, ); -class CurrentDeviceDataNotifier extends StateNotifier { +class CurrentDeviceDataNotifier extends StateNotifier> { final RpcSession _rpc; final DeviceNode? _deviceNode; Timer? _pollTimer; - CurrentDeviceDataNotifier(this._rpc, this._deviceNode) : super(null) { + CurrentDeviceDataNotifier(this._rpc, this._deviceNode) + : super(const AsyncValue.loading()) { final dev = _deviceNode; if (dev is UsbYubiKeyNode) { final info = dev.info; if (info != null) { - state = YubiKeyData(dev, dev.name, info); + state = AsyncValue.data(YubiKeyData(dev, dev.name, info)); } } } @@ -254,10 +256,10 @@ class CurrentDeviceDataNotifier extends StateNotifier { var result = await _rpc.command('get', node.path.segments); if (mounted) { if (result['data']['present']) { - state = YubiKeyData(node, result['data']['name'], - DeviceInfo.fromJson(result['data']['info'])); + state = AsyncValue.data(YubiKeyData(node, result['data']['name'], + DeviceInfo.fromJson(result['data']['info']))); } else { - state = null; + state = AsyncValue.error(result['data']['status']); } } } on RpcError catch (e) { @@ -265,7 +267,7 @@ class CurrentDeviceDataNotifier extends StateNotifier { } if (mounted) { _pollTimer = Timer( - state == null ? _nfcAttachPollDelay : _nfcDetachPollDelay, + state is AsyncData ? _nfcDetachPollDelay : _nfcAttachPollDelay, _pollReader); } } diff --git a/lib/oath/views/account_dialog.dart b/lib/oath/views/account_dialog.dart index ce4974c2..ba3e447f 100755 --- a/lib/oath/views/account_dialog.dart +++ b/lib/oath/views/account_dialog.dart @@ -94,7 +94,7 @@ class AccountDialog extends ConsumerWidget with AccountMixin { @override Widget build(BuildContext context, WidgetRef ref) { // TODO: Solve this in a cleaner way - if (ref.watch(currentDeviceDataProvider) == null) { + if (ref.watch(currentDeviceDataProvider) is! AsyncData) { // The rest of this method assumes there is a device, and will throw an exception if not. // This will never be shown, as the dialog will be immediately closed return const SizedBox(); diff --git a/lib/oath/views/account_mixin.dart b/lib/oath/views/account_mixin.dart index 86b8821d..3efab697 100755 --- a/lib/oath/views/account_mixin.dart +++ b/lib/oath/views/account_mixin.dart @@ -94,63 +94,67 @@ mixin AccountMixin { } @protected - List buildActions(BuildContext context, WidgetRef ref) { - final deviceData = ref.watch(currentDeviceDataProvider); - if (deviceData == null) { - return []; - } - final code = getCode(ref); - final expired = isExpired(code, ref); - final manual = - credential.touchRequired || credential.oathType == OathType.hotp; - final ready = expired || credential.oathType == OathType.hotp; - final pinned = isPinned(ref); + List buildActions(BuildContext context, WidgetRef ref) => + ref.watch(currentDeviceDataProvider).maybeWhen( + data: (data) { + final code = getCode(ref); + final expired = isExpired(code, ref); + final manual = credential.touchRequired || + credential.oathType == OathType.hotp; + final ready = expired || credential.oathType == OathType.hotp; + final pinned = isPinned(ref); - return [ - MenuAction( - text: 'Copy to clipboard', - icon: const Icon(Icons.copy), - action: code == null || expired - ? null - : (context) { - Clipboard.setData(ClipboardData(text: code.value)); - showMessage(context, 'Code copied to clipboard'); - }, - ), - if (manual) - MenuAction( - text: 'Calculate', - icon: const Icon(Icons.refresh), - action: ready - ? (context) { - calculateCode(context, ref); - } - : null, - ), - MenuAction( - text: pinned ? 'Unpin account' : 'Pin account', - icon: pinned ? pushPinStrokeIcon : const Icon(Icons.push_pin_outlined), - action: (context) { - ref.read(favoritesProvider.notifier).toggleFavorite(credential.id); - }, - ), - if (deviceData.info.version.isAtLeast(5, 3)) - MenuAction( - icon: const Icon(Icons.edit_outlined), - text: 'Rename account', - action: (context) async { - await renameCredential(context, ref); - }, - ), - MenuAction( - text: 'Delete account', - icon: const Icon(Icons.delete_outline), - action: (context) async { - await deleteCredential(context, ref); - }, - ), - ]; - } + return [ + MenuAction( + text: 'Copy to clipboard', + icon: const Icon(Icons.copy), + action: code == null || expired + ? null + : (context) { + Clipboard.setData(ClipboardData(text: code.value)); + showMessage(context, 'Code copied to clipboard'); + }, + ), + if (manual) + MenuAction( + text: 'Calculate', + icon: const Icon(Icons.refresh), + action: ready + ? (context) { + calculateCode(context, ref); + } + : null, + ), + MenuAction( + text: pinned ? 'Unpin account' : 'Pin account', + icon: pinned + ? pushPinStrokeIcon + : const Icon(Icons.push_pin_outlined), + action: (context) { + ref + .read(favoritesProvider.notifier) + .toggleFavorite(credential.id); + }, + ), + if (data.info.version.isAtLeast(5, 3)) + MenuAction( + icon: const Icon(Icons.edit_outlined), + text: 'Rename account', + action: (context) async { + await renameCredential(context, ref); + }, + ), + MenuAction( + text: 'Delete account', + icon: const Icon(Icons.delete_outline), + action: (context) async { + await deleteCredential(context, ref); + }, + ), + ]; + }, + orElse: () => [], + ); @protected Widget buildCodeView(WidgetRef ref) { From de2fcba82687765764c88476fdad900a9f3564e5 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 28 Jun 2022 20:03:00 +0200 Subject: [PATCH 08/38] Fix FIDO commands for python-fido2 1.0.0. --- helper/helper/fido.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helper/helper/fido.py b/helper/helper/fido.py index 201bfc5d..3a8146df 100644 --- a/helper/helper/fido.py +++ b/helper/helper/fido.py @@ -171,7 +171,7 @@ class Ctap2Node(RpcNode): self.ctap = Ctap2(connection) if target != _ctap_id(self.ctap): raise ValueError("Re-inserted YubiKey does not match initial device") - self.ctap.reset(event) + self.ctap.reset(event=event) self._info = self.ctap.get_info() self._auth_blocked = False self._token = None @@ -343,7 +343,7 @@ class FingerprintsNode(RpcNode): template_id = None while template_id is None: try: - template_id = enroller.capture(event) + template_id = enroller.capture(event=event) signal("capture", dict(remaining=enroller.remaining)) except CaptureError as e: signal("capture-error", dict(code=e.code)) From 588fe2e6475dc019155754737f3f2f8da523ac77 Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Wed, 29 Jun 2022 15:28:04 +0200 Subject: [PATCH 09/38] Win: release script --- .github/workflows/windows.yml | 1 + resources/win/release-win.ps1 | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 resources/win/release-win.ps1 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index e95b10c1..bb318f2b 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -70,6 +70,7 @@ jobs: mkdir $dest mv build\windows\runner\Release\* $dest\ mv yubioath-desktop.msi deploy + mv resources\win\release-win.ps1 deploy - name: Upload artifact uses: actions/upload-artifact@v1 diff --git a/resources/win/release-win.ps1 b/resources/win/release-win.ps1 new file mode 100644 index 00000000..97b15fbb --- /dev/null +++ b/resources/win/release-win.ps1 @@ -0,0 +1,24 @@ +echo "Cloning the Git repo" +git clone git@github.com:Yubico/yubioath-desktop-private.git + +echo "Renaming the Actions folder and moving it" +mv yubioath-desktop-main-windows release +mv release yubioath-desktop-private + +echo "Signing the executables" +cd yubioath-desktop-private +echo "Sleeping 5s: Change to signing key" +Start-Sleep -s 5 +signtool.exe sign /fd SHA256 /t http://timestamp.digicert.com/scripts/timstamp.dll release/authenticator.exe +signtool.exe sign /fd SHA256 /t http://timestamp.digicert.com/scripts/timstamp.dll release/helper/authenticator-helper.exe + +echo "Setting env var and building installer" +$env:SRCDIR = ".\release\" +heat dir .\release -out fragment.wxs -gg -scom -srd -sfrag -dr INSTALLDIR -cg ApplicationFiles -var env.SRCDIR +candle .\fragment.wxs .\resources\win\yubioath-desktop.wxs -ext WixUtilExtension -arch x64 +light fragment.wixobj yubioath-desktop.wixobj -ext WixUIExtension -ext WixUtilExtension -o yubioath-desktop-VERSION-win64.msi + +echo "Signing the installer" +signtool.exe sign /d "Yubico Authenticator" /fd SHA256 /t http://timestamp.digicert.com/scripts/timstamp.dll yubioath-desktop-VERSION-win64.msi + +echo "All done" From df03354554a99be34e0a3e060c73f4cee3e3d6dc Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Thu, 30 Jun 2022 11:20:57 +0200 Subject: [PATCH 10/38] Add version as a parameter to the script --- resources/win/release-win.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/win/release-win.ps1 b/resources/win/release-win.ps1 index 97b15fbb..a064da40 100644 --- a/resources/win/release-win.ps1 +++ b/resources/win/release-win.ps1 @@ -1,3 +1,5 @@ +$version = $args[0] + echo "Cloning the Git repo" git clone git@github.com:Yubico/yubioath-desktop-private.git @@ -16,9 +18,9 @@ echo "Setting env var and building installer" $env:SRCDIR = ".\release\" heat dir .\release -out fragment.wxs -gg -scom -srd -sfrag -dr INSTALLDIR -cg ApplicationFiles -var env.SRCDIR candle .\fragment.wxs .\resources\win\yubioath-desktop.wxs -ext WixUtilExtension -arch x64 -light fragment.wixobj yubioath-desktop.wixobj -ext WixUIExtension -ext WixUtilExtension -o yubioath-desktop-VERSION-win64.msi +light fragment.wixobj yubioath-desktop.wixobj -ext WixUIExtension -ext WixUtilExtension -o yubioath-desktop-$version-win64.msi echo "Signing the installer" -signtool.exe sign /d "Yubico Authenticator" /fd SHA256 /t http://timestamp.digicert.com/scripts/timstamp.dll yubioath-desktop-VERSION-win64.msi +signtool.exe sign /d "Yubico Authenticator" /fd SHA256 /t http://timestamp.digicert.com/scripts/timstamp.dll yubioath-desktop-$version-win64.msi echo "All done" From 63bb6d88ead2be0ea84bf4db0b5262715c2df222 Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Thu, 30 Jun 2022 12:04:24 +0200 Subject: [PATCH 11/38] Win: cleanup the script --- .github/workflows/windows.yml | 2 +- resources/win/release-win.ps1 | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index bb318f2b..2f656c96 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -70,7 +70,7 @@ jobs: mkdir $dest mv build\windows\runner\Release\* $dest\ mv yubioath-desktop.msi deploy - mv resources\win\release-win.ps1 deploy + mv resources deploy - name: Upload artifact uses: actions/upload-artifact@v1 diff --git a/resources/win/release-win.ps1 b/resources/win/release-win.ps1 index a064da40..39d697ab 100644 --- a/resources/win/release-win.ps1 +++ b/resources/win/release-win.ps1 @@ -1,16 +1,9 @@ $version = $args[0] -echo "Cloning the Git repo" -git clone git@github.com:Yubico/yubioath-desktop-private.git - echo "Renaming the Actions folder and moving it" -mv yubioath-desktop-main-windows release -mv release yubioath-desktop-private +mv yubioath-desktop-* release echo "Signing the executables" -cd yubioath-desktop-private -echo "Sleeping 5s: Change to signing key" -Start-Sleep -s 5 signtool.exe sign /fd SHA256 /t http://timestamp.digicert.com/scripts/timstamp.dll release/authenticator.exe signtool.exe sign /fd SHA256 /t http://timestamp.digicert.com/scripts/timstamp.dll release/helper/authenticator-helper.exe From 61804c726eff5aaa4d7c9c1a739dfa53ab10114d Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Fri, 1 Jul 2022 09:12:54 +0200 Subject: [PATCH 12/38] Fix version number through set-version.py --- resources/win/release-win.ps1 | 2 +- set-version.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/resources/win/release-win.ps1 b/resources/win/release-win.ps1 index 39d697ab..a778cc1a 100644 --- a/resources/win/release-win.ps1 +++ b/resources/win/release-win.ps1 @@ -1,4 +1,4 @@ -$version = $args[0] +$version=6.0.0-dev.0 echo "Renaming the Actions folder and moving it" mv yubioath-desktop-* release diff --git a/set-version.py b/set-version.py index a8dc4790..9329c08a 100755 --- a/set-version.py +++ b/set-version.py @@ -131,7 +131,15 @@ def update_helper_version(buf): ) return buf +# release-win.ps1 +def update_release_win(buf): + return sub1( + rf'\$version={version_pattern}', + f'$version={version}', + buf, + ) update_file("pubspec.yaml", update_pubspec) update_file("windows/runner/Runner.rc", update_runner_rc) -update_file("helper/version_info.txt", update_helper_version) \ No newline at end of file +update_file("helper/version_info.txt", update_helper_version) +update_file("resources/win/release-win.ps1", update_release_win) \ No newline at end of file From 5742763e1f9f808e7236c6917fd16e410cb2bfed Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 4 Jul 2022 12:56:31 +0200 Subject: [PATCH 13/38] Add blur effect. --- lib/app/message.dart | 25 +++++++++++++++++++++++++ lib/app/views/device_button.dart | 3 ++- lib/app/views/main_drawer.dart | 7 ++++--- lib/fido/views/locked_page.dart | 6 +++--- lib/fido/views/unlocked_page.dart | 12 ++++++------ lib/oath/views/account_dialog.dart | 3 ++- lib/oath/views/account_mixin.dart | 4 ++-- lib/oath/views/account_view.dart | 3 ++- lib/oath/views/oath_screen.dart | 10 +++++----- 9 files changed, 51 insertions(+), 22 deletions(-) diff --git a/lib/app/message.dart b/lib/app/message.dart index cd6c3700..61439644 100755 --- a/lib/app/message.dart +++ b/lib/app/message.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -58,3 +60,26 @@ class _BottomMenu extends ConsumerWidget { ); } } + +Future showBlurDialog({ + required BuildContext context, + required Widget Function(BuildContext) builder, + RouteSettings? routeSettings, +}) => + showGeneralDialog( + context: context, + barrierDismissible: true, + barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, + barrierColor: Colors.black12, + pageBuilder: (ctx, anim1, anim2) => builder(ctx), + transitionDuration: const Duration(milliseconds: 150), + transitionBuilder: (ctx, anim1, anim2, child) => BackdropFilter( + filter: + ImageFilter.blur(sigmaX: 4 * anim1.value, sigmaY: 4 * anim1.value), + child: FadeTransition( + opacity: anim1, + child: child, + ), + ), + routeSettings: routeSettings, + ); diff --git a/lib/app/views/device_button.dart b/lib/app/views/device_button.dart index 6d7b6378..aaf4efc2 100755 --- a/lib/app/views/device_button.dart +++ b/lib/app/views/device_button.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../message.dart'; import '../state.dart'; import 'device_avatar.dart'; import 'device_picker_dialog.dart'; @@ -43,7 +44,7 @@ class DeviceButton extends ConsumerWidget { child: deviceWidget, ), onPressed: () { - showDialog( + showBlurDialog( context: context, builder: (context) => const DevicePickerDialog(), routeSettings: const RouteSettings(name: 'device_picker'), diff --git a/lib/app/views/main_drawer.dart b/lib/app/views/main_drawer.dart index 6a980b96..af3f3d1c 100755 --- a/lib/app/views/main_drawer.dart +++ b/lib/app/views/main_drawer.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../management/views/management_screen.dart'; import '../../about_page.dart'; import '../../settings_page.dart'; +import '../message.dart'; import '../models.dart'; import '../state.dart'; @@ -77,7 +78,7 @@ class MainPageDrawer extends ConsumerWidget { icon: Icon(Application.management._icon), onTap: () { if (shouldPop) Navigator.of(context).pop(); - showDialog( + showBlurDialog( context: context, builder: (context) => ManagementScreen(data), ); @@ -93,7 +94,7 @@ class MainPageDrawer extends ConsumerWidget { onTap: () { final nav = Navigator.of(context); if (shouldPop) nav.pop(); - showDialog( + showBlurDialog( context: context, builder: (context) => const SettingsPage(), routeSettings: const RouteSettings(name: 'settings'), @@ -106,7 +107,7 @@ class MainPageDrawer extends ConsumerWidget { onTap: () { final nav = Navigator.of(context); if (shouldPop) nav.pop(); - showDialog( + showBlurDialog( context: context, builder: (context) => const AboutPage(), routeSettings: const RouteSettings(name: 'about'), diff --git a/lib/fido/views/locked_page.dart b/lib/fido/views/locked_page.dart index b446805b..44ae723e 100755 --- a/lib/fido/views/locked_page.dart +++ b/lib/fido/views/locked_page.dart @@ -71,7 +71,7 @@ class FidoLockedPage extends ConsumerWidget { label: const Text('Set PIN'), icon: const Icon(Icons.pin), onPressed: () { - showDialog( + showBlurDialog( context: context, builder: (context) => FidoPinDialog(node.path, state), ); @@ -87,7 +87,7 @@ class FidoLockedPage extends ConsumerWidget { text: 'Change PIN', icon: const Icon(Icons.pin), action: (context) { - showDialog( + showBlurDialog( context: context, builder: (context) => FidoPinDialog(node.path, state), ); @@ -97,7 +97,7 @@ class FidoLockedPage extends ConsumerWidget { text: 'Reset FIDO', icon: const Icon(Icons.delete), action: (context) { - showDialog( + showBlurDialog( context: context, builder: (context) => ResetDialog(node), ); diff --git a/lib/fido/views/unlocked_page.dart b/lib/fido/views/unlocked_page.dart index d89bce71..908aa84e 100755 --- a/lib/fido/views/unlocked_page.dart +++ b/lib/fido/views/unlocked_page.dart @@ -57,7 +57,7 @@ class FidoUnlockedPage extends ConsumerWidget { children: [ IconButton( onPressed: () { - showDialog( + showBlurDialog( context: context, builder: (context) => DeleteCredentialDialog(node.path, cred), @@ -96,7 +96,7 @@ class FidoUnlockedPage extends ConsumerWidget { children: [ IconButton( onPressed: () { - showDialog( + showBlurDialog( context: context, builder: (context) => RenameFingerprintDialog(node.path, fp), @@ -105,7 +105,7 @@ class FidoUnlockedPage extends ConsumerWidget { icon: const Icon(Icons.edit_outlined)), IconButton( onPressed: () { - showDialog( + showBlurDialog( context: context, builder: (context) => DeleteFingerprintDialog(node.path, fp), @@ -163,7 +163,7 @@ class FidoUnlockedPage extends ConsumerWidget { label: const Text('Add fingerprint'), icon: const Icon(Icons.fingerprint), onPressed: () { - showDialog( + showBlurDialog( context: context, builder: (context) => AddFingerprintDialog(node.path), ); @@ -178,7 +178,7 @@ class FidoUnlockedPage extends ConsumerWidget { text: 'Change PIN', icon: const Icon(Icons.pin), action: (context) { - showDialog( + showBlurDialog( context: context, builder: (context) => FidoPinDialog(node.path, state), ); @@ -188,7 +188,7 @@ class FidoUnlockedPage extends ConsumerWidget { text: 'Reset FIDO', icon: const Icon(Icons.delete), action: (context) { - showDialog( + showBlurDialog( context: context, builder: (context) => ResetDialog(node), ); diff --git a/lib/oath/views/account_dialog.dart b/lib/oath/views/account_dialog.dart index ba3e447f..95bef63c 100755 --- a/lib/oath/views/account_dialog.dart +++ b/lib/oath/views/account_dialog.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../app/message.dart'; import '../../app/shortcuts.dart'; import '../../app/state.dart'; import '../../core/models.dart'; @@ -24,7 +25,7 @@ class AccountDialog extends ConsumerWidget with AccountMixin { // Replace this dialog with a new one, for the renamed credential. await ref.read(withContextProvider)((context) async { Navigator.of(context).pop(); - await showDialog( + await showBlurDialog( context: context, builder: (context) { return AccountDialog(renamed); diff --git a/lib/oath/views/account_mixin.dart b/lib/oath/views/account_mixin.dart index 3efab697..a74caf7e 100755 --- a/lib/oath/views/account_mixin.dart +++ b/lib/oath/views/account_mixin.dart @@ -77,7 +77,7 @@ mixin AccountMixin { Future renameCredential( BuildContext context, WidgetRef ref) async { final node = ref.read(currentDeviceProvider)!; - return await showDialog( + return await showBlurDialog( context: context, builder: (context) => RenameAccountDialog(node, credential), ); @@ -86,7 +86,7 @@ mixin AccountMixin { @protected Future deleteCredential(BuildContext context, WidgetRef ref) async { final node = ref.read(currentDeviceProvider)!; - return await showDialog( + return await showBlurDialog( context: context, builder: (context) => DeleteAccountDialog(node, credential), ) ?? diff --git a/lib/oath/views/account_view.dart b/lib/oath/views/account_view.dart index 9b1fe28a..2a361a1d 100755 --- a/lib/oath/views/account_view.dart +++ b/lib/oath/views/account_view.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../app/message.dart'; import '../../app/shortcuts.dart'; import '../../app/state.dart'; import '../models.dart'; @@ -117,7 +118,7 @@ class AccountView extends ConsumerWidget with AccountMixin { RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), focusNode: focusNode, onTap: () { - showDialog( + showBlurDialog( context: context, builder: (context) { return AccountDialog(credential); diff --git a/lib/oath/views/oath_screen.dart b/lib/oath/views/oath_screen.dart index 0be28141..d26a344f 100755 --- a/lib/oath/views/oath_screen.dart +++ b/lib/oath/views/oath_screen.dart @@ -62,7 +62,7 @@ class _LockedView extends ConsumerWidget { text: 'Manage password', icon: const Icon(Icons.password), action: (context) { - showDialog( + showBlurDialog( context: context, builder: (context) => ManagePasswordDialog(devicePath, oathState), @@ -73,7 +73,7 @@ class _LockedView extends ConsumerWidget { text: 'Reset OATH', icon: const Icon(Icons.delete), action: (context) { - showDialog( + showBlurDialog( context: context, builder: (context) => ResetDialog(devicePath), ); @@ -195,7 +195,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { label: const Text('Add account'), icon: const Icon(Icons.person_add_alt_1), onPressed: () { - showDialog( + showBlurDialog( context: context, builder: (context) => OathAddAccountPage( widget.devicePath, @@ -215,7 +215,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { widget.oathState.hasKey ? 'Manage password' : 'Set password', icon: const Icon(Icons.password), action: (context) { - showDialog( + showBlurDialog( context: context, builder: (context) => ManagePasswordDialog(widget.devicePath, widget.oathState), @@ -226,7 +226,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { text: 'Reset OATH', icon: const Icon(Icons.delete), action: (context) { - showDialog( + showBlurDialog( context: context, builder: (context) => ResetDialog(widget.devicePath), ); From 7d5ca654a7e36288e246dcae8c2654f1ffec43b8 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 5 Jul 2022 12:04:12 +0200 Subject: [PATCH 14/38] Fix regression in Helper when elevating FIDO on Windows. --- helper/helper/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/helper/device.py b/helper/helper/device.py index d3be50f0..ff639fcb 100644 --- a/helper/helper/device.py +++ b/helper/helper/device.py @@ -280,7 +280,7 @@ class UsbDeviceNode(AbstractDeviceNode): super().__init__(device, info) def _supports_connection(self, conn_type): - return self._device.supports_connection(conn_type) + return self._device.pid.supports_connection(conn_type) def _create_connection(self, conn_type): connection = self._device.open_connection(conn_type) From 68a776f23b6c2f4c6d9ff43c2d8a53b5c93ba818 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 5 Jul 2022 12:11:43 +0200 Subject: [PATCH 15/38] Update snackbars and options dialogs. * Re-implement snackbar to always be floating, always on top. * Use a dialog for Options instead of bottom sheet. --- lib/app/message.dart | 80 +++++-------- lib/app/views/app_failure_page.dart | 4 +- lib/app/views/device_error_screen.dart | 4 +- lib/management/views/management_screen.dart | 2 +- lib/oath/views/account_dialog.dart | 107 +++++++++-------- lib/oath/views/account_view.dart | 1 + lib/widgets/dialog_frame.dart | 22 ---- lib/widgets/responsive_dialog.dart | 36 +++--- lib/widgets/toast.dart | 121 ++++++++++++++++++++ 9 files changed, 226 insertions(+), 151 deletions(-) delete mode 100755 lib/widgets/dialog_frame.dart create mode 100755 lib/widgets/toast.dart diff --git a/lib/app/message.dart b/lib/app/message.dart index 61439644..90699be1 100755 --- a/lib/app/message.dart +++ b/lib/app/message.dart @@ -1,64 +1,46 @@ +import 'dart:async'; import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../widgets/toast.dart'; import 'models.dart'; -import 'state.dart'; -ScaffoldFeatureController showMessage( +void Function() showMessage( BuildContext context, String message, { - Duration duration = const Duration(seconds: 1), -}) { - final width = MediaQuery.of(context).size.width; - final narrow = width < 540; - return ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(message), - duration: duration, - behavior: narrow ? SnackBarBehavior.fixed : SnackBarBehavior.floating, - width: narrow ? null : 400, - )); -} + Duration duration = const Duration(seconds: 2), +}) => + showToast(context, message, duration: duration); Future showBottomMenu( BuildContext context, List actions) async { - MediaQuery? mediaQuery = context.findAncestorWidgetOfExactType(); - var width = mediaQuery?.data.size.width ?? 0; - await showModalBottomSheet( + await showBlurDialog( context: context, - constraints: width > 540 ? const BoxConstraints(maxWidth: 380) : null, - builder: (context) => SafeArea(child: _BottomMenu(actions))); -} - -class _BottomMenu extends ConsumerWidget { - final List actions; - const _BottomMenu(this.actions); - - @override - Widget build(BuildContext context, WidgetRef ref) { - // If current device changes, we need to pop back to the main Page. - ref.listen(currentDeviceProvider, (previous, next) { - Navigator.of(context).pop(); - }); - - return Column( - mainAxisSize: MainAxisSize.min, - children: actions - .map((a) => ListTile( - leading: a.icon, - title: Text(a.text), - enabled: a.action != null, - onTap: a.action == null - ? null - : () { - Navigator.pop(context); - a.action?.call(context); - }, - )) - .toList(), - ); - } + builder: (context) { + return AlertDialog( + title: const Text('Options'), + contentPadding: const EdgeInsets.only(bottom: 24, top: 4), + content: Column( + mainAxisSize: MainAxisSize.min, + children: actions + .map((a) => ListTile( + leading: a.icon, + title: Text(a.text), + contentPadding: + const EdgeInsets.symmetric(horizontal: 24), + enabled: a.action != null, + onTap: a.action == null + ? null + : () { + Navigator.pop(context); + a.action?.call(context); + }, + )) + .toList(), + ), + ); + }); } Future showBlurDialog({ diff --git a/lib/app/views/app_failure_page.dart b/lib/app/views/app_failure_page.dart index 87dba8c0..281799bc 100755 --- a/lib/app/views/app_failure_page.dart +++ b/lib/app/views/app_failure_page.dart @@ -49,7 +49,7 @@ class AppFailurePage extends ConsumerWidget { icon: const Icon(Icons.lock_open), style: AppTheme.primaryOutlinedButtonStyle(context), onPressed: () async { - final controller = showMessage( + final closeMessage = showMessage( context, 'Elevating permissions...', duration: const Duration(seconds: 30)); try { @@ -59,7 +59,7 @@ class AppFailurePage extends ConsumerWidget { showMessage(context, 'Permission denied'); } } finally { - controller.close(); + closeMessage(); } }), ]; diff --git a/lib/app/views/device_error_screen.dart b/lib/app/views/device_error_screen.dart index 06cc1af1..73c0465e 100755 --- a/lib/app/views/device_error_screen.dart +++ b/lib/app/views/device_error_screen.dart @@ -30,7 +30,7 @@ class DeviceErrorScreen extends ConsumerWidget { label: const Text('Unlock'), icon: const Icon(Icons.lock_open), onPressed: () async { - final controller = showMessage( + final closeMessage = showMessage( context, 'Elevating permissions...', duration: const Duration(seconds: 30)); try { @@ -40,7 +40,7 @@ class DeviceErrorScreen extends ConsumerWidget { showMessage(context, 'Permission denied'); } } finally { - controller.close(); + closeMessage(); } }, ), diff --git a/lib/management/views/management_screen.dart b/lib/management/views/management_screen.dart index 3213bbbc..f834a42f 100755 --- a/lib/management/views/management_screen.dart +++ b/lib/management/views/management_screen.dart @@ -180,7 +180,7 @@ class _ManagementScreenState extends ConsumerState { context, 'Reconfiguring YubiKey...', duration: const Duration(seconds: 8), - ).close; + ); } await ref .read(managementStateProvider(widget.deviceData.node.path).notifier) diff --git a/lib/oath/views/account_dialog.dart b/lib/oath/views/account_dialog.dart index 95bef63c..96b1a243 100755 --- a/lib/oath/views/account_dialog.dart +++ b/lib/oath/views/account_dialog.dart @@ -8,7 +8,6 @@ import '../../app/shortcuts.dart'; import '../../app/state.dart'; import '../../core/models.dart'; import '../../core/state.dart'; -import '../../widgets/dialog_frame.dart'; import '../models.dart'; import 'account_mixin.dart'; @@ -123,68 +122,66 @@ class AccountDialog extends ConsumerWidget with AccountMixin { }, child: Focus( autofocus: true, - child: DialogFrame( - child: AlertDialog( - title: Center( - child: Text( - title, - overflow: TextOverflow.fade, - style: Theme.of(context).textTheme.headlineSmall, - maxLines: 1, - softWrap: false, - ), + child: AlertDialog( + title: Center( + child: Text( + title, + overflow: TextOverflow.fade, + style: Theme.of(context).textTheme.headlineSmall, + maxLines: 1, + softWrap: false, ), - contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), - content: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - if (subtitle != null) - Text( - subtitle!, - overflow: TextOverflow.fade, - maxLines: 1, - softWrap: false, - // This is what ListTile uses for subtitle - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context).textTheme.caption!.color, - ), - ), - const SizedBox(height: 12.0), - DecoratedBox( - decoration: BoxDecoration( - shape: BoxShape.rectangle, - color: CardTheme.of(context).color, - borderRadius: const BorderRadius.all(Radius.circular(30.0)), - ), - child: Center( - child: FittedBox( - child: DefaultTextStyle.merge( - style: const TextStyle(fontSize: 28), - child: IconTheme( - data: IconTheme.of(context).copyWith(size: 24), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, vertical: 8.0), - child: buildCodeView(ref), - ), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), + content: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + if (subtitle != null) + Text( + subtitle!, + overflow: TextOverflow.fade, + maxLines: 1, + softWrap: false, + // This is what ListTile uses for subtitle + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).textTheme.caption!.color, + ), + ), + const SizedBox(height: 12.0), + DecoratedBox( + decoration: BoxDecoration( + shape: BoxShape.rectangle, + color: CardTheme.of(context).color, + borderRadius: const BorderRadius.all(Radius.circular(30.0)), + ), + child: Center( + child: FittedBox( + child: DefaultTextStyle.merge( + style: const TextStyle(fontSize: 28), + child: IconTheme( + data: IconTheme.of(context).copyWith(size: 24), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 8.0), + child: buildCodeView(ref), ), ), ), ), ), - ], - ), - actionsPadding: const EdgeInsets.only(top: 10.0, right: -16.0), - actions: [ - Center( - child: FittedBox( - alignment: Alignment.center, - child: Row(children: _buildActions(context, ref)), - ), - ) + ), ], ), + actionsPadding: const EdgeInsets.only(top: 10.0, right: -16.0), + actions: [ + Center( + child: FittedBox( + alignment: Alignment.center, + child: Row(children: _buildActions(context, ref)), + ), + ) + ], ), ), ); diff --git a/lib/oath/views/account_view.dart b/lib/oath/views/account_view.dart index 2a361a1d..287bff55 100755 --- a/lib/oath/views/account_view.dart +++ b/lib/oath/views/account_view.dart @@ -58,6 +58,7 @@ class AccountView extends ConsumerWidget with AccountMixin { child: ListTile( leading: e.icon, title: Text(e.text), + enabled: action != null, dense: true, contentPadding: EdgeInsets.zero, ), diff --git a/lib/widgets/dialog_frame.dart b/lib/widgets/dialog_frame.dart deleted file mode 100755 index 5b2b5b04..00000000 --- a/lib/widgets/dialog_frame.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/material.dart'; - -class DialogFrame extends StatelessWidget { - final Widget child; - const DialogFrame({super.key, required this.child}); - - @override - Widget build(BuildContext context) => GestureDetector( - onTap: () { - Navigator.of(context).pop(); - }, - // Shows Snackbars above modal - child: Scaffold( - backgroundColor: Colors.transparent, - body: GestureDetector( - behavior: HitTestBehavior.deferToChild, - onTap: () {}, // Block onTap of parent gesture detector - child: child, - ), - ), - ); -} diff --git a/lib/widgets/responsive_dialog.dart b/lib/widgets/responsive_dialog.dart index f264ae09..31e23473 100755 --- a/lib/widgets/responsive_dialog.dart +++ b/lib/widgets/responsive_dialog.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'dialog_frame.dart'; - class ResponsiveDialog extends StatefulWidget { final Widget? title; final Widget child; @@ -48,25 +46,23 @@ class _ResponsiveDialogState extends State { final cancelText = widget.onCancel == null && widget.actions.isEmpty ? 'Close' : 'Cancel'; - return DialogFrame( - child: AlertDialog( - title: widget.title, - scrollable: true, - content: SizedBox( - width: 380, - child: Container(key: _childKey, child: widget.child), - ), - actions: [ - TextButton( - child: Text(cancelText), - onPressed: () { - widget.onCancel?.call(); - Navigator.of(context).pop(); - }, - ), - ...widget.actions - ], + return AlertDialog( + title: widget.title, + scrollable: true, + content: SizedBox( + width: 380, + child: Container(key: _childKey, child: widget.child), ), + actions: [ + TextButton( + child: Text(cancelText), + onPressed: () { + widget.onCancel?.call(); + Navigator.of(context).pop(); + }, + ), + ...widget.actions + ], ); } })); diff --git a/lib/widgets/toast.dart b/lib/widgets/toast.dart new file mode 100755 index 00000000..f00ff16f --- /dev/null +++ b/lib/widgets/toast.dart @@ -0,0 +1,121 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class Toast extends StatefulWidget { + final String message; + final Duration duration; + final void Function() onComplete; + final Color? backgroundColor; + final TextStyle? textStyle; + const Toast( + this.message, + this.duration, { + required this.onComplete, + this.backgroundColor, + this.textStyle, + super.key, + }); + + @override + State createState() => _ToastState(); +} + +class _ToastState extends State with SingleTickerProviderStateMixin { + late AnimationController _animator; + late Tween _tween; + late Animation _opacity; + + @override + void initState() { + super.initState(); + + _animator = AnimationController( + duration: const Duration(milliseconds: 100), vsync: this); + _tween = Tween(begin: 0, end: 1); + _opacity = _tween.animate(_animator) + ..addListener(() { + setState(() {}); + }); + + _animate(); + } + + void _animate() async { + await _animator.forward(); + if (mounted) { + await Future.delayed(widget.duration); + } + if (mounted) { + await _animator.reverse(); + } + widget.onComplete(); + } + + @override + void dispose() { + _animator.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _opacity, + child: Material( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + color: widget.backgroundColor, + child: Center( + child: Text( + widget.message, + style: widget.textStyle, + )), + ), + ); + } +} + +void Function() showToast( + BuildContext context, + String message, { + Duration duration = const Duration(seconds: 2), +}) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + final bool isThemeDark = theme.brightness == Brightness.dark; + final Color backgroundColor = isThemeDark + ? colorScheme.onSurface + : Color.alphaBlend( + colorScheme.onSurface.withOpacity(0.80), colorScheme.surface); + final textStyle = + ThemeData(brightness: isThemeDark ? Brightness.light : Brightness.dark) + .textTheme + .subtitle1; + + OverlayEntry? entry; + entry = OverlayEntry(builder: (context) { + return Align( + alignment: Alignment.bottomCenter, + child: Container( + height: 50, + width: 400, + margin: const EdgeInsets.all(8), + child: Toast( + message, + duration, + backgroundColor: backgroundColor, + textStyle: textStyle, + onComplete: () { + entry!.remove(); + }, + ), + ), + ); + }); + Timer.run(() { + Overlay.of(context)!.insert(entry!); + }); + return entry.remove; +} From c81faf6ce68f6bd992cf0c9d45575c3283e15f90 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 5 Jul 2022 12:13:56 +0200 Subject: [PATCH 16/38] Increase blur. --- lib/app/message.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/app/message.dart b/lib/app/message.dart index 90699be1..31ffd054 100755 --- a/lib/app/message.dart +++ b/lib/app/message.dart @@ -56,8 +56,8 @@ Future showBlurDialog({ pageBuilder: (ctx, anim1, anim2) => builder(ctx), transitionDuration: const Duration(milliseconds: 150), transitionBuilder: (ctx, anim1, anim2, child) => BackdropFilter( - filter: - ImageFilter.blur(sigmaX: 4 * anim1.value, sigmaY: 4 * anim1.value), + filter: ImageFilter.blur( + sigmaX: 20 * anim1.value, sigmaY: 20 * anim1.value), child: FadeTransition( opacity: anim1, child: child, From 827ad19062d6a05e62672f4bb1c25b9d1320b37b Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 5 Jul 2022 12:14:10 +0200 Subject: [PATCH 17/38] Start improving DevicePicker UI. - Add right-click menu for hide/show NFC readers. - Put more focus on selected device. --- lib/app/views/device_picker_dialog.dart | 257 +++++++++++++++++------- 1 file changed, 185 insertions(+), 72 deletions(-) diff --git a/lib/app/views/device_picker_dialog.dart b/lib/app/views/device_picker_dialog.dart index 2a66105d..30e23c23 100755 --- a/lib/app/views/device_picker_dialog.dart +++ b/lib/app/views/device_picker_dialog.dart @@ -2,7 +2,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../../core/state.dart'; import '../../management/models.dart'; import '../models.dart'; import '../state.dart'; @@ -18,12 +20,36 @@ String _getSubtitle(DeviceInfo info) { return subtitle; } +final _hiddenDevicesProvider = + StateNotifierProvider<_HiddenDevicesNotifier, List>( + (ref) => _HiddenDevicesNotifier(ref.watch(prefProvider))); + +class _HiddenDevicesNotifier extends StateNotifier> { + static const String _key = 'DEVICE_PICKER_HIDDEN'; + final SharedPreferences _prefs; + _HiddenDevicesNotifier(this._prefs) : super(_prefs.getStringList(_key) ?? []); + + void showAll() { + state = []; + _prefs.setStringList(_key, state); + } + + void hideDevice(DevicePath devicePath) { + state = [...state, devicePath.key]; + _prefs.setStringList(_key, state); + } +} + class DevicePickerDialog extends ConsumerWidget { const DevicePickerDialog({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final devices = ref.watch(attachedDevicesProvider).toList(); + final hidden = ref.watch(_hiddenDevicesProvider); + final devices = ref + .watch(attachedDevicesProvider) + .where((e) => !hidden.contains(e.path.key)) + .toList(); final currentNode = ref.watch(currentDeviceProvider); final Widget hero; @@ -34,23 +60,25 @@ class DevicePickerDialog extends ConsumerWidget { hero = _CurrentDeviceRow( currentNode, ref.watch(currentDeviceDataProvider), - onTap: () { - Navigator.of(context).pop(); - }, ); } else { - hero = ListTile( - leading: DeviceAvatar( - selected: true, - child: Icon(Platform.isAndroid ? Icons.no_cell : Icons.usb), - ), - title: Text(Platform.isAndroid ? 'No YubiKey' : 'USB'), - subtitle: Text(Platform.isAndroid - ? 'Insert or tap a YubiKey' - : 'Insert a YubiKey'), - onTap: () { - Navigator.of(context).pop(); - }, + hero = Column( + children: [ + DeviceAvatar( + selected: true, + radius: 64, + child: Icon(Platform.isAndroid ? Icons.no_cell : Icons.usb), + ), + ListTile( + title: + Center(child: Text(Platform.isAndroid ? 'No YubiKey' : 'USB')), + subtitle: Center( + child: Text(Platform.isAndroid + ? 'Insert or tap a YubiKey' + : 'Insert a YubiKey'), + ), + ), + ], ); showUsb = false; } @@ -68,31 +96,55 @@ class DevicePickerDialog extends ConsumerWidget { title: const Text('USB'), subtitle: const Text('No YubiKey present'), onTap: () { - Navigator.of(context).pop(); + //Navigator.of(context).pop(); ref.read(currentDeviceProvider.notifier).setCurrentDevice(null); }, ), ...devices.map( - (e) => _DeviceRow( - e, - info: e.map( - usbYubiKey: (node) => node.info, - nfcReader: (_) => null, - ), - onTap: () { - Navigator.of(context).pop(); - ref.read(currentDeviceProvider.notifier).setCurrentDevice(e); - }, + (e) => e.map( + usbYubiKey: (node) => _DeviceRow(node, info: node.info), + nfcReader: (node) => _NfcDeviceRow(node), ), ), ]; - return SimpleDialog( - children: [ - hero, - if (others.isNotEmpty) const Divider(), - ...others, - ], + return GestureDetector( + onSecondaryTapDown: hidden.isEmpty + ? null + : (details) { + showMenu( + context: context, + position: RelativeRect.fromLTRB( + details.globalPosition.dx, + details.globalPosition.dy, + details.globalPosition.dx, + 0, + ), + items: [ + PopupMenuItem( + onTap: () { + ref.read(_hiddenDevicesProvider.notifier).showAll(); + }, + child: const ListTile( + title: Text('Show hidden devices'), + dense: true, + contentPadding: EdgeInsets.zero, + ), + ), + ], + ); + }, + child: SimpleDialog( + children: [ + hero, + if (others.isNotEmpty) + const Padding( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Divider(), + ), + ...others, + ], + ), ); } } @@ -100,29 +152,32 @@ class DevicePickerDialog extends ConsumerWidget { class _CurrentDeviceRow extends StatelessWidget { final DeviceNode node; final AsyncValue data; - final Function() onTap; - const _CurrentDeviceRow( - this.node, - this.data, { - required this.onTap, - }); + const _CurrentDeviceRow(this.node, this.data); @override Widget build(BuildContext context) => data.when( data: (data) { final isNfc = data.node is NfcReaderNode; - return ListTile( - leading: DeviceAvatar.yubiKeyData( - data, - selected: true, - ), - isThreeLine: isNfc, - title: Text(isNfc ? node.name : data.name), - subtitle: Text(isNfc - ? '${data.name}\n${_getSubtitle(data.info)}' - : _getSubtitle(data.info)), - onTap: onTap, + return Column( + children: [ + DeviceAvatar.yubiKeyData( + data, + selected: true, + radius: 64, + ), + ListTile( + isThreeLine: isNfc, + title: Center(child: Text(data.name)), + subtitle: Column( + children: [ + Text(_getSubtitle(data.info)), + if (isNfc) Text(node.name), + ], + ), + //onTap: onTap, + ), + ], ); }, error: (error, _) { @@ -134,41 +189,47 @@ class _CurrentDeviceRow extends StatelessWidget { default: message = 'No YubiKey present'; } - return ListTile( - leading: DeviceAvatar.deviceNode( - node, - selected: true, - ), - title: Text(message), - subtitle: Text(node.name), - onTap: onTap, + return Column( + children: [ + DeviceAvatar.deviceNode( + node, + selected: true, + radius: 64, + ), + ListTile( + title: Center(child: Text(message)), + subtitle: Center(child: Text(node.name)), + ), + ], ); }, - loading: () => ListTile( - leading: DeviceAvatar.deviceNode( - node, - selected: true, - ), - title: const Text('No YubiKey present'), - subtitle: Text(node.name), - onTap: onTap, + loading: () => Column( + children: [ + DeviceAvatar.deviceNode( + node, + selected: true, + radius: 64, + ), + ListTile( + title: const Center(child: Text('No YubiKey present')), + subtitle: Center(child: Text(node.name)), + ), + ], ), ); } -class _DeviceRow extends StatelessWidget { +class _DeviceRow extends ConsumerWidget { final DeviceNode node; final DeviceInfo? info; - final Function() onTap; const _DeviceRow( this.node, { - required this.info, - required this.onTap, + this.info, }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return ListTile( leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 4), @@ -185,7 +246,59 @@ class _DeviceRow extends StatelessWidget { nfcReader: (_, __) => 'Select to scan', ), ), - onTap: onTap, + onTap: () { + //Navigator.of(context).pop(); + ref.read(currentDeviceProvider.notifier).setCurrentDevice(node); + }, + ); + } +} + +class _NfcDeviceRow extends ConsumerWidget { + final DeviceNode node; + + const _NfcDeviceRow(this.node); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final hidden = ref.watch(_hiddenDevicesProvider); + return GestureDetector( + onSecondaryTapDown: (details) { + showMenu( + context: context, + position: RelativeRect.fromLTRB( + details.globalPosition.dx, + details.globalPosition.dy, + details.globalPosition.dx, + 0, + ), + items: [ + PopupMenuItem( + enabled: hidden.isNotEmpty, + onTap: () { + ref.read(_hiddenDevicesProvider.notifier).showAll(); + }, + child: ListTile( + title: const Text('Show hidden devices'), + dense: true, + contentPadding: EdgeInsets.zero, + enabled: hidden.isNotEmpty, + ), + ), + PopupMenuItem( + onTap: () { + ref.read(_hiddenDevicesProvider.notifier).hideDevice(node.path); + }, + child: const ListTile( + title: Text('Hide device'), + dense: true, + contentPadding: EdgeInsets.zero, + ), + ), + ], + ); + }, + child: _DeviceRow(node), ); } } From 33e986ff7f8649359f06854fd8a90706b06fcc5e Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 5 Jul 2022 13:25:25 +0200 Subject: [PATCH 18/38] Fix device picker for inaccessible device. --- lib/app/views/device_picker_dialog.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/app/views/device_picker_dialog.dart b/lib/app/views/device_picker_dialog.dart index 30e23c23..1b74089e 100755 --- a/lib/app/views/device_picker_dialog.dart +++ b/lib/app/views/device_picker_dialog.dart @@ -211,8 +211,8 @@ class _CurrentDeviceRow extends StatelessWidget { radius: 64, ), ListTile( - title: const Center(child: Text('No YubiKey present')), - subtitle: Center(child: Text(node.name)), + title: Center(child: Text(node.name)), + subtitle: const Center(child: Text('Device inaccessible')), ), ], ), From e5db679d0ec860324639c6e447a41dac4d6dc130 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 5 Jul 2022 13:26:12 +0200 Subject: [PATCH 19/38] Fix toast: Check mounted before removal. --- lib/widgets/toast.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/widgets/toast.dart b/lib/widgets/toast.dart index f00ff16f..94d0d0c1 100755 --- a/lib/widgets/toast.dart +++ b/lib/widgets/toast.dart @@ -95,6 +95,12 @@ void Function() showToast( .subtitle1; OverlayEntry? entry; + void close() { + if (entry != null && entry.mounted) { + entry.remove(); + } + } + entry = OverlayEntry(builder: (context) { return Align( alignment: Alignment.bottomCenter, @@ -107,9 +113,7 @@ void Function() showToast( duration, backgroundColor: backgroundColor, textStyle: textStyle, - onComplete: () { - entry!.remove(); - }, + onComplete: close, ), ), ); @@ -117,5 +121,6 @@ void Function() showToast( Timer.run(() { Overlay.of(context)!.insert(entry!); }); - return entry.remove; + + return close; } From 7ee5b82906dfdae5d3119ff511e67543db786968 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 5 Jul 2022 13:26:43 +0200 Subject: [PATCH 20/38] Improve user interation dialog. --- lib/app/views/user_interaction.dart | 23 +++++++++++++++++++---- lib/desktop/oath/state.dart | 6 ++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/app/views/user_interaction.dart b/lib/app/views/user_interaction.dart index 8006c03e..09e85fdf 100755 --- a/lib/app/views/user_interaction.dart +++ b/lib/app/views/user_interaction.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import '../message.dart'; + abstract class UserInteractionController { void updateContent({String? title, String? description, Widget? icon}); void close(); @@ -56,15 +58,28 @@ class _UserInteractionDialogState extends State<_UserInteractionDialog> { Widget? icon = widget.controller.icon; return AlertDialog( scrollable: true, - title: Text(widget.controller.title), content: SizedBox( - width: 300, + height: 160, + width: 100, child: Column( mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, children: [ - if (icon != null) icon, + if (icon != null) + Padding( + padding: const EdgeInsets.all(24), + child: IconTheme( + data: IconTheme.of(context).copyWith(size: 36), + child: icon, + ), + ), + Text( + widget.controller.title, + style: Theme.of(context).textTheme.headline6, + ), Text( widget.controller.description, + textAlign: TextAlign.center, softWrap: true, ), ], @@ -107,7 +122,7 @@ UserInteractionController promptUserInteraction( Navigator.of(context).pop(); }, ); - showDialog( + showBlurDialog( context: context, builder: (context) { return WillPopScope( diff --git a/lib/desktop/oath/state.dart b/lib/desktop/oath/state.dart index d6ced485..0cc1f47e 100755 --- a/lib/desktop/oath/state.dart +++ b/lib/desktop/oath/state.dart @@ -2,13 +2,14 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; -import 'package:yubico_authenticator/app/logging.dart'; -import 'package:yubico_authenticator/app/views/user_interaction.dart'; +import '../../app/logging.dart'; import '../../app/models.dart'; import '../../app/state.dart'; +import '../../app/views/user_interaction.dart'; import '../../core/models.dart'; import '../../oath/models.dart'; import '../../oath/state.dart'; @@ -254,6 +255,7 @@ class _DesktopCredentialListNotifier extends OathCredentialListNotifier { controller = await _withContext( (context) async => promptUserInteraction( context, + icon: const Icon(Icons.touch_app), title: 'Touch Required', description: 'Touch the button on your YubiKey now.', ), From 662536140abc43d1e6222557e6d40fe17f8fd86b Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 5 Jul 2022 14:53:21 +0200 Subject: [PATCH 21/38] Count UTF8 bytes for byte-limited text fields. --- lib/fido/views/add_fingerprint_dialog.dart | 3 +++ lib/fido/views/rename_fingerprint_dialog.dart | 4 +++- lib/oath/views/add_account_page.dart | 7 ++++++- lib/oath/views/rename_account_dialog.dart | 5 +++++ lib/oath/views/utils.dart | 5 +++-- lib/widgets/utf8_text_fields.dart | 21 +++++++++++++++++++ 6 files changed, 41 insertions(+), 4 deletions(-) create mode 100755 lib/widgets/utf8_text_fields.dart diff --git a/lib/fido/views/add_fingerprint_dialog.dart b/lib/fido/views/add_fingerprint_dialog.dart index 0ce0e8b9..d0618490 100755 --- a/lib/fido/views/add_fingerprint_dialog.dart +++ b/lib/fido/views/add_fingerprint_dialog.dart @@ -10,6 +10,7 @@ import 'package:yubico_authenticator/app/logging.dart'; import '../../app/message.dart'; import '../../desktop/models.dart'; import '../../widgets/responsive_dialog.dart'; +import '../../widgets/utf8_text_fields.dart'; import '../state.dart'; import '../../fido/models.dart'; import '../../app/models.dart'; @@ -179,6 +180,8 @@ class _AddFingerprintDialogState extends ConsumerState TextFormField( focusNode: _nameFocus, maxLength: 15, + inputFormatters: [limitBytesLength(15)], + buildCounter: buildCountersFor(_label), autofocus: true, decoration: InputDecoration( enabled: _fingerprint != null, diff --git a/lib/fido/views/rename_fingerprint_dialog.dart b/lib/fido/views/rename_fingerprint_dialog.dart index c493b48f..a2f5f57b 100755 --- a/lib/fido/views/rename_fingerprint_dialog.dart +++ b/lib/fido/views/rename_fingerprint_dialog.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../app/message.dart'; import '../../desktop/models.dart'; import '../../widgets/responsive_dialog.dart'; +import '../../widgets/utf8_text_fields.dart'; import '../models.dart'; import '../state.dart'; import '../../app/models.dart'; @@ -69,8 +70,9 @@ class _RenameAccountDialogState extends ConsumerState { const Text('This will change the label of the fingerprint.'), TextFormField( initialValue: _label, - // TODO: Make this field count UTF-8 bytes instead of characters. maxLength: 15, + inputFormatters: [limitBytesLength(15)], + buildCounter: buildCountersFor(_label), decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Label', diff --git a/lib/oath/views/add_account_page.dart b/lib/oath/views/add_account_page.dart index bf6d272d..101deb47 100755 --- a/lib/oath/views/add_account_page.dart +++ b/lib/oath/views/add_account_page.dart @@ -5,14 +5,15 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; -import 'package:yubico_authenticator/app/logging.dart'; +import '../../app/logging.dart'; import '../../app/message.dart'; import '../../app/models.dart'; import '../../app/state.dart'; import '../../desktop/models.dart'; import '../../widgets/file_drop_target.dart'; import '../../widgets/responsive_dialog.dart'; +import '../../widgets/utf8_text_fields.dart'; import '../models.dart'; import '../state.dart'; import 'utils.dart'; @@ -210,6 +211,8 @@ class _OathAddAccountPageState extends ConsumerState { autofocus: true, enabled: issuerRemaining > 0, maxLength: max(issuerRemaining, 1), + inputFormatters: [limitBytesLength(issuerRemaining)], + buildCounter: buildCountersFor(_issuerController.text), decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Issuer (optional)', @@ -229,6 +232,8 @@ class _OathAddAccountPageState extends ConsumerState { key: const Key('name'), controller: _accountController, maxLength: max(nameRemaining, 1), + buildCounter: buildCountersFor(_accountController.text), + inputFormatters: [limitBytesLength(nameRemaining)], decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Account name', diff --git a/lib/oath/views/rename_account_dialog.dart b/lib/oath/views/rename_account_dialog.dart index c695a091..21303dae 100755 --- a/lib/oath/views/rename_account_dialog.dart +++ b/lib/oath/views/rename_account_dialog.dart @@ -7,6 +7,7 @@ import '../../app/message.dart'; import '../../app/models.dart'; import '../../desktop/models.dart'; import '../../widgets/responsive_dialog.dart'; +import '../../widgets/utf8_text_fields.dart'; import '../models.dart'; import '../state.dart'; import 'utils.dart'; @@ -97,6 +98,8 @@ class _RenameAccountDialogState extends ConsumerState { initialValue: _issuer, enabled: issuerRemaining > 0, maxLength: issuerRemaining > 0 ? issuerRemaining : null, + buildCounter: buildCountersFor(_issuer), + inputFormatters: [limitBytesLength(issuerRemaining)], decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Issuer (optional)', @@ -112,6 +115,8 @@ class _RenameAccountDialogState extends ConsumerState { TextFormField( initialValue: _account, maxLength: nameRemaining, + inputFormatters: [limitBytesLength(nameRemaining)], + buildCounter: buildCountersFor(_account), decoration: InputDecoration( border: const OutlineInputBorder(), labelText: 'Account name', diff --git a/lib/oath/views/utils.dart b/lib/oath/views/utils.dart index 96cb3b09..62634c4d 100755 --- a/lib/oath/views/utils.dart +++ b/lib/oath/views/utils.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:math'; import '../models.dart'; @@ -18,7 +19,7 @@ Pair getRemainingKeySpace( // Non-standard TOTP periods are stored as part of this data, as a "D/"- prefix. remaining -= '$period/'.length; } - int issuerSpace = issuer.length; + int issuerSpace = utf8.encode(issuer).length; if (issuer.isNotEmpty) { // Issuer is separated from name with a ":", if present. issuerSpace += 1; @@ -26,7 +27,7 @@ Pair getRemainingKeySpace( return Pair( // Always reserve at least one character for name - remaining - 1 - max(name.length, 1), + remaining - 1 - max(utf8.encode(name).length, 1), remaining - issuerSpace, ); } diff --git a/lib/widgets/utf8_text_fields.dart b/lib/widgets/utf8_text_fields.dart new file mode 100755 index 00000000..d6ccdb06 --- /dev/null +++ b/lib/widgets/utf8_text_fields.dart @@ -0,0 +1,21 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +int _byteLength(String value) => utf8.encode(value).length; + +InputCounterWidgetBuilder buildCountersFor(String currentValue) => + (context, {required currentLength, required isFocused, maxLength}) => Text( + maxLength != null ? '${_byteLength(currentValue)}/$maxLength' : '', + style: Theme.of(context).textTheme.caption, + ); + +TextInputFormatter limitBytesLength(int maxByteLength) => + TextInputFormatter.withFunction((oldValue, newValue) { + final newLength = _byteLength(newValue.text); + if (newLength <= maxByteLength) { + return newValue; + } + return oldValue; + }); From 9a753ffcc98e37da0dd93a503d20a46f75a78311 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 6 Jul 2022 10:12:30 +0200 Subject: [PATCH 22/38] OATH make sure values get URL-encoded. Fixes issue with some special characters not working (like ?). --- lib/oath/models.dart | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/lib/oath/models.dart b/lib/oath/models.dart index 1d74da63..d2b4fc08 100755 --- a/lib/oath/models.dart +++ b/lib/oath/models.dart @@ -125,26 +125,18 @@ class CredentialData with _$CredentialData { ); } - Uri toUri() { - final path = issuer != null ? '$issuer:$name' : name; - var uri = 'otpauth://${oathType.name}/$path?secret=$secret'; - switch (oathType) { - case OathType.hotp: - uri += '&counter=$counter'; - break; - case OathType.totp: - uri += '&period=$period'; - break; - } - if (issuer != null) { - uri += '&issuer=$issuer'; - } - if (digits != 6) { - uri += '&digits=$digits'; - } - if (hashAlgorithm != HashAlgorithm.sha1) { - uri += '&algorithm=${hashAlgorithm.name}'; - } - return Uri.parse(uri); - } + Uri toUri() => Uri( + scheme: 'otpauth', + host: oathType.name, + path: issuer != null ? '$issuer:$name' : name, + queryParameters: { + 'secret': secret, + if (oathType == OathType.totp) 'period': period.toString(), + if (oathType == OathType.hotp) 'counter': counter.toString(), + if (issuer != null) 'issuer': issuer!, + if (digits != 6) 'digits': digits.toString(), + if (hashAlgorithm != HashAlgorithm.sha1) + 'algorithm': hashAlgorithm.name, + }, + ); } From 0f41871347b9a9a45108722ea15700ece079bd04 Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Wed, 6 Jul 2022 11:49:08 +0200 Subject: [PATCH 23/38] Win: license for installer --- resources/win/license.rtf | Bin 0 -> 578 bytes resources/win/yubioath-desktop.wxs | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 resources/win/license.rtf diff --git a/resources/win/license.rtf b/resources/win/license.rtf new file mode 100644 index 0000000000000000000000000000000000000000..fb175ef1d1f0bf9c2218f43f8b0bf56384d8e7a8 GIT binary patch literal 578 zcmcJM!D`$v5QcjW*F%zk6kG3G@M? z!;cvL(R}l3#Kg8i6|!4IcWom#5)D}^FvQ;Jc<2<*1+PNei1m6z8w2ysmy19foJXto zDrI5;_sY9na@%P-KO0d;A5(F{784nPZ6XQ+cfKdN*=!fj1-29@DUJzVo!*m?0vfRr ztLA!rB`17$H2ikKTw<>qI6uQqC4*T>AuxOS^{VKh_uMQvSxTHqZ{Y3-+B;oZTX>;A z49D1mnh}5_@SfnU-E!_R1WP>JB#X$qO(+iX5iVDM2ZCIgCLENeURX2c7$ z-nfu?e0=-y@cR7p@*VcfUB0dBVHj4&<@r@fu+Dwgl_YAWbjX#C!8mp?qYx%z~e*t1g(Ki49 literal 0 HcmV?d00001 diff --git a/resources/win/yubioath-desktop.wxs b/resources/win/yubioath-desktop.wxs index 762ca5bd..5dcb4ed9 100644 --- a/resources/win/yubioath-desktop.wxs +++ b/resources/win/yubioath-desktop.wxs @@ -10,6 +10,7 @@ + @@ -31,6 +32,11 @@ 1 + Date: Wed, 6 Jul 2022 15:02:28 +0200 Subject: [PATCH 24/38] Win actions fix --- .github/workflows/windows.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 2f656c96..63a69c2c 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -58,6 +58,7 @@ jobs: run: | $env:PATH += ";$env:WIX\bin" $env:SRCDIR = "build\windows\runner\Release\" + mv resources\win\license.rtf .\ heat dir .\build\windows\runner\Release\ -out fragment.wxs -gg -scom -srd -sfrag -dr INSTALLDIR -cg ApplicationFiles -var env.SRCDIR candle .\fragment.wxs .\resources\win\yubioath-desktop.wxs -ext WixUtilExtension -arch x64 light fragment.wixobj yubioath-desktop.wixobj -ext WixUIExtension -ext WixUtilExtension -o yubioath-desktop.msi From f4838850e761f657d2a8a83b57bc8c0de8880378 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 6 Jul 2022 13:24:01 +0200 Subject: [PATCH 25/38] Add gradient behind device image in picker. --- lib/app/views/device_avatar.dart | 36 ++---- lib/app/views/device_button.dart | 19 +-- lib/app/views/device_picker_dialog.dart | 160 ++++++++++++------------ lib/desktop/devices.dart | 2 + 4 files changed, 102 insertions(+), 115 deletions(-) diff --git a/lib/app/views/device_avatar.dart b/lib/app/views/device_avatar.dart index d11a4433..f368fb34 100755 --- a/lib/app/views/device_avatar.dart +++ b/lib/app/views/device_avatar.dart @@ -5,46 +5,34 @@ import '../models.dart'; import 'device_images.dart'; class DeviceAvatar extends StatelessWidget { - final bool selected; final Widget child; final Widget? badge; final double? radius; - const DeviceAvatar( - {super.key, - this.selected = false, - required this.child, - this.badge, - this.radius}); + const DeviceAvatar({super.key, required this.child, this.badge, this.radius}); - factory DeviceAvatar.yubiKeyData(YubiKeyData data, - {bool selected = false, double? radius}) => + factory DeviceAvatar.yubiKeyData(YubiKeyData data, {double? radius}) => DeviceAvatar( badge: data.node is NfcReaderNode ? nfcIcon : null, - selected: selected, radius: radius, child: getProductImage(data.info, data.name), ); - factory DeviceAvatar.deviceNode(DeviceNode node, - {bool selected = false, double? radius}) => + factory DeviceAvatar.deviceNode(DeviceNode node, {double? radius}) => node.map( usbYubiKey: (node) { final info = node.info; if (info != null) { return DeviceAvatar.yubiKeyData( YubiKeyData(node, node.name, info), - selected: selected, radius: radius, ); } return DeviceAvatar( - selected: selected, radius: radius, child: const Icon(Icons.device_unknown), ); }, nfcReader: (_) => DeviceAvatar( - selected: selected, radius: radius, child: nfcIcon, ), @@ -52,24 +40,18 @@ class DeviceAvatar extends StatelessWidget { @override Widget build(BuildContext context) { - final radius = this.radius ?? 24; + final radius = this.radius ?? 20; return Stack( alignment: AlignmentDirectional.bottomEnd, children: [ CircleAvatar( radius: radius, - backgroundColor: selected - ? Theme.of(context).colorScheme.primary - : Colors.transparent, - child: CircleAvatar( - radius: radius - 1, - backgroundColor: Theme.of(context).colorScheme.background, - child: IconTheme( - data: IconTheme.of(context).copyWith( - size: radius, - ), - child: child, + backgroundColor: Theme.of(context).colorScheme.background, + child: IconTheme( + data: IconTheme.of(context).copyWith( + size: radius, ), + child: child, ), ), if (badge != null) diff --git a/lib/app/views/device_button.dart b/lib/app/views/device_button.dart index aaf4efc2..2e061326 100755 --- a/lib/app/views/device_button.dart +++ b/lib/app/views/device_button.dart @@ -18,19 +18,16 @@ class DeviceButton extends ConsumerWidget { deviceWidget = ref.watch(currentDeviceDataProvider).maybeWhen( data: (data) => DeviceAvatar.yubiKeyData( data, - selected: true, - radius: radius, + radius: radius - 1, ), orElse: () => DeviceAvatar.deviceNode( deviceNode, - selected: true, - radius: radius, + radius: radius - 1, ), ); } else { deviceWidget = DeviceAvatar( - radius: radius, - selected: true, + radius: radius - 1, child: const Icon(Icons.usb), ); } @@ -41,7 +38,15 @@ class DeviceButton extends ConsumerWidget { icon: OverflowBox( maxHeight: 44, maxWidth: 44, - child: deviceWidget, + child: CircleAvatar( + radius: radius, + backgroundColor: Theme.of(context).colorScheme.primary, + child: IconTheme( + // Force the standard icon theme + data: IconTheme.of(context), + child: deviceWidget, + ), + ), ), onPressed: () { showBlurDialog( diff --git a/lib/app/views/device_picker_dialog.dart b/lib/app/views/device_picker_dialog.dart index 1b74089e..27bcb78b 100755 --- a/lib/app/views/device_picker_dialog.dart +++ b/lib/app/views/device_picker_dialog.dart @@ -10,7 +10,7 @@ import '../models.dart'; import '../state.dart'; import 'device_avatar.dart'; -String _getSubtitle(DeviceInfo info) { +String _getInfoString(DeviceInfo info) { final serial = info.serial; var subtitle = ''; if (serial != null) { @@ -64,10 +64,11 @@ class DevicePickerDialog extends ConsumerWidget { } else { hero = Column( children: [ - DeviceAvatar( - selected: true, - radius: 64, - child: Icon(Platform.isAndroid ? Icons.no_cell : Icons.usb), + _HeroAvatar( + child: DeviceAvatar( + radius: 64, + child: Icon(Platform.isAndroid ? Icons.no_cell : Icons.usb), + ), ), ListTile( title: @@ -88,15 +89,11 @@ class DevicePickerDialog extends ConsumerWidget { ListTile( leading: const Padding( padding: EdgeInsets.symmetric(horizontal: 4), - child: DeviceAvatar( - radius: 20, - child: Icon(Icons.usb), - ), + child: DeviceAvatar(child: Icon(Icons.usb)), ), title: const Text('USB'), subtitle: const Text('No YubiKey present'), onTap: () { - //Navigator.of(context).pop(); ref.read(currentDeviceProvider.notifier).setCurrentDevice(null); }, ), @@ -149,6 +146,37 @@ class DevicePickerDialog extends ConsumerWidget { } } +class _HeroAvatar extends StatelessWidget { + final Widget child; + const _HeroAvatar({required this.child}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + theme.colorScheme.background, + theme.colorScheme.background.withOpacity(0.4), + (theme.dialogTheme.backgroundColor ?? theme.dialogBackgroundColor) + .withOpacity(0), + ], + ), + ), + padding: const EdgeInsets.all(12), + child: Theme( + // Give the avatar a transparent background + data: theme.copyWith( + colorScheme: + theme.colorScheme.copyWith(background: Colors.transparent)), + child: child, + ), + ); + } +} + class _CurrentDeviceRow extends StatelessWidget { final DeviceNode node; final AsyncValue data; @@ -156,98 +184,68 @@ class _CurrentDeviceRow extends StatelessWidget { const _CurrentDeviceRow(this.node, this.data); @override - Widget build(BuildContext context) => data.when( - data: (data) { - final isNfc = data.node is NfcReaderNode; - return Column( - children: [ - DeviceAvatar.yubiKeyData( - data, - selected: true, - radius: 64, - ), - ListTile( - isThreeLine: isNfc, - title: Center(child: Text(data.name)), - subtitle: Column( - children: [ - Text(_getSubtitle(data.info)), - if (isNfc) Text(node.name), - ], - ), - //onTap: onTap, - ), - ], - ); - }, - error: (error, _) { - final String message; - switch (error) { - case 'unknown-device': - message = 'Unrecognized device'; - break; - default: - message = 'No YubiKey present'; - } - return Column( - children: [ - DeviceAvatar.deviceNode( - node, - selected: true, - radius: 64, - ), - ListTile( - title: Center(child: Text(message)), - subtitle: Center(child: Text(node.name)), - ), - ], - ); - }, - loading: () => Column( - children: [ - DeviceAvatar.deviceNode( - node, - selected: true, - radius: 64, - ), - ListTile( - title: Center(child: Text(node.name)), - subtitle: const Center(child: Text('Device inaccessible')), - ), - ], - ), - ); + Widget build(BuildContext context) { + final isNfc = node is NfcReaderNode; + final hero = data.maybeWhen( + data: (data) => DeviceAvatar.yubiKeyData(data, radius: 64), + orElse: () => DeviceAvatar.deviceNode(node, radius: 64), + ); + + final messages = data.whenOrNull( + data: (data) => [_getInfoString(data.info)], + error: (error, _) { + switch (error) { + case 'unknown-device': + return ['Unrecognized device']; + case 'device-inaccessible': + return ['Device inacessible']; + } + return null; + }, + ) ?? + ['No YubiKey present']; + + String name = + data.asData?.value.name ?? (isNfc ? messages.removeAt(0) : node.name); + if (isNfc) { + messages.add(node.name); + } + + return Column( + children: [ + _HeroAvatar(child: hero), + ListTile( + title: Text(name, textAlign: TextAlign.center), + isThreeLine: messages.length > 1, + subtitle: Text(messages.join('\n'), textAlign: TextAlign.center), + ) + ], + ); + } } class _DeviceRow extends ConsumerWidget { final DeviceNode node; final DeviceInfo? info; - const _DeviceRow( - this.node, { - this.info, - }); + const _DeviceRow(this.node, {this.info}); @override Widget build(BuildContext context, WidgetRef ref) { return ListTile( leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 4), - child: DeviceAvatar.deviceNode( - node, - radius: 20, - ), + child: DeviceAvatar.deviceNode(node), ), title: Text(node.name), subtitle: Text( node.when( usbYubiKey: (_, __, ___, info) => - info == null ? 'Device inaccessible' : _getSubtitle(info), + info == null ? 'Device inaccessible' : _getInfoString(info), nfcReader: (_, __) => 'Select to scan', ), ), onTap: () { - //Navigator.of(context).pop(); ref.read(currentDeviceProvider.notifier).setCurrentDevice(node); }, ); diff --git a/lib/desktop/devices.dart b/lib/desktop/devices.dart index ddec4c24..086e35f8 100755 --- a/lib/desktop/devices.dart +++ b/lib/desktop/devices.dart @@ -226,6 +226,8 @@ class CurrentDeviceDataNotifier extends StateNotifier> { final info = dev.info; if (info != null) { state = AsyncValue.data(YubiKeyData(dev, dev.name, info)); + } else { + state = const AsyncValue.error('device-inaccessible'); } } } From 38528c81aeaa77f1123c1a79a075c23e09c476e1 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 6 Jul 2022 15:22:15 +0200 Subject: [PATCH 26/38] Rename and add comments to utf8 utils. --- lib/fido/views/add_fingerprint_dialog.dart | 4 +-- lib/fido/views/rename_fingerprint_dialog.dart | 4 +-- lib/oath/views/add_account_page.dart | 6 ++-- lib/oath/views/rename_account_dialog.dart | 6 ++-- lib/oath/views/utils.dart | 6 ++-- lib/widgets/utf8_text_fields.dart | 21 -------------- lib/widgets/utf8_utils.dart | 29 +++++++++++++++++++ 7 files changed, 42 insertions(+), 34 deletions(-) delete mode 100755 lib/widgets/utf8_text_fields.dart create mode 100755 lib/widgets/utf8_utils.dart diff --git a/lib/fido/views/add_fingerprint_dialog.dart b/lib/fido/views/add_fingerprint_dialog.dart index d0618490..ae25a783 100755 --- a/lib/fido/views/add_fingerprint_dialog.dart +++ b/lib/fido/views/add_fingerprint_dialog.dart @@ -10,7 +10,7 @@ import 'package:yubico_authenticator/app/logging.dart'; import '../../app/message.dart'; import '../../desktop/models.dart'; import '../../widgets/responsive_dialog.dart'; -import '../../widgets/utf8_text_fields.dart'; +import '../../widgets/utf8_utils.dart'; import '../state.dart'; import '../../fido/models.dart'; import '../../app/models.dart'; @@ -181,7 +181,7 @@ class _AddFingerprintDialogState extends ConsumerState focusNode: _nameFocus, maxLength: 15, inputFormatters: [limitBytesLength(15)], - buildCounter: buildCountersFor(_label), + buildCounter: buildByteCounterFor(_label), autofocus: true, decoration: InputDecoration( enabled: _fingerprint != null, diff --git a/lib/fido/views/rename_fingerprint_dialog.dart b/lib/fido/views/rename_fingerprint_dialog.dart index a2f5f57b..200482f8 100755 --- a/lib/fido/views/rename_fingerprint_dialog.dart +++ b/lib/fido/views/rename_fingerprint_dialog.dart @@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../app/message.dart'; import '../../desktop/models.dart'; import '../../widgets/responsive_dialog.dart'; -import '../../widgets/utf8_text_fields.dart'; +import '../../widgets/utf8_utils.dart'; import '../models.dart'; import '../state.dart'; import '../../app/models.dart'; @@ -72,7 +72,7 @@ class _RenameAccountDialogState extends ConsumerState { initialValue: _label, maxLength: 15, inputFormatters: [limitBytesLength(15)], - buildCounter: buildCountersFor(_label), + buildCounter: buildByteCounterFor(_label), decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Label', diff --git a/lib/oath/views/add_account_page.dart b/lib/oath/views/add_account_page.dart index 101deb47..e5ff81e0 100755 --- a/lib/oath/views/add_account_page.dart +++ b/lib/oath/views/add_account_page.dart @@ -13,7 +13,7 @@ import '../../app/state.dart'; import '../../desktop/models.dart'; import '../../widgets/file_drop_target.dart'; import '../../widgets/responsive_dialog.dart'; -import '../../widgets/utf8_text_fields.dart'; +import '../../widgets/utf8_utils.dart'; import '../models.dart'; import '../state.dart'; import 'utils.dart'; @@ -212,7 +212,7 @@ class _OathAddAccountPageState extends ConsumerState { enabled: issuerRemaining > 0, maxLength: max(issuerRemaining, 1), inputFormatters: [limitBytesLength(issuerRemaining)], - buildCounter: buildCountersFor(_issuerController.text), + buildCounter: buildByteCounterFor(_issuerController.text), decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Issuer (optional)', @@ -232,7 +232,7 @@ class _OathAddAccountPageState extends ConsumerState { key: const Key('name'), controller: _accountController, maxLength: max(nameRemaining, 1), - buildCounter: buildCountersFor(_accountController.text), + buildCounter: buildByteCounterFor(_accountController.text), inputFormatters: [limitBytesLength(nameRemaining)], decoration: const InputDecoration( border: OutlineInputBorder(), diff --git a/lib/oath/views/rename_account_dialog.dart b/lib/oath/views/rename_account_dialog.dart index 21303dae..9053d0a0 100755 --- a/lib/oath/views/rename_account_dialog.dart +++ b/lib/oath/views/rename_account_dialog.dart @@ -7,7 +7,7 @@ import '../../app/message.dart'; import '../../app/models.dart'; import '../../desktop/models.dart'; import '../../widgets/responsive_dialog.dart'; -import '../../widgets/utf8_text_fields.dart'; +import '../../widgets/utf8_utils.dart'; import '../models.dart'; import '../state.dart'; import 'utils.dart'; @@ -98,7 +98,7 @@ class _RenameAccountDialogState extends ConsumerState { initialValue: _issuer, enabled: issuerRemaining > 0, maxLength: issuerRemaining > 0 ? issuerRemaining : null, - buildCounter: buildCountersFor(_issuer), + buildCounter: buildByteCounterFor(_issuer), inputFormatters: [limitBytesLength(issuerRemaining)], decoration: const InputDecoration( border: OutlineInputBorder(), @@ -116,7 +116,7 @@ class _RenameAccountDialogState extends ConsumerState { initialValue: _account, maxLength: nameRemaining, inputFormatters: [limitBytesLength(nameRemaining)], - buildCounter: buildCountersFor(_account), + buildCounter: buildByteCounterFor(_account), decoration: InputDecoration( border: const OutlineInputBorder(), labelText: 'Account name', diff --git a/lib/oath/views/utils.dart b/lib/oath/views/utils.dart index 62634c4d..e4c4a32c 100755 --- a/lib/oath/views/utils.dart +++ b/lib/oath/views/utils.dart @@ -1,6 +1,6 @@ -import 'dart:convert'; import 'dart:math'; +import '../../widgets/utf8_utils.dart'; import '../models.dart'; import '../../core/models.dart'; @@ -19,7 +19,7 @@ Pair getRemainingKeySpace( // Non-standard TOTP periods are stored as part of this data, as a "D/"- prefix. remaining -= '$period/'.length; } - int issuerSpace = utf8.encode(issuer).length; + int issuerSpace = byteLength(issuer); if (issuer.isNotEmpty) { // Issuer is separated from name with a ":", if present. issuerSpace += 1; @@ -27,7 +27,7 @@ Pair getRemainingKeySpace( return Pair( // Always reserve at least one character for name - remaining - 1 - max(utf8.encode(name).length, 1), + remaining - 1 - max(byteLength(name), 1), remaining - issuerSpace, ); } diff --git a/lib/widgets/utf8_text_fields.dart b/lib/widgets/utf8_text_fields.dart deleted file mode 100755 index d6ccdb06..00000000 --- a/lib/widgets/utf8_text_fields.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -int _byteLength(String value) => utf8.encode(value).length; - -InputCounterWidgetBuilder buildCountersFor(String currentValue) => - (context, {required currentLength, required isFocused, maxLength}) => Text( - maxLength != null ? '${_byteLength(currentValue)}/$maxLength' : '', - style: Theme.of(context).textTheme.caption, - ); - -TextInputFormatter limitBytesLength(int maxByteLength) => - TextInputFormatter.withFunction((oldValue, newValue) { - final newLength = _byteLength(newValue.text); - if (newLength <= maxByteLength) { - return newValue; - } - return oldValue; - }); diff --git a/lib/widgets/utf8_utils.dart b/lib/widgets/utf8_utils.dart new file mode 100755 index 00000000..336b34ef --- /dev/null +++ b/lib/widgets/utf8_utils.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// Get the number of bytes used by a String when encoded to UTF-8. +int byteLength(String value) => utf8.encode(value).length; + +/// Builds a counter widget showing number of bytes used/available. +/// +/// Set this as a [TextField.buildCounter] callback to show the number of bytes +/// used rather than number of characters. [currentValue] should always match +/// the input text value to measure. +InputCounterWidgetBuilder buildByteCounterFor(String currentValue) => + (context, {required currentLength, required isFocused, maxLength}) => Text( + maxLength != null ? '${byteLength(currentValue)}/$maxLength' : '', + style: Theme.of(context).textTheme.caption, + ); + +/// Limits the input in length based on the byte length when encoded. +/// This is generally used together with [buildByteCounterFor]. +TextInputFormatter limitBytesLength(int maxByteLength) => + TextInputFormatter.withFunction((oldValue, newValue) { + final newLength = byteLength(newValue.text); + if (newLength <= maxByteLength) { + return newValue; + } + return oldValue; + }); From f81d2d06bc1ce514fd0d56fb1f7efbae022b4474 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 6 Jul 2022 16:56:34 +0200 Subject: [PATCH 27/38] Add workaround for device picker losing focus on device change. --- lib/app/views/device_picker_dialog.dart | 41 ++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/app/views/device_picker_dialog.dart b/lib/app/views/device_picker_dialog.dart index 27bcb78b..6ec53689 100755 --- a/lib/app/views/device_picker_dialog.dart +++ b/lib/app/views/device_picker_dialog.dart @@ -40,9 +40,48 @@ class _HiddenDevicesNotifier extends StateNotifier> { } } -class DevicePickerDialog extends ConsumerWidget { +class DevicePickerDialog extends StatefulWidget { const DevicePickerDialog({super.key}); + @override + State createState() => _DevicePickerDialogState(); +} + +class _DevicePickerDialogState extends State { + late FocusScopeNode _focus; + + @override + void initState() { + super.initState(); + _focus = FocusScopeNode(); + } + + @override + void dispose() { + _focus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // This keeps the focus in the dialog, even if the underlying page + // changes as it does when a new device is selected. + return FocusScope( + node: _focus, + autofocus: true, + onFocusChange: (focused) { + if (!focused) { + _focus.requestFocus(); + } + }, + child: const _DevicePickerContent(), + ); + } +} + +class _DevicePickerContent extends ConsumerWidget { + const _DevicePickerContent(); + @override Widget build(BuildContext context, WidgetRef ref) { final hidden = ref.watch(_hiddenDevicesProvider); From c00a16bbea1372b0ae603a920deaa5619e446bc4 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Thu, 7 Jul 2022 10:15:57 +0200 Subject: [PATCH 28/38] Update Python dependencies. --- helper/poetry.lock | 264 +++++++++++++++++++++++++-------------------- 1 file changed, 149 insertions(+), 115 deletions(-) diff --git a/helper/poetry.lock b/helper/poetry.lock index f3d28ffb..f7dc9362 100755 --- a/helper/poetry.lock +++ b/helper/poetry.lock @@ -30,7 +30,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> [[package]] name = "cffi" -version = "1.15.0" +version = "1.15.1" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -60,7 +60,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "cryptography" -version = "37.0.3" +version = "37.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -204,14 +204,14 @@ future = "*" [[package]] name = "pillow" -version = "9.1.1" +version = "9.2.0" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] [[package]] @@ -370,7 +370,7 @@ pywin32 = {version = ">=223", markers = "sys_platform == \"win32\""} type = "git" url = "https://github.com/Yubico/yubikey-manager.git" reference = "next" -resolved_reference = "ef70133ebbc97809a7abc89b593078268c47189a" +resolved_reference = "32612d177db0d8dd768679ce26c4e509d10f2a97" [[package]] name = "zipp" @@ -414,56 +414,70 @@ attrs = [ {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] cffi = [ - {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, - {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, - {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, - {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, - {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, - {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, - {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, - {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, - {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, - {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, - {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, - {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, - {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, - {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, - {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, - {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, - {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, @@ -474,28 +488,28 @@ colorama = [ {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] cryptography = [ - {file = "cryptography-37.0.3-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:d10413d493e98075060d3e62e5826de372912ea653ccc948f3c41b21ddca087f"}, - {file = "cryptography-37.0.3-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:cd64147ff16506632893ceb2569624b48c84daa3ba4d89695f7c7bc24188eee9"}, - {file = "cryptography-37.0.3-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:17c74f7d9e9e9bb7e84521243695c1b4bdc3a0e44ca764e6bcf8f05f3de3d0df"}, - {file = "cryptography-37.0.3-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:0713bee6c8077786c56bdec9c5d3f099d40d2c862ff3200416f6862e9dd63156"}, - {file = "cryptography-37.0.3-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9c2008417741cdfbe945ef2d16b7b7ba0790886a0b49e1de533acf93eb66ed6"}, - {file = "cryptography-37.0.3-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646905ff7a712e415bf0d0f214e0eb669dd2257c4d7a27db1e8baec5d2a1d55f"}, - {file = "cryptography-37.0.3-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:dcafadb5a06cb7a6bb49fb4c1de7414ee2f8c8e12b047606d97c3175d690f582"}, - {file = "cryptography-37.0.3-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0b4bfc5ccfe4e5c7de535670680398fed4a0bbc5dfd52b3a295baad42230abdf"}, - {file = "cryptography-37.0.3-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a03dbc0d8ce8c1146c177cd0e3a66ea106f36733fb1b997ea4d051f8a68539ff"}, - {file = "cryptography-37.0.3-cp36-abi3-win32.whl", hash = "sha256:190a24c14e91c1fa3101069aac7e77d11c5a73911c3904128367f52946bbb6fd"}, - {file = "cryptography-37.0.3-cp36-abi3-win_amd64.whl", hash = "sha256:b05c5478524deb7a019e240f2a970040c4b0f01f58f0425e6262c96b126c6a3e"}, - {file = "cryptography-37.0.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891ed8312840fd43e0696468a6520a582a033c0109f7b14b96067bfe1123226b"}, - {file = "cryptography-37.0.3-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:30d6aabf623a01affc7c0824936c3dde6590076b61f5dd299df3cc2c75fc5915"}, - {file = "cryptography-37.0.3-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:31a7c1f1c2551f013d4294d06e22848e2ccd77825f0987cba3239df6ebf7b020"}, - {file = "cryptography-37.0.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a94fd1ff80001cb97add71d07f596d8b865b716f25ef501183e0e199390e50d3"}, - {file = "cryptography-37.0.3-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:8a85dbcc770256918b40c2f40bd3ffd3b2ae45b0cf19068b561db8f8d61bf492"}, - {file = "cryptography-37.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:773d5b5f2e2bd2c7cbb1bd24902ad41283c88b9dd463a0f82adc9a2870d9d066"}, - {file = "cryptography-37.0.3-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0f9193428a55a4347af2d4fd8141a2002dedbcc26487e67fd2ae19f977ee8afc"}, - {file = "cryptography-37.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bf652c73e8f7c32a3f92f7184bf7f9106dacdf5ef59c3c3683d7dae2c4972fb"}, - {file = "cryptography-37.0.3-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:c3c8b1ad2c266fdf7adc041cc4156d6a3d14db93de2f81b26a5af97ef3f209e5"}, - {file = "cryptography-37.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2383d6c3088e863304c37c65cd2ea404b7fbb4886823eab1d74137cc27f3d2ee"}, - {file = "cryptography-37.0.3.tar.gz", hash = "sha256:ae430d51c67ac638dfbb42edf56c669ca9c74744f4d225ad11c6f3d355858187"}, + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"}, + {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"}, + {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, + {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, ] fido2 = [ {file = "fido2-1.0.0-py3-none-any.whl", hash = "sha256:dce13d739b8e0df30505b33f5fd2868fad20f3b309acacce72e5f2d1b0c58761"}, @@ -560,44 +574,64 @@ pefile = [ {file = "pefile-2022.5.30.tar.gz", hash = "sha256:a5488a3dd1fd021ce33f969780b88fe0f7eebb76eb20996d7318f307612a045b"}, ] pillow = [ - {file = "Pillow-9.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe"}, - {file = "Pillow-9.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e"}, - {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602"}, - {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530"}, - {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2"}, - {file = "Pillow-9.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a"}, - {file = "Pillow-9.1.1-cp310-cp310-win32.whl", hash = "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c"}, - {file = "Pillow-9.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108"}, - {file = "Pillow-9.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b"}, - {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d"}, - {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84"}, - {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4"}, - {file = "Pillow-9.1.1-cp37-cp37m-win32.whl", hash = "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578"}, - {file = "Pillow-9.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9"}, - {file = "Pillow-9.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf"}, - {file = "Pillow-9.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b"}, - {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546"}, - {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f"}, - {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a"}, - {file = "Pillow-9.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1"}, - {file = "Pillow-9.1.1-cp38-cp38-win32.whl", hash = "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54"}, - {file = "Pillow-9.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf"}, - {file = "Pillow-9.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92"}, - {file = "Pillow-9.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a"}, - {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251"}, - {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d"}, - {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1"}, - {file = "Pillow-9.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601"}, - {file = "Pillow-9.1.1-cp39-cp39-win32.whl", hash = "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45"}, - {file = "Pillow-9.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c"}, - {file = "Pillow-9.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6"}, - {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098"}, - {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8"}, - {file = "Pillow-9.1.1.tar.gz", hash = "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0"}, + {file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"}, + {file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"}, + {file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"}, + {file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"}, + {file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"}, + {file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8"}, + {file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"}, + {file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"}, + {file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"}, + {file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"}, + {file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"}, + {file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"}, + {file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"}, + {file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"}, + {file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"}, + {file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"}, + {file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"}, + {file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"}, + {file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"}, + {file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"}, + {file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"}, + {file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"}, + {file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"}, + {file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, From b41e4bc5715fe08f0be13fe7f4dfebad771f3327 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Thu, 7 Jul 2022 10:18:43 +0200 Subject: [PATCH 29/38] Update Flutter dependencies. --- pubspec.lock | 30 +++++++++++++++--------------- pubspec.yaml | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) mode change 100644 => 100755 pubspec.lock diff --git a/pubspec.lock b/pubspec.lock old mode 100644 new mode 100755 index 74db5575..a44e9fe9 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "38.0.0" + version: "41.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "3.4.1" + version: "4.2.0" archive: dependency: transitive description: @@ -98,7 +98,7 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.3.2" + version: "8.3.3" characters: dependency: transitive description: @@ -189,7 +189,7 @@ packages: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" file: dependency: transitive description: @@ -244,7 +244,7 @@ packages: name: freezed url: "https://pub.dartlang.org" source: hosted - version: "2.0.3+1" + version: "2.0.4" freezed_annotation: dependency: "direct main" description: @@ -270,7 +270,7 @@ packages: name: glob url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.1.0" graphs: dependency: transitive description: @@ -284,7 +284,7 @@ packages: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "3.2.0" + version: "3.2.1" http_parser: dependency: transitive description: @@ -373,7 +373,7 @@ packages: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.1.0" path: dependency: transitive description: @@ -408,7 +408,7 @@ packages: name: pigeon url: "https://pub.dartlang.org" source: hosted - version: "3.1.6" + version: "3.2.3" platform: dependency: transitive description: @@ -429,7 +429,7 @@ packages: name: pool url: "https://pub.dartlang.org" source: hosted - version: "1.5.0" + version: "1.5.1" process: dependency: transitive description: @@ -534,14 +534,14 @@ packages: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" sky_engine: dependency: transitive description: flutter @@ -644,7 +644,7 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.1.3" + version: "6.1.4" url_launcher_android: dependency: transitive description: @@ -679,14 +679,14 @@ packages: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.1.0" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.12" url_launcher_windows: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 00773466..0057c827 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,7 +43,7 @@ dependencies: collection: ^1.16.0 shared_preferences: ^2.0.12 flutter_riverpod: ^1.0.0 - json_annotation: ^4.4.0 + json_annotation: ^4.5.0 freezed_annotation: ^2.0.3 window_manager: ^0.2.0 qrscanner_zxing: From beec1224ffa3a6ba9f1d260a43c811194d3a2262 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Thu, 7 Jul 2022 10:29:42 +0200 Subject: [PATCH 30/38] Rebuild freezed classes. --- lib/app/models.freezed.dart | 30 ++++++++--------- lib/core/models.freezed.dart | 10 +++--- lib/desktop/models.freezed.dart | 16 ++++----- lib/fido/models.freezed.dart | 26 +++++++-------- lib/management/models.freezed.dart | 26 +++++++-------- lib/oath/models.freezed.dart | 52 +++++++++++++++--------------- 6 files changed, 79 insertions(+), 81 deletions(-) diff --git a/lib/app/models.freezed.dart b/lib/app/models.freezed.dart index 1978364e..5f34e648 100755 --- a/lib/app/models.freezed.dart +++ b/lib/app/models.freezed.dart @@ -175,11 +175,11 @@ abstract class _YubiKeyData implements YubiKeyData { _$_YubiKeyData; @override - DeviceNode get node => throw _privateConstructorUsedError; + DeviceNode get node; @override - String get name => throw _privateConstructorUsedError; + String get name; @override - DeviceInfo get info => throw _privateConstructorUsedError; + DeviceInfo get info; @override @JsonKey(ignore: true) _$$_YubiKeyDataCopyWith<_$_YubiKeyData> get copyWith => @@ -454,11 +454,11 @@ abstract class UsbYubiKeyNode extends DeviceNode { UsbYubiKeyNode._() : super._(); @override - DevicePath get path => throw _privateConstructorUsedError; + DevicePath get path; @override - String get name => throw _privateConstructorUsedError; - UsbPid get pid => throw _privateConstructorUsedError; - DeviceInfo? get info => throw _privateConstructorUsedError; + String get name; + UsbPid get pid; + DeviceInfo? get info; @override @JsonKey(ignore: true) _$$UsbYubiKeyNodeCopyWith<_$UsbYubiKeyNode> get copyWith => @@ -613,9 +613,9 @@ abstract class NfcReaderNode extends DeviceNode { NfcReaderNode._() : super._(); @override - DevicePath get path => throw _privateConstructorUsedError; + DevicePath get path; @override - String get name => throw _privateConstructorUsedError; + String get name; @override @JsonKey(ignore: true) _$$NfcReaderNodeCopyWith<_$NfcReaderNode> get copyWith => @@ -762,11 +762,11 @@ abstract class _MenuAction implements MenuAction { final void Function(BuildContext)? action}) = _$_MenuAction; @override - String get text => throw _privateConstructorUsedError; + String get text; @override - Widget get icon => throw _privateConstructorUsedError; + Widget get icon; @override - void Function(BuildContext)? get action => throw _privateConstructorUsedError; + void Function(BuildContext)? get action; @override @JsonKey(ignore: true) _$$_MenuActionCopyWith<_$_MenuAction> get copyWith => @@ -914,11 +914,11 @@ abstract class _WindowState implements WindowState { required final bool active}) = _$_WindowState; @override - bool get focused => throw _privateConstructorUsedError; + bool get focused; @override - bool get visible => throw _privateConstructorUsedError; + bool get visible; @override - bool get active => throw _privateConstructorUsedError; + bool get active; @override @JsonKey(ignore: true) _$$_WindowStateCopyWith<_$_WindowState> get copyWith => diff --git a/lib/core/models.freezed.dart b/lib/core/models.freezed.dart index 91e5f4ac..dce8a62d 100755 --- a/lib/core/models.freezed.dart +++ b/lib/core/models.freezed.dart @@ -151,11 +151,11 @@ abstract class _Version extends Version { const _Version._() : super._(); @override - int get major => throw _privateConstructorUsedError; + int get major; @override - int get minor => throw _privateConstructorUsedError; + int get minor; @override - int get patch => throw _privateConstructorUsedError; + int get patch; @override @JsonKey(ignore: true) _$$_VersionCopyWith<_$_Version> get copyWith => @@ -284,9 +284,9 @@ abstract class _Pair implements Pair { factory _Pair(final T1 first, final T2 second) = _$_Pair; @override - T1 get first => throw _privateConstructorUsedError; + T1 get first; @override - T2 get second => throw _privateConstructorUsedError; + T2 get second; @override @JsonKey(ignore: true) _$$_PairCopyWith> get copyWith => diff --git a/lib/desktop/models.freezed.dart b/lib/desktop/models.freezed.dart index 2108f3d8..cfdb7ee5 100755 --- a/lib/desktop/models.freezed.dart +++ b/lib/desktop/models.freezed.dart @@ -272,7 +272,7 @@ abstract class Success implements RpcResponse { factory Success.fromJson(Map json) = _$Success.fromJson; @override - Map get body => throw _privateConstructorUsedError; + Map get body; @override @JsonKey(ignore: true) _$$SuccessCopyWith<_$Success> get copyWith => @@ -446,9 +446,9 @@ abstract class Signal implements RpcResponse { factory Signal.fromJson(Map json) = _$Signal.fromJson; - String get status => throw _privateConstructorUsedError; + String get status; @override - Map get body => throw _privateConstructorUsedError; + Map get body; @override @JsonKey(ignore: true) _$$SignalCopyWith<_$Signal> get copyWith => @@ -633,10 +633,10 @@ abstract class RpcError implements RpcResponse { factory RpcError.fromJson(Map json) = _$RpcError.fromJson; - String get status => throw _privateConstructorUsedError; - String get message => throw _privateConstructorUsedError; + String get status; + String get message; @override - Map get body => throw _privateConstructorUsedError; + Map get body; @override @JsonKey(ignore: true) _$$RpcErrorCopyWith<_$RpcError> get copyWith => @@ -780,9 +780,9 @@ abstract class _RpcState implements RpcState { factory _RpcState.fromJson(Map json) = _$_RpcState.fromJson; @override - String get version => throw _privateConstructorUsedError; + String get version; @override - bool get isAdmin => throw _privateConstructorUsedError; + bool get isAdmin; @override @JsonKey(ignore: true) _$$_RpcStateCopyWith<_$_RpcState> get copyWith => diff --git a/lib/fido/models.freezed.dart b/lib/fido/models.freezed.dart index aa9bc85b..b573ef00 100755 --- a/lib/fido/models.freezed.dart +++ b/lib/fido/models.freezed.dart @@ -162,9 +162,9 @@ abstract class _FidoState extends FidoState { _$_FidoState.fromJson; @override - Map get info => throw _privateConstructorUsedError; + Map get info; @override - bool get unlocked => throw _privateConstructorUsedError; + bool get unlocked; @override @JsonKey(ignore: true) _$$_FidoStateCopyWith<_$_FidoState> get copyWith => @@ -471,8 +471,8 @@ abstract class _PinFailure implements PinResult { factory _PinFailure(final int retries, final bool authBlocked) = _$_PinFailure; - int get retries => throw _privateConstructorUsedError; - bool get authBlocked => throw _privateConstructorUsedError; + int get retries; + bool get authBlocked; @JsonKey(ignore: true) _$$_PinFailureCopyWith<_$_PinFailure> get copyWith => throw _privateConstructorUsedError; @@ -620,9 +620,9 @@ abstract class _Fingerprint extends Fingerprint { _$_Fingerprint.fromJson; @override - String get templateId => throw _privateConstructorUsedError; + String get templateId; @override - String? get name => throw _privateConstructorUsedError; + String? get name; @override @JsonKey(ignore: true) _$$_FingerprintCopyWith<_$_Fingerprint> get copyWith => @@ -828,7 +828,7 @@ class _$_EventCapture implements _EventCapture { abstract class _EventCapture implements FingerprintEvent { factory _EventCapture(final int remaining) = _$_EventCapture; - int get remaining => throw _privateConstructorUsedError; + int get remaining; @JsonKey(ignore: true) _$$_EventCaptureCopyWith<_$_EventCapture> get copyWith => throw _privateConstructorUsedError; @@ -978,7 +978,7 @@ class _$_EventComplete implements _EventComplete { abstract class _EventComplete implements FingerprintEvent { factory _EventComplete(final Fingerprint fingerprint) = _$_EventComplete; - Fingerprint get fingerprint => throw _privateConstructorUsedError; + Fingerprint get fingerprint; @JsonKey(ignore: true) _$$_EventCompleteCopyWith<_$_EventComplete> get copyWith => throw _privateConstructorUsedError; @@ -1118,7 +1118,7 @@ class _$_EventError implements _EventError { abstract class _EventError implements FingerprintEvent { factory _EventError(final int code) = _$_EventError; - int get code => throw _privateConstructorUsedError; + int get code; @JsonKey(ignore: true) _$$_EventErrorCopyWith<_$_EventError> get copyWith => throw _privateConstructorUsedError; @@ -1304,13 +1304,13 @@ abstract class _FidoCredential implements FidoCredential { _$_FidoCredential.fromJson; @override - String get rpId => throw _privateConstructorUsedError; + String get rpId; @override - String get credentialId => throw _privateConstructorUsedError; + String get credentialId; @override - String get userId => throw _privateConstructorUsedError; + String get userId; @override - String get userName => throw _privateConstructorUsedError; + String get userName; @override @JsonKey(ignore: true) _$$_FidoCredentialCopyWith<_$_FidoCredential> get copyWith => diff --git a/lib/management/models.freezed.dart b/lib/management/models.freezed.dart index 63a32b19..e6b5b7e0 100755 --- a/lib/management/models.freezed.dart +++ b/lib/management/models.freezed.dart @@ -208,14 +208,13 @@ abstract class _DeviceConfig implements DeviceConfig { _$_DeviceConfig.fromJson; @override - Map get enabledCapabilities => - throw _privateConstructorUsedError; + Map get enabledCapabilities; @override - int? get autoEjectTimeout => throw _privateConstructorUsedError; + int? get autoEjectTimeout; @override - int? get challengeResponseTimeout => throw _privateConstructorUsedError; + int? get challengeResponseTimeout; @override - int? get deviceFlags => throw _privateConstructorUsedError; + int? get deviceFlags; @override @JsonKey(ignore: true) _$$_DeviceConfigCopyWith<_$_DeviceConfig> get copyWith => @@ -514,22 +513,21 @@ abstract class _DeviceInfo implements DeviceInfo { _$_DeviceInfo.fromJson; @override - DeviceConfig get config => throw _privateConstructorUsedError; + DeviceConfig get config; @override - int? get serial => throw _privateConstructorUsedError; + int? get serial; @override - Version get version => throw _privateConstructorUsedError; + Version get version; @override - FormFactor get formFactor => throw _privateConstructorUsedError; + FormFactor get formFactor; @override - Map get supportedCapabilities => - throw _privateConstructorUsedError; + Map get supportedCapabilities; @override - bool get isLocked => throw _privateConstructorUsedError; + bool get isLocked; @override - bool get isFips => throw _privateConstructorUsedError; + bool get isFips; @override - bool get isSky => throw _privateConstructorUsedError; + bool get isSky; @override @JsonKey(ignore: true) _$$_DeviceInfoCopyWith<_$_DeviceInfo> get copyWith => diff --git a/lib/oath/models.freezed.dart b/lib/oath/models.freezed.dart index d10ecc25..4d4d94f4 100755 --- a/lib/oath/models.freezed.dart +++ b/lib/oath/models.freezed.dart @@ -253,19 +253,19 @@ abstract class _OathCredential implements OathCredential { _$_OathCredential.fromJson; @override - String get deviceId => throw _privateConstructorUsedError; + String get deviceId; @override - String get id => throw _privateConstructorUsedError; + String get id; @override - String? get issuer => throw _privateConstructorUsedError; + String? get issuer; @override - String get name => throw _privateConstructorUsedError; + String get name; @override - OathType get oathType => throw _privateConstructorUsedError; + OathType get oathType; @override - int get period => throw _privateConstructorUsedError; + int get period; @override - bool get touchRequired => throw _privateConstructorUsedError; + bool get touchRequired; @override @JsonKey(ignore: true) _$$_OathCredentialCopyWith<_$_OathCredential> get copyWith => @@ -424,11 +424,11 @@ abstract class _OathCode implements OathCode { factory _OathCode.fromJson(Map json) = _$_OathCode.fromJson; @override - String get value => throw _privateConstructorUsedError; + String get value; @override - int get validFrom => throw _privateConstructorUsedError; + int get validFrom; @override - int get validTo => throw _privateConstructorUsedError; + int get validTo; @override @JsonKey(ignore: true) _$$_OathCodeCopyWith<_$_OathCode> get copyWith => @@ -599,9 +599,9 @@ abstract class _OathPair implements OathPair { factory _OathPair.fromJson(Map json) = _$_OathPair.fromJson; @override - OathCredential get credential => throw _privateConstructorUsedError; + OathCredential get credential; @override - OathCode? get code => throw _privateConstructorUsedError; + OathCode? get code; @override @JsonKey(ignore: true) _$$_OathPairCopyWith<_$_OathPair> get copyWith => @@ -838,17 +838,17 @@ abstract class _OathState implements OathState { _$_OathState.fromJson; @override - String get deviceId => throw _privateConstructorUsedError; + String get deviceId; @override - Version get version => throw _privateConstructorUsedError; + Version get version; @override - bool get hasKey => throw _privateConstructorUsedError; + bool get hasKey; @override - bool get remembered => throw _privateConstructorUsedError; + bool get remembered; @override - bool get locked => throw _privateConstructorUsedError; + bool get locked; @override - KeystoreState get keystore => throw _privateConstructorUsedError; + KeystoreState get keystore; @override @JsonKey(ignore: true) _$$_OathStateCopyWith<_$_OathState> get copyWith => @@ -1126,21 +1126,21 @@ abstract class _CredentialData extends CredentialData { _$_CredentialData.fromJson; @override - String? get issuer => throw _privateConstructorUsedError; + String? get issuer; @override - String get name => throw _privateConstructorUsedError; + String get name; @override - String get secret => throw _privateConstructorUsedError; + String get secret; @override - OathType get oathType => throw _privateConstructorUsedError; + OathType get oathType; @override - HashAlgorithm get hashAlgorithm => throw _privateConstructorUsedError; + HashAlgorithm get hashAlgorithm; @override - int get digits => throw _privateConstructorUsedError; + int get digits; @override - int get period => throw _privateConstructorUsedError; + int get period; @override - int get counter => throw _privateConstructorUsedError; + int get counter; @override @JsonKey(ignore: true) _$$_CredentialDataCopyWith<_$_CredentialData> get copyWith => From 45e068e2e55aadbb619b37206c082e6445cba9c5 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Thu, 7 Jul 2022 11:30:34 +0200 Subject: [PATCH 31/38] Fix keyboard focus issue in OATH dialog. --- lib/oath/views/account_dialog.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/oath/views/account_dialog.dart b/lib/oath/views/account_dialog.dart index 96b1a243..17c96b48 100755 --- a/lib/oath/views/account_dialog.dart +++ b/lib/oath/views/account_dialog.dart @@ -120,7 +120,7 @@ class AccountDialog extends ConsumerWidget with AccountMixin { return null; }), }, - child: Focus( + child: FocusScope( autofocus: true, child: AlertDialog( title: Center( From 315534ecc2b18e7e4ceedc99c86ca8ba0ea38444 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Thu, 7 Jul 2022 11:31:22 +0200 Subject: [PATCH 32/38] Fix naming. --- lib/core/models.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/models.dart b/lib/core/models.dart index 20ff46f6..4c1d575c 100644 --- a/lib/core/models.dart +++ b/lib/core/models.dart @@ -88,7 +88,7 @@ enum UsbPid { final suffix = UsbInterface.values .where((e) => e.value & usbInterfaces != 0) .map((e) => e.name.toUpperCase()) - .join(' '); + .join('+'); return '$prefix $suffix'; } } From c505eb4586ea4a44d8a8b10a279246a01abba288 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Thu, 7 Jul 2022 12:40:54 +0200 Subject: [PATCH 33/38] Simplify custom Focus ordering. --- lib/oath/views/account_list.dart | 93 +++++++------------------------- lib/oath/views/account_view.dart | 8 +-- lib/oath/views/oath_screen.dart | 75 +++++++++++++------------- 3 files changed, 57 insertions(+), 119 deletions(-) diff --git a/lib/oath/views/account_list.dart b/lib/oath/views/account_list.dart index a633ef08..717d4c48 100755 --- a/lib/oath/views/account_list.dart +++ b/lib/oath/views/account_list.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../app/models.dart'; @@ -8,65 +7,14 @@ import '../models.dart'; import '../state.dart'; import 'account_view.dart'; -class AccountList extends ConsumerStatefulWidget { +class AccountList extends ConsumerWidget { final DevicePath devicePath; final OathState oathState; const AccountList(this.devicePath, this.oathState, {super.key}); @override - ConsumerState createState() => _AccountListState(); -} - -class _AccountListState extends ConsumerState { - List _credentials = []; - Map _focusNodes = {}; - - @override - void dispose() { - super.dispose(); - for (var e in _focusNodes.values) { - e.dispose(); - } - _focusNodes.clear(); - } - - void _updateFocusNodes() { - _focusNodes = { - for (var cred in _credentials) - cred: _focusNodes[cred] ?? - FocusNode( - onKeyEvent: (node, event) { - if (event is KeyDownEvent) { - int index = -1; - ScrollPositionAlignmentPolicy policy = - ScrollPositionAlignmentPolicy.explicit; - if (event.logicalKey == LogicalKeyboardKey.arrowDown) { - index = _credentials.indexOf(cred) + 1; - policy = ScrollPositionAlignmentPolicy.keepVisibleAtEnd; - } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { - index = _credentials.indexOf(cred) - 1; - policy = ScrollPositionAlignmentPolicy.keepVisibleAtStart; - } - if (index >= 0 && index < _credentials.length) { - final targetNode = _focusNodes[_credentials[index]]!; - targetNode.requestFocus(); - Scrollable.ensureVisible( - targetNode.context!, - alignmentPolicy: policy, - ); - return KeyEventResult.handled; - } - } - return KeyEventResult.ignored; - }, - ) - }; - _focusNodes.removeWhere((cred, _) => !_credentials.contains(cred)); - } - - @override - Widget build(BuildContext context) { - final accounts = ref.watch(credentialListProvider(widget.devicePath)); + Widget build(BuildContext context, WidgetRef ref) { + final accounts = ref.watch(credentialListProvider(devicePath)); if (accounts == null) { return Column( mainAxisAlignment: MainAxisAlignment.center, @@ -88,27 +36,24 @@ class _AccountListState extends ConsumerState { final creds = credentials.where((entry) => !favorites.contains(entry.credential.id)); - _credentials = - pinnedCreds.followedBy(creds).map((e) => e.credential).toList(); - _updateFocusNodes(); - - return Column( - children: [ - if (pinnedCreds.isNotEmpty) const ListTitle('Pinned'), - ...pinnedCreds.map( - (entry) => AccountView( - entry.credential, - focusNode: _focusNodes[entry.credential], + return FocusTraversalGroup( + policy: WidgetOrderTraversalPolicy(), + child: Column( + children: [ + if (pinnedCreds.isNotEmpty) const ListTitle('Pinned'), + ...pinnedCreds.map( + (entry) => AccountView( + entry.credential, + ), ), - ), - if (creds.isNotEmpty) const ListTitle('Accounts'), - ...creds.map( - (entry) => AccountView( - entry.credential, - focusNode: _focusNodes[entry.credential], + if (creds.isNotEmpty) const ListTitle('Accounts'), + ...creds.map( + (entry) => AccountView( + entry.credential, + ), ), - ), - ], + ], + ), ); } } diff --git a/lib/oath/views/account_view.dart b/lib/oath/views/account_view.dart index 287bff55..6ea7d010 100755 --- a/lib/oath/views/account_view.dart +++ b/lib/oath/views/account_view.dart @@ -14,8 +14,7 @@ import 'account_mixin.dart'; class AccountView extends ConsumerWidget with AccountMixin { @override final OathCredential credential; - final FocusNode? focusNode; - AccountView(this.credential, {super.key, this.focusNode}); + AccountView(this.credential, {super.key}); Color _iconColor(int shade) { final colors = [ @@ -117,13 +116,10 @@ class AccountView extends ConsumerWidget with AccountMixin { return ListTile( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), - focusNode: focusNode, onTap: () { showBlurDialog( context: context, - builder: (context) { - return AccountDialog(credential); - }, + builder: (context) => AccountDialog(credential), ); }, onLongPress: triggerCopy, diff --git a/lib/oath/views/oath_screen.dart b/lib/oath/views/oath_screen.dart index d26a344f..81d51053 100755 --- a/lib/oath/views/oath_screen.dart +++ b/lib/oath/views/oath_screen.dart @@ -143,47 +143,44 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { return null; }), }, - child: Focus( - autofocus: true, - child: AppPage( - title: Focus( - canRequestFocus: false, - onKeyEvent: (node, event) { - if (event.logicalKey == LogicalKeyboardKey.arrowDown) { - node.focusInDirection(TraversalDirection.down); - return KeyEventResult.handled; - } - return KeyEventResult.ignored; - }, - child: Builder(builder: (context) { - return TextFormField( - key: const Key('search_accounts'), - controller: searchController, - focusNode: searchFocus, - style: Theme.of(context).textTheme.titleSmall, - decoration: const InputDecoration( - hintText: 'Search accounts', - isDense: true, - prefixIcon: Icon(Icons.search_outlined), - prefixIconConstraints: BoxConstraints( - minHeight: 30, - minWidth: 30, - ), - border: InputBorder.none, + child: AppPage( + title: Focus( + canRequestFocus: false, + onKeyEvent: (node, event) { + if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + node.focusInDirection(TraversalDirection.down); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: Builder(builder: (context) { + return TextFormField( + key: const Key('search_accounts'), + controller: searchController, + focusNode: searchFocus, + style: Theme.of(context).textTheme.titleSmall, + decoration: const InputDecoration( + hintText: 'Search accounts', + isDense: true, + prefixIcon: Icon(Icons.search_outlined), + prefixIconConstraints: BoxConstraints( + minHeight: 30, + minWidth: 30, ), - onChanged: (value) { - ref.read(searchProvider.notifier).setFilter(value); - }, - textInputAction: TextInputAction.next, - onFieldSubmitted: (value) { - Focus.of(context).focusInDirection(TraversalDirection.down); - }, - ); - }), - ), - actions: _buildActions(context, false), - child: AccountList(widget.devicePath, widget.oathState), + border: InputBorder.none, + ), + onChanged: (value) { + ref.read(searchProvider.notifier).setFilter(value); + }, + textInputAction: TextInputAction.next, + onFieldSubmitted: (value) { + Focus.of(context).focusInDirection(TraversalDirection.down); + }, + ); + }), ), + actions: _buildActions(context, false), + child: AccountList(widget.devicePath, widget.oathState), ), ); } From 5605df31fba77d520b69ec151b554f5fd875122f Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Thu, 7 Jul 2022 13:45:39 +0200 Subject: [PATCH 34/38] Show keyboard shortcut for copy in action name. --- lib/oath/views/account_mixin.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/oath/views/account_mixin.dart b/lib/oath/views/account_mixin.dart index a74caf7e..e13f0407 100755 --- a/lib/oath/views/account_mixin.dart +++ b/lib/oath/views/account_mixin.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'dart:ui'; import 'package:flutter/material.dart'; @@ -104,9 +105,10 @@ mixin AccountMixin { final ready = expired || credential.oathType == OathType.hotp; final pinned = isPinned(ref); + final shortcut = Platform.isMacOS ? '\u2318 C' : 'Ctrl+C'; return [ MenuAction( - text: 'Copy to clipboard', + text: 'Copy to clipboard ($shortcut)', icon: const Icon(Icons.copy), action: code == null || expired ? null From cb407691e063841865dbedce03de00abfe2e58af Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Thu, 7 Jul 2022 20:20:04 +0200 Subject: [PATCH 35/38] Move Chip actions to Popup menu. --- lib/app/views/app_page.dart | 8 +- lib/app/views/device_avatar.dart | 23 ++++ lib/app/views/device_button.dart | 132 ++++++++++++++++------ lib/app/views/device_picker_dialog.dart | 37 +----- lib/app/views/device_utils.dart | 44 ++++++++ lib/app/views/message_page.dart | 3 + lib/fido/views/locked_page.dart | 58 +++------- lib/fido/views/unlocked_page.dart | 67 +++++------ lib/oath/views/account_view.dart | 30 ++--- lib/oath/views/oath_screen.dart | 144 +++++++++++------------- lib/widgets/menu_list_tile.dart | 31 +++++ 11 files changed, 333 insertions(+), 244 deletions(-) create mode 100755 lib/app/views/device_utils.dart create mode 100755 lib/widgets/menu_list_tile.dart diff --git a/lib/app/views/app_page.dart b/lib/app/views/app_page.dart index f9f67709..06768b61 100755 --- a/lib/app/views/app_page.dart +++ b/lib/app/views/app_page.dart @@ -9,12 +9,14 @@ class AppPage extends ConsumerWidget { final Widget? title; final Widget child; final List actions; + final List keyActions; final bool centered; AppPage({ super.key, this.title, required this.child, this.actions = const [], + this.keyActions = const [], this.centered = false, }); @@ -82,10 +84,10 @@ class AppPage extends ConsumerWidget { title: title, centerTitle: true, titleTextStyle: Theme.of(context).textTheme.titleLarge, - actions: const [ + actions: [ Padding( - padding: EdgeInsets.only(right: 6), - child: DeviceButton(), + padding: const EdgeInsets.only(right: 6), + child: DeviceButton(actions: keyActions), ), ], ), diff --git a/lib/app/views/device_avatar.dart b/lib/app/views/device_avatar.dart index f368fb34..f9146bbf 100755 --- a/lib/app/views/device_avatar.dart +++ b/lib/app/views/device_avatar.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../widgets/custom_icons.dart'; import '../models.dart'; +import '../state.dart'; import 'device_images.dart'; class DeviceAvatar extends StatelessWidget { @@ -38,6 +40,27 @@ class DeviceAvatar extends StatelessWidget { ), ); + factory DeviceAvatar.currentDevice(WidgetRef ref, {double? radius}) { + final deviceNode = ref.watch(currentDeviceProvider); + if (deviceNode != null) { + return ref.watch(currentDeviceDataProvider).maybeWhen( + data: (data) => DeviceAvatar.yubiKeyData( + data, + radius: radius, + ), + orElse: () => DeviceAvatar.deviceNode( + deviceNode, + radius: radius, + ), + ); + } else { + return DeviceAvatar( + radius: radius, + child: const Icon(Icons.usb), + ); + } + } + @override Widget build(BuildContext context) { final radius = this.radius ?? 20; diff --git a/lib/app/views/device_button.dart b/lib/app/views/device_button.dart index 2e061326..aa481e34 100755 --- a/lib/app/views/device_button.dart +++ b/lib/app/views/device_button.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -5,57 +7,121 @@ import '../message.dart'; import '../state.dart'; import 'device_avatar.dart'; import 'device_picker_dialog.dart'; +import 'device_utils.dart'; + +class _CircledDeviceAvatar extends ConsumerWidget { + final double radius; + const _CircledDeviceAvatar(this.radius); + + @override + Widget build(BuildContext context, WidgetRef ref) => CircleAvatar( + radius: radius, + backgroundColor: Theme.of(context).colorScheme.primary, + child: IconTheme( + // Force the standard icon theme + data: IconTheme.of(context), + child: DeviceAvatar.currentDevice(ref, radius: radius - 1), + ), + ); +} class DeviceButton extends ConsumerWidget { final double radius; - const DeviceButton({super.key, this.radius = 16}); + final List actions; + const DeviceButton({super.key, this.actions = const [], this.radius = 16}); @override Widget build(BuildContext context, WidgetRef ref) { - final deviceNode = ref.watch(currentDeviceProvider); - Widget deviceWidget; - if (deviceNode != null) { - deviceWidget = ref.watch(currentDeviceDataProvider).maybeWhen( - data: (data) => DeviceAvatar.yubiKeyData( - data, - radius: radius - 1, - ), - orElse: () => DeviceAvatar.deviceNode( - deviceNode, - radius: radius - 1, - ), - ); - } else { - deviceWidget = DeviceAvatar( - radius: radius - 1, - child: const Icon(Icons.usb), - ); - } return Padding( padding: const EdgeInsets.only(right: 8.0), child: IconButton( - tooltip: 'Select YubiKey or device', + tooltip: 'More actions', icon: OverflowBox( maxHeight: 44, maxWidth: 44, - child: CircleAvatar( - radius: radius, - backgroundColor: Theme.of(context).colorScheme.primary, - child: IconTheme( - // Force the standard icon theme - data: IconTheme.of(context), - child: deviceWidget, - ), - ), + child: _CircledDeviceAvatar(radius), ), onPressed: () { - showBlurDialog( + final withContext = ref.read(withContextProvider); + + showMenu( context: context, - builder: (context) => const DevicePickerDialog(), - routeSettings: const RouteSettings(name: 'device_picker'), + position: const RelativeRect.fromLTRB(100, 0, 0, 0), + items: [ + PopupMenuItem( + padding: const EdgeInsets.only(left: 11, right: 16), + onTap: () { + // Wait for menu to close, and use the main context to open + Timer.run(() { + withContext( + (context) async { + await showBlurDialog( + context: context, + builder: (context) => const DevicePickerDialog(), + routeSettings: + const RouteSettings(name: 'device_picker'), + ); + }, + ); + }); + }, + child: _SlideInWidget(radius: radius), + ), + if (actions.isNotEmpty) const PopupMenuDivider(), + ...actions, + ], ); }, ), ); } } + +class _SlideInWidget extends ConsumerStatefulWidget { + final double radius; + const _SlideInWidget({required this.radius}); + + @override + ConsumerState<_SlideInWidget> createState() => _SlideInWidgetState(); +} + +class _SlideInWidgetState extends ConsumerState<_SlideInWidget> + with SingleTickerProviderStateMixin { + late final AnimationController _controller = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + )..forward(); + late final Animation _offsetAnimation = Tween( + begin: const Offset(0.9, 0.0), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _controller, + curve: Curves.easeOut, + )); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final messages = getDeviceMessages( + ref.watch(currentDeviceProvider), + ref.watch(currentDeviceDataProvider), + ); + return SlideTransition( + position: _offsetAnimation, + child: ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + minLeadingWidth: 0, + horizontalTitleGap: 13, + leading: _CircledDeviceAvatar(widget.radius), + title: Text(messages.removeAt(0)), + subtitle: Text(messages.first), + ), + ); + } +} diff --git a/lib/app/views/device_picker_dialog.dart b/lib/app/views/device_picker_dialog.dart index 6ec53689..b7f4ca91 100755 --- a/lib/app/views/device_picker_dialog.dart +++ b/lib/app/views/device_picker_dialog.dart @@ -9,16 +9,7 @@ import '../../management/models.dart'; import '../models.dart'; import '../state.dart'; import 'device_avatar.dart'; - -String _getInfoString(DeviceInfo info) { - final serial = info.serial; - var subtitle = ''; - if (serial != null) { - subtitle += 'S/N: $serial '; - } - subtitle += 'F/W: ${info.version}'; - return subtitle; -} +import 'device_utils.dart'; final _hiddenDevicesProvider = StateNotifierProvider<_HiddenDevicesNotifier, List>( @@ -224,37 +215,17 @@ class _CurrentDeviceRow extends StatelessWidget { @override Widget build(BuildContext context) { - final isNfc = node is NfcReaderNode; final hero = data.maybeWhen( data: (data) => DeviceAvatar.yubiKeyData(data, radius: 64), orElse: () => DeviceAvatar.deviceNode(node, radius: 64), ); - - final messages = data.whenOrNull( - data: (data) => [_getInfoString(data.info)], - error: (error, _) { - switch (error) { - case 'unknown-device': - return ['Unrecognized device']; - case 'device-inaccessible': - return ['Device inacessible']; - } - return null; - }, - ) ?? - ['No YubiKey present']; - - String name = - data.asData?.value.name ?? (isNfc ? messages.removeAt(0) : node.name); - if (isNfc) { - messages.add(node.name); - } + final messages = getDeviceMessages(node, data); return Column( children: [ _HeroAvatar(child: hero), ListTile( - title: Text(name, textAlign: TextAlign.center), + title: Text(messages.removeAt(0), textAlign: TextAlign.center), isThreeLine: messages.length > 1, subtitle: Text(messages.join('\n'), textAlign: TextAlign.center), ) @@ -280,7 +251,7 @@ class _DeviceRow extends ConsumerWidget { subtitle: Text( node.when( usbYubiKey: (_, __, ___, info) => - info == null ? 'Device inaccessible' : _getInfoString(info), + info == null ? 'Device inaccessible' : getDeviceInfoString(info), nfcReader: (_, __) => 'Select to scan', ), ), diff --git a/lib/app/views/device_utils.dart b/lib/app/views/device_utils.dart new file mode 100755 index 00000000..94a201dd --- /dev/null +++ b/lib/app/views/device_utils.dart @@ -0,0 +1,44 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../management/models.dart'; +import '../models.dart'; + +String getDeviceInfoString(DeviceInfo info) { + final serial = info.serial; + var subtitle = ''; + if (serial != null) { + subtitle += 'S/N: $serial '; + } + subtitle += 'F/W: ${info.version}'; + return subtitle; +} + +List getDeviceMessages(DeviceNode? node, AsyncValue data) { + if (node == null) { + return ['Insert a YubiKey', 'USB']; + } + final messages = data.whenOrNull( + data: (data) => [getDeviceInfoString(data.info)], + error: (error, _) { + switch (error) { + case 'unknown-device': + return ['Unrecognized device']; + case 'device-inaccessible': + return ['Device inacessible']; + } + return null; + }, + ) ?? + ['No YubiKey present']; + + final name = data.asData?.value.name; + if (name != null) { + messages.insert(0, name); + } + + if (node is NfcReaderNode) { + messages.add(node.name); + } + + return messages; +} diff --git a/lib/app/views/message_page.dart b/lib/app/views/message_page.dart index 3f2d766b..e325bcb9 100755 --- a/lib/app/views/message_page.dart +++ b/lib/app/views/message_page.dart @@ -8,6 +8,7 @@ class MessagePage extends StatelessWidget { final String? header; final String? message; final List actions; + final List keyActions; const MessagePage({ super.key, @@ -16,6 +17,7 @@ class MessagePage extends StatelessWidget { this.header, this.message, this.actions = const [], + this.keyActions = const [], }); @override @@ -23,6 +25,7 @@ class MessagePage extends StatelessWidget { title: title, centered: true, actions: actions, + keyActions: keyActions, child: Padding( padding: const EdgeInsets.all(8.0), child: Column( diff --git a/lib/fido/views/locked_page.dart b/lib/fido/views/locked_page.dart index 44ae723e..a5528771 100755 --- a/lib/fido/views/locked_page.dart +++ b/lib/fido/views/locked_page.dart @@ -6,7 +6,7 @@ import '../../app/models.dart'; import '../../app/views/app_page.dart'; import '../../app/views/graphics.dart'; import '../../app/views/message_page.dart'; -import '../../theme.dart'; +import '../../widgets/menu_list_tile.dart'; import '../models.dart'; import '../state.dart'; import 'pin_dialog.dart'; @@ -27,7 +27,7 @@ class FidoLockedPage extends ConsumerWidget { graphic: noFingerprints, header: 'No fingerprints', message: 'Set a PIN to register fingerprints', - actions: _buildActions(context), + keyActions: _buildActions(context), ); } else { return MessagePage( @@ -36,7 +36,7 @@ class FidoLockedPage extends ConsumerWidget { header: state.credMgmt ? 'No discoverable accounts' : 'Ready to use', message: 'Optionally set a PIN to protect access to your YubiKey\nRegister as a Security Key on websites', - actions: _buildActions(context), + keyActions: _buildActions(context), ); } } @@ -47,13 +47,13 @@ class FidoLockedPage extends ConsumerWidget { graphic: manageAccounts, header: 'Ready to use', message: 'Register as a Security Key on websites', - actions: _buildActions(context), + keyActions: _buildActions(context), ); } return AppPage( title: const Text('WebAuthn'), - actions: _buildActions(context), + keyActions: _buildActions(context), child: Column( children: [ _PinEntryForm(state, node), @@ -62,48 +62,26 @@ class FidoLockedPage extends ConsumerWidget { ); } - List _buildActions(BuildContext context) => [ + List _buildActions(BuildContext context) => [ if (!state.hasPin) - OutlinedButton.icon( - style: state.bioEnroll != null - ? AppTheme.primaryOutlinedButtonStyle(context) - : null, - label: const Text('Set PIN'), - icon: const Icon(Icons.pin), - onPressed: () { + buildMenuItem( + title: const Text('Set PIN'), + leading: const Icon(Icons.pin), + action: () { showBlurDialog( context: context, builder: (context) => FidoPinDialog(node.path, state), ); }, ), - OutlinedButton.icon( - label: const Text('Options'), - icon: const Icon(Icons.tune), - onPressed: () { - showBottomMenu(context, [ - if (state.hasPin) - MenuAction( - text: 'Change PIN', - icon: const Icon(Icons.pin), - action: (context) { - showBlurDialog( - context: context, - builder: (context) => FidoPinDialog(node.path, state), - ); - }, - ), - MenuAction( - text: 'Reset FIDO', - icon: const Icon(Icons.delete), - action: (context) { - showBlurDialog( - context: context, - builder: (context) => ResetDialog(node), - ); - }, - ), - ]); + buildMenuItem( + title: const Text('Reset FIDO'), + leading: const Icon(Icons.delete), + action: () { + showBlurDialog( + context: context, + builder: (context) => ResetDialog(node), + ); }, ), ]; diff --git a/lib/fido/views/unlocked_page.dart b/lib/fido/views/unlocked_page.dart index 908aa84e..1f540d48 100755 --- a/lib/fido/views/unlocked_page.dart +++ b/lib/fido/views/unlocked_page.dart @@ -6,8 +6,8 @@ import '../../app/models.dart'; import '../../app/views/app_page.dart'; import '../../app/views/graphics.dart'; import '../../app/views/message_page.dart'; -import '../../theme.dart'; import '../../widgets/list_title.dart'; +import '../../widgets/menu_list_tile.dart'; import '../models.dart'; import '../state.dart'; import 'add_fingerprint_dialog.dart'; @@ -121,7 +121,7 @@ class FidoUnlockedPage extends ConsumerWidget { if (children.isNotEmpty) { return AppPage( title: const Text('WebAuthn'), - actions: _buildActions(context), + keyActions: _buildKeyActions(context), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: children), ); @@ -133,7 +133,7 @@ class FidoUnlockedPage extends ConsumerWidget { graphic: noFingerprints, header: 'No fingerprints', message: 'Add one or more (up to five) fingerprints', - actions: _buildActions(context, fingerprintPrimary: true), + keyActions: _buildKeyActions(context), ); } @@ -142,7 +142,7 @@ class FidoUnlockedPage extends ConsumerWidget { graphic: manageAccounts, header: 'No discoverable accounts', message: 'Register as a Security Key on websites', - actions: _buildActions(context), + keyActions: _buildKeyActions(context), ); } @@ -152,49 +152,36 @@ class FidoUnlockedPage extends ConsumerWidget { child: const CircularProgressIndicator(), ); - List _buildActions(BuildContext context, - {bool fingerprintPrimary = false}) => - [ + List _buildKeyActions(BuildContext context) => [ if (state.bioEnroll != null) - OutlinedButton.icon( - style: fingerprintPrimary - ? AppTheme.primaryOutlinedButtonStyle(context) - : null, - label: const Text('Add fingerprint'), - icon: const Icon(Icons.fingerprint), - onPressed: () { + buildMenuItem( + title: const Text('Add fingerprint'), + leading: const Icon(Icons.fingerprint), + action: () { showBlurDialog( context: context, builder: (context) => AddFingerprintDialog(node.path), ); }, ), - OutlinedButton.icon( - label: const Text('Options'), - icon: const Icon(Icons.tune), - onPressed: () { - showBottomMenu(context, [ - MenuAction( - text: 'Change PIN', - icon: const Icon(Icons.pin), - action: (context) { - showBlurDialog( - context: context, - builder: (context) => FidoPinDialog(node.path, state), - ); - }, - ), - MenuAction( - text: 'Reset FIDO', - icon: const Icon(Icons.delete), - action: (context) { - showBlurDialog( - context: context, - builder: (context) => ResetDialog(node), - ); - }, - ), - ]); + buildMenuItem( + title: const Text('Change PIN'), + leading: const Icon(Icons.pin), + action: () { + showBlurDialog( + context: context, + builder: (context) => FidoPinDialog(node.path, state), + ); + }, + ), + buildMenuItem( + title: const Text('Reset FIDO'), + leading: const Icon(Icons.delete), + action: () { + showBlurDialog( + context: context, + builder: (context) => ResetDialog(node), + ); }, ), ]; diff --git a/lib/oath/views/account_view.dart b/lib/oath/views/account_view.dart index 6ea7d010..6253343d 100755 --- a/lib/oath/views/account_view.dart +++ b/lib/oath/views/account_view.dart @@ -1,11 +1,10 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../app/message.dart'; import '../../app/shortcuts.dart'; import '../../app/state.dart'; +import '../../widgets/menu_list_tile.dart'; import '../models.dart'; import '../state.dart'; import 'account_dialog.dart'; @@ -44,23 +43,16 @@ class AccountView extends ConsumerWidget with AccountMixin { List _buildPopupMenu(BuildContext context, WidgetRef ref) { return buildActions(context, ref).map((e) { final action = e.action; - return PopupMenuItem( - enabled: action != null, - onTap: () { - // As soon as onTap returns, the Navigator is popped, - // closing the topmost item. Since we sometimes open new dialogs in - // the action, make sure that happens *after* the pop. - Timer(Duration.zero, () { - action?.call(context); - }); - }, - child: ListTile( - leading: e.icon, - title: Text(e.text), - enabled: action != null, - dense: true, - contentPadding: EdgeInsets.zero, - ), + return buildMenuItem( + leading: e.icon, + title: Text(e.text), + action: action != null + ? () { + ref.read(withContextProvider)((context) async { + action.call(context); + }); + } + : null, ); }).toList(); } diff --git a/lib/oath/views/oath_screen.dart b/lib/oath/views/oath_screen.dart index 81d51053..9195f781 100755 --- a/lib/oath/views/oath_screen.dart +++ b/lib/oath/views/oath_screen.dart @@ -12,7 +12,7 @@ import '../../app/views/app_loading_screen.dart'; import '../../app/views/app_page.dart'; import '../../app/views/graphics.dart'; import '../../app/views/message_page.dart'; -import '../../theme.dart'; +import '../../widgets/menu_list_tile.dart'; import '../models.dart'; import '../state.dart'; import 'account_list.dart'; @@ -52,34 +52,26 @@ class _LockedView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) => AppPage( title: const Text('Authenticator'), - actions: [ - OutlinedButton.icon( - label: const Text('Options'), - icon: const Icon(Icons.tune), - onPressed: () { - showBottomMenu(context, [ - MenuAction( - text: 'Manage password', - icon: const Icon(Icons.password), - action: (context) { - showBlurDialog( - context: context, - builder: (context) => - ManagePasswordDialog(devicePath, oathState), - ); - }, - ), - MenuAction( - text: 'Reset OATH', - icon: const Icon(Icons.delete), - action: (context) { - showBlurDialog( - context: context, - builder: (context) => ResetDialog(devicePath), - ); - }, - ), - ]); + keyActions: [ + buildMenuItem( + title: const Text('Manage password'), + leading: const Icon(Icons.password), + action: () { + showBlurDialog( + context: context, + builder: (context) => + ManagePasswordDialog(devicePath, oathState), + ); + }, + ), + buildMenuItem( + title: const Text('Reset OATH'), + leading: const Icon(Icons.delete), + action: () { + showBlurDialog( + context: context, + builder: (context) => ResetDialog(devicePath), + ); }, ), ], @@ -124,14 +116,17 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { @override Widget build(BuildContext context) { - final isEmpty = ref.watch(credentialListProvider(widget.devicePath) - .select((value) => value?.isEmpty == true)); - if (isEmpty) { + final credentials = ref.watch(credentialListProvider(widget.devicePath)); + if (credentials?.isEmpty == true) { return MessagePage( title: const Text('Authenticator'), graphic: noAccounts, header: 'No accounts', - actions: _buildActions(context, true), + keyActions: _buildActions( + context, + used: 0, + capacity: widget.oathState.version.isAtLeast(4) ? 32 : null, + ), ); } return Actions( @@ -179,59 +174,56 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { ); }), ), - actions: _buildActions(context, false), + keyActions: _buildActions( + context, + used: credentials?.length ?? 0, + capacity: widget.oathState.version.isAtLeast(4) ? 32 : null, + ), child: AccountList(widget.devicePath, widget.oathState), ), ); } - List _buildActions(BuildContext context, bool isEmpty) { + List _buildActions(BuildContext context, + {required int used, int? capacity}) { return [ - OutlinedButton.icon( - style: isEmpty ? AppTheme.primaryOutlinedButtonStyle(context) : null, - label: const Text('Add account'), - icon: const Icon(Icons.person_add_alt_1), - onPressed: () { - showBlurDialog( - context: context, - builder: (context) => OathAddAccountPage( - widget.devicePath, - widget.oathState, - openQrScanner: Platform.isAndroid, - ), - ); - }, - ), - OutlinedButton.icon( - label: const Text('Options'), - icon: const Icon(Icons.tune), - onPressed: () { - showBottomMenu(context, [ - MenuAction( - text: - widget.oathState.hasKey ? 'Manage password' : 'Set password', - icon: const Icon(Icons.password), - action: (context) { + buildMenuItem( + title: const Text('Add account'), + leading: const Icon(Icons.person_add_alt_1), + trailing: capacity != null ? '$used/$capacity' : null, + action: capacity == null || capacity > used + ? () { showBlurDialog( context: context, - builder: (context) => - ManagePasswordDialog(widget.devicePath, widget.oathState), + builder: (context) => OathAddAccountPage( + widget.devicePath, + widget.oathState, + openQrScanner: Platform.isAndroid, + ), ); - }, - ), - MenuAction( - text: 'Reset OATH', - icon: const Icon(Icons.delete), - action: (context) { - showBlurDialog( - context: context, - builder: (context) => ResetDialog(widget.devicePath), - ); - }, - ), - ]); - }, + } + : null, ), + buildMenuItem( + title: Text( + widget.oathState.hasKey ? 'Manage password' : 'Set password'), + leading: const Icon(Icons.password), + action: () { + showBlurDialog( + context: context, + builder: (context) => + ManagePasswordDialog(widget.devicePath, widget.oathState), + ); + }), + buildMenuItem( + title: const Text('Reset OATH'), + leading: const Icon(Icons.delete), + action: () { + showBlurDialog( + context: context, + builder: (context) => ResetDialog(widget.devicePath), + ); + }), ]; } } diff --git a/lib/widgets/menu_list_tile.dart b/lib/widgets/menu_list_tile.dart new file mode 100755 index 00000000..236f7504 --- /dev/null +++ b/lib/widgets/menu_list_tile.dart @@ -0,0 +1,31 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +PopupMenuItem buildMenuItem({ + required Widget title, + Widget? leading, + String? trailing, + void Function()? action, +}) => + PopupMenuItem( + enabled: action != null, + onTap: () { + // Wait for popup menu to close before running action. + Timer.run(action!); + }, + child: ListTile( + enabled: action != null, + dense: true, + contentPadding: EdgeInsets.zero, + minLeadingWidth: 0, + title: title, + leading: leading, + trailing: trailing != null + ? Opacity( + opacity: 0.5, + child: Text(trailing, textScaleFactor: 0.7), + ) + : null, + ), + ); From bc5f47d0d77116bcad656e7ba9fdf13f1d9e635b Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Fri, 8 Jul 2022 09:12:31 +0200 Subject: [PATCH 36/38] Test: Fix OATH credential provider. --- lib/oath/state.dart | 33 +++++++++++++++++++++++++++++---- lib/oath/views/oath_screen.dart | 2 +- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/oath/state.dart b/lib/oath/state.dart index 6966879b..217aad6c 100755 --- a/lib/oath/state.dart +++ b/lib/oath/state.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -59,15 +60,39 @@ abstract class OathCredentialListNotifier Future deleteAccount(OathCredential credential); } -final credentialsProvider = Provider.autoDispose?>((ref) { +final credentialsProvider = StateNotifierProvider.autoDispose< + _CredentialsProviderNotifier, List?>((ref) { + final provider = _CredentialsProviderNotifier(); final node = ref.watch(currentDeviceProvider); if (node != null) { - return ref.watch(credentialListProvider(node.path) - .select((pairs) => pairs?.map((e) => e.credential).toList())); + ref.listen?>(credentialListProvider(node.path), + (previous, next) { + provider._updatePairs(next); + }); } - return null; + return provider; }); +class _CredentialsProviderNotifier + extends StateNotifier?> { + _CredentialsProviderNotifier() : super(null); + + void _updatePairs(List? pairs) { + if (mounted) { + if (pairs == null) { + if (state != null) { + state = null; + } + } else { + final creds = pairs.map((p) => p.credential).toList(); + if (!const ListEquality().equals(creds, state)) { + state = creds; + } + } + } + } +} + final codeProvider = Provider.autoDispose.family((ref, credential) { final node = ref.watch(currentDeviceProvider); diff --git a/lib/oath/views/oath_screen.dart b/lib/oath/views/oath_screen.dart index 9195f781..d00cd6db 100755 --- a/lib/oath/views/oath_screen.dart +++ b/lib/oath/views/oath_screen.dart @@ -116,7 +116,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { @override Widget build(BuildContext context) { - final credentials = ref.watch(credentialListProvider(widget.devicePath)); + final credentials = ref.watch(credentialsProvider); if (credentials?.isEmpty == true) { return MessagePage( title: const Text('Authenticator'), From a27ace428cccc11dec07c8837f959856a38ccf1b Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Fri, 8 Jul 2022 15:16:08 +0200 Subject: [PATCH 37/38] Fix QR scanning and update zxing. --- helper/helper/qr.py | 3 ++- helper/poetry.lock | 32 ++++++++++++++++---------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/helper/helper/qr.py b/helper/helper/qr.py index ce84b941..9aac92fd 100644 --- a/helper/helper/qr.py +++ b/helper/helper/qr.py @@ -8,6 +8,7 @@ import subprocess import tempfile from mss.exception import ScreenShotError from PIL import Image +import numpy.core.multiarray # noqa def _capture_screen(): @@ -41,6 +42,6 @@ def scan_qr(image_data=None): img = _capture_screen() result = zxingcpp.read_barcode(img) - if result.valid: + if result and result.valid: return result.text return None diff --git a/helper/poetry.lock b/helper/poetry.lock index f7dc9362..16daa3e1 100755 --- a/helper/poetry.lock +++ b/helper/poetry.lock @@ -386,7 +386,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [[package]] name = "zxing-cpp" -version = "1.3.0" +version = "1.4.0" description = "Python bindings for the zxing-cpp barcode library" category = "main" optional = false @@ -711,19 +711,19 @@ zipp = [ {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, ] zxing-cpp = [ - {file = "zxing-cpp-1.3.0.tar.gz", hash = "sha256:5f30545afad01a278fc8c17efae11d82e36f8c2caa87c89096aec5a8d69103b2"}, - {file = "zxing_cpp-1.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3a6e183b6c0aae9378f674f9e7714a39482595915cf15198d10b9ba8c33b25f"}, - {file = "zxing_cpp-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88eadb723d20655caf81a6ba6ef64d74a266f57cbd782da82736c52a61a73fa5"}, - {file = "zxing_cpp-1.3.0-cp310-cp310-win32.whl", hash = "sha256:15fb165ada1730ab0d96b67eb2d9827870d9ae534686e27541f3b3add15b96d7"}, - {file = "zxing_cpp-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:8dbb17a31ee1ac2c946a96e83b170ecefbc87a52b9c35b41809d9afff77d8879"}, - {file = "zxing_cpp-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31578db20ba0668e010cb62e4718cb86f47563ec5122e29a0746651ff1e13735"}, - {file = "zxing_cpp-1.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9253a3b6c8c143f3c22d172922226b10c8cc319d2554c73107fefce7e263daaa"}, - {file = "zxing_cpp-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:250afd201f08bd1be8fd349766e32ef184a463b616c13102b2f80a4422695957"}, - {file = "zxing_cpp-1.3.0-cp38-cp38-win32.whl", hash = "sha256:d2891dfba5c53b913867e7b01b8b430d801e15e54f53b3c05b9645dc824dfed3"}, - {file = "zxing_cpp-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:6201e60cbefbc8de90c5f18e6e25c3cb1be19be8f369bf4dad3ab910b954f29d"}, - {file = "zxing_cpp-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:44467984c1a65a332c8656926f30af1752c1ff774c6a030b95572e0a1543b23b"}, - {file = "zxing_cpp-1.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0dbb54f8694063376d73be6f7dbddd39f3e7907ab885403d90cff7d518c54f7f"}, - {file = "zxing_cpp-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3cff8a7fe960c2016bc8e217fcf02b9b1ac61b17fc5c0c5158f853088be4ad9"}, - {file = "zxing_cpp-1.3.0-cp39-cp39-win32.whl", hash = "sha256:f75431cf7cddcb21c267d39a5895831a3c20abfa7676426974652d25b29ae429"}, - {file = "zxing_cpp-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:de9dd0a2d01969e9828c5704d709b2559a417fea562bd2f308ebc8d4a9678b5e"}, + {file = "zxing-cpp-1.4.0.tar.gz", hash = "sha256:3d3ec36954ecbf9b0f633dab4b8cebcf0059d8a27f7a5969c4e41a308111af38"}, + {file = "zxing_cpp-1.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25b11d77cf6b9f7405af3ed6bacf4a6e0756ea74dfda7040ff53e7c58f352b05"}, + {file = "zxing_cpp-1.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1f849205237d4bda462d0a4b745e72494f825e5b6b06581e05b58d34d9869aa"}, + {file = "zxing_cpp-1.4.0-cp310-cp310-win32.whl", hash = "sha256:76e9777d943af3c51b6406b323b3f28cbf9e40cc65b53cf847fda08295f18e48"}, + {file = "zxing_cpp-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:234d672e34e607ffc8e06639e79c8e1bf2ddb7c249134a6836569e92a2f2dd64"}, + {file = "zxing_cpp-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1f66c61e43163740c59c58880c3a8c41ebd2109573c0494f255c9c96134e8c"}, + {file = "zxing_cpp-1.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9418e1bd0775820a4933b60007b7f8a177e4ddd23692c1aaed2348fafc0a8e01"}, + {file = "zxing_cpp-1.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bc1e48ddfd6692d183782f091fbf54e5e1d36d0070822b1eab14cfb580b1625"}, + {file = "zxing_cpp-1.4.0-cp38-cp38-win32.whl", hash = "sha256:4f340b6907780e8eb0e6473fec43ea145c4dd3275e3c21d6f887c0e28e114f29"}, + {file = "zxing_cpp-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:71772f81c4602133b2dba6a1107339ed965725001ce9a4caaf772598110351a1"}, + {file = "zxing_cpp-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:331bec6b0ac8a9b339bc82956c52c022e7b2debfeb9102209483eb7538ed72d4"}, + {file = "zxing_cpp-1.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b0844c6ad3c944452c980a025238ba3fbd3a414fd2c36e2bec1bc5bed03b21e"}, + {file = "zxing_cpp-1.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a770aff618cd00dda3922de2f7085c1f84bbe02f2b6df114d19054ad41c52fb0"}, + {file = "zxing_cpp-1.4.0-cp39-cp39-win32.whl", hash = "sha256:ebe67de6a4d3c48a5ee52211ecf2003301ab39bd7d7b7dfa72ae80be429cfcf9"}, + {file = "zxing_cpp-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:d0e8b54b29497ed9238f31ce522ddb0189c0d6c4597787ef2eb823ca9fb42350"}, ] From f1883bb88a22c1d33aee6afb38a9907b2deaf5ce Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Mon, 11 Jul 2022 10:32:26 +0200 Subject: [PATCH 38/38] Actions win: change mv to cp --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 63a69c2c..1481037c 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -58,7 +58,7 @@ jobs: run: | $env:PATH += ";$env:WIX\bin" $env:SRCDIR = "build\windows\runner\Release\" - mv resources\win\license.rtf .\ + cp resources\win\license.rtf .\ heat dir .\build\windows\runner\Release\ -out fragment.wxs -gg -scom -srd -sfrag -dr INSTALLDIR -cg ApplicationFiles -var env.SRCDIR candle .\fragment.wxs .\resources\win\yubioath-desktop.wxs -ext WixUtilExtension -arch x64 light fragment.wixobj yubioath-desktop.wixobj -ext WixUIExtension -ext WixUtilExtension -o yubioath-desktop.msi