diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9b3dbf59..2dacf749 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -71,7 +71,7 @@ stages: - export GO111MODULE=on - export PATH="${GOPATH}/bin:${PATH}" - export MSYSTEM= - - export QT6DIR=/c/grrrQt/6.3.2/msvc2019_64 + - export QT6DIR=/c/grrrQt/6.4.3/msvc2019_64 - export PATH=$PATH:${QT6DIR}/bin - export PATH="/c/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin:$PATH" - $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove" @@ -93,7 +93,7 @@ stages: - export PATH="${GOROOT}/bin:$PATH" - export GOPATH=~/go1.20 - export PATH="${GOPATH}/bin:$PATH" - - export QT6DIR=/opt/Qt/6.3.2/macos + - export QT6DIR=/opt/Qt/6.4.3/macos - export PATH="${QT6DIR}/bin:$PATH" - uname -a cache: {} @@ -101,7 +101,7 @@ stages: - macos-m1-bridge .env-linux-build: - image: gitlab.protontech.ch:4567/go/bridge-internal:build-go1.20-qt6.3.2 + image: gitlab.protontech.ch:4567/go/bridge-internal:build-go1.20-qt6.4.3 variables: VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache cache: @@ -282,4 +282,18 @@ build-windows-qa: variables: BUILD_TAGS: "build_qa" +trigeer-qa-installer: + stage: build + needs: ["lint"] + extends: + - .rules-branch-and-MR-manual + variables: + APP: bridge + WORKFLOW: build-all + SRC_TAG: $CI_COMMIT_BRANCH + SRC_HASH: $CI_COMMIT_SHA + trigger: + project: "jcuth/bridge-release" + branch: master + # TODO: PUT BACK ALL THE JOBS! JUST DID THIS FOR NOW TO GET CI WORKING AGAIN... diff --git a/BUILDS.md b/BUILDS.md index 237694bd..261ecd69 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -10,7 +10,7 @@ * Windres (Windows) * libglvnd and libsecret development files (Linux) * pkg-config (Linux) -* cmake, ninja-build and Qt 6 are required to build the graphical user interface. On Linux, +* cmake, ninja-build and Qt 6.4.3 are required to build the graphical user interface. On Linux, the Mesa OpenGL development files are also needed. To enable the sending of crash reports using Sentry please set the @@ -19,7 +19,7 @@ Otherwise, the sending of crash reports will be disabled. ## Build In order to build Bridge app with Qt interface we are using -[Qt 6.3](https://doc.qt.io/qt-6/gettingstarted.html). +[Qt 6.4.3](https://doc.qt.io/qt-6/gettingstarted.html). Please note that qmake path must be in your `PATH` to ensure Qt to be found. Also, before you start build **on Windows**, please unset the `MSYSTEM` variable diff --git a/COPYING_NOTES.md b/COPYING_NOTES.md index f7f4d43a..8fbda386 100644 --- a/COPYING_NOTES.md +++ b/COPYING_NOTES.md @@ -40,6 +40,7 @@ Proton Mail Bridge includes the following 3rd party software: * [go-message](https://github.com/emersion/go-message) available under [license](https://github.com/emersion/go-message/blob/master/LICENSE) * [go-sasl](https://github.com/emersion/go-sasl) available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE) * [go-smtp](https://github.com/emersion/go-smtp) available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE) +* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE) * [color](https://github.com/fatih/color) available under [license](https://github.com/fatih/color/blob/master/LICENSE) * [sentry-go](https://github.com/getsentry/sentry-go) available under [license](https://github.com/getsentry/sentry-go/blob/master/LICENSE) * [resty](https://github.com/go-resty/resty/v2) available under [license](https://github.com/go-resty/resty/v2/blob/master/LICENSE) @@ -83,7 +84,6 @@ Proton Mail Bridge includes the following 3rd party software: * [go-spew](https://github.com/davecgh/go-spew) available under [license](https://github.com/davecgh/go-spew/blob/master/LICENSE) * [go-windows](https://github.com/elastic/go-windows) available under [license](https://github.com/elastic/go-windows/blob/master/LICENSE) * [go-textwrapper](https://github.com/emersion/go-textwrapper) available under [license](https://github.com/emersion/go-textwrapper/blob/master/LICENSE) -* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE) * [fgprof](https://github.com/felixge/fgprof) available under [license](https://github.com/felixge/fgprof/blob/master/LICENSE) * [go-shlex](https://github.com/flynn-archive/go-shlex) available under [license](https://github.com/flynn-archive/go-shlex/blob/master/LICENSE) * [mimetype](https://github.com/gabriel-vasile/mimetype) available under [license](https://github.com/gabriel-vasile/mimetype/blob/master/LICENSE) @@ -123,6 +123,7 @@ Proton Mail Bridge includes the following 3rd party software: * [codec](https://github.com/ugorji/go/codec) available under [license](https://github.com/ugorji/go/codec/blob/master/LICENSE) * [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE) * [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE) +* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json) * [arch](https://golang.org/x/arch) available under [license](https://cs.opensource.google/go/x/arch/+/master:LICENSE) * [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE) * [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE) diff --git a/Changelog.md b/Changelog.md index 13df6ffa..e2f7059f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,36 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) +## Vasco da Gama Bridge 3.6.0 + +### Added +* GODT-2762: Setup wizard. +* GODT-2772: Setup wizard content. +* GODT-2769: Setup Wizard architecture. +* GODT-2767: Setup Wizard foundations. +* GODT-2725: Implement receive message step with expected structure exposed. + +### Changed +* GODT-2960: Added content in empty view when there is no account. +* GODT-2771: Cert related tools for macOS. +* GODT-2770: Proof of concept for web view as a tool window and overlay (not used). +* GODT-2916: Split Decryption from Message Building. +* GODT-2597: Implement contact specific settings in integration tests. +* GODT-2664: Trigger QA installer. + +### Fixed +* GODT-2992: Fix link in 'no account view' in main window after 2FA or TOTP are cancelled. +* GODT-2989: Allow to send bug report when no account connected. +* GODT-2988: Fix setup wizard KB links. +* GODT-2968: Use proper base64 encoded string even for bad password test. +* GODT-2965: Fix multipart/mixed testdata + structure parsing steps related to this. +* GODT-2932: Fix syncing not being reported in GUI. +* GODT-2967: Tray menu entries close the setup wizard when needed. +* GODT-2212: Preserver Header order in message building. +* Fixed missing GoOs gRPC call in bridge-gui-tester. +* GODT-2929: Message dedup with different text transfer encoding. + + ## Umshiang Bridge 3.5.3 ### Changed diff --git a/Makefile b/Makefile index 14dd0298..0ae1998a 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) .PHONY: build build-gui build-nogui build-launcher versioner hasher # Keep version hardcoded so app build works also without Git repository. -BRIDGE_APP_VERSION?=3.5.3+git +BRIDGE_APP_VERSION?=3.6.0+git APP_VERSION:=${BRIDGE_APP_VERSION} APP_FULL_NAME:=Proton Mail Bridge APP_VENDOR:=Proton AG diff --git a/go.mod b/go.mod index e21f548e..b6f6097d 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 github.com/ProtonMail/gluon v0.17.1-0.20231009084701-3af0474b0b3c github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a - github.com/ProtonMail/go-proton-api v0.4.1-0.20231011062329-f3b976b7dbca + github.com/ProtonMail/go-proton-api v0.4.1-0.20231011132529-24b5b817ee1f github.com/ProtonMail/gopenpgp/v2 v2.7.3-proton github.com/PuerkitoBio/goquery v1.8.1 github.com/abiosoft/ishell v2.0.0+incompatible @@ -22,6 +22,7 @@ require ( github.com/emersion/go-message v0.16.0 github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead github.com/emersion/go-smtp v0.15.1-0.20221021114529-49b17434419d + github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 github.com/fatih/color v1.13.0 github.com/getsentry/sentry-go v0.15.0 github.com/go-resty/resty/v2 v2.7.0 @@ -68,7 +69,6 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/elastic/go-windows v1.0.1 // indirect github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect - github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 // indirect github.com/felixge/fgprof v0.9.3 // indirect github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect @@ -108,6 +108,7 @@ require ( github.com/ugorji/go/codec v1.2.11 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.9.0 // indirect golang.org/x/mod v0.8.0 // indirect diff --git a/go.sum b/go.sum index 4a0bebe5..157d4409 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/ github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= -github.com/ProtonMail/go-proton-api v0.4.1-0.20231011062329-f3b976b7dbca h1:nO/xuvyEgWWLo2cBAqfxCHh7Ri0ofV3PXnTOfk0QcyI= -github.com/ProtonMail/go-proton-api v0.4.1-0.20231011062329-f3b976b7dbca/go.mod h1:IGVXKy6NLHt4WeWiOnAFmSsXRpd6elkjDZMtr5vBLJ8= +github.com/ProtonMail/go-proton-api v0.4.1-0.20231011132529-24b5b817ee1f h1:n0oBMAz2dJhn5+1WA6NrjkWqkZN+22FQMkPlRwNGhpU= +github.com/ProtonMail/go-proton-api v0.4.1-0.20231011132529-24b5b817ee1f/go.mod h1:ZmvQMA8hanLiD1tFsvu9+qGBcuxbIRfch/4z/nqBhXA= github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI= github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk= github.com/ProtonMail/gopenpgp/v2 v2.7.3-proton h1:wuAxBUU9qF2wyDVJprn/2xPDx000eol5gwlKbOUYY88= @@ -399,6 +399,8 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a h1:DxppxFKRqJ8WD6oJ3+ZXKDY0iMONQDl5UTg2aTyHh8k= +gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a/go.mod h1:NREvu3a57BaK0R1+ztrEzHWiZAihohNLQ6trPxlIqZI= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= diff --git a/internal/app/vault.go b/internal/app/vault.go index 042e89f2..dc349c22 100644 --- a/internal/app/vault.go +++ b/internal/app/vault.go @@ -22,7 +22,6 @@ import ( "path" "github.com/ProtonMail/gluon/async" - "github.com/ProtonMail/proton-bridge/v3/internal/certs" "github.com/ProtonMail/proton-bridge/v3/internal/constants" "github.com/ProtonMail/proton-bridge/v3/internal/locations" "github.com/ProtonMail/proton-bridge/v3/internal/vault" @@ -45,23 +44,6 @@ func WithVault(locations *locations.Locations, panicHandler async.PanicHandler, "corrupt": corrupt, }).Debug("Vault created") - // Install the certificates if needed. - if installed := encVault.GetCertsInstalled(); !installed { - logrus.Debug("Installing certificates") - - certPEM, _ := encVault.GetBridgeTLSCert() - - if err := certs.NewInstaller().InstallCert(certPEM); err != nil { - return fmt.Errorf("failed to install certs: %w", err) - } - - if err := encVault.SetCertsInstalled(true); err != nil { - return fmt.Errorf("failed to set certs installed: %w", err) - } - - logrus.Debug("Certificates successfully installed") - } - // GODT-1950: Add teardown actions (e.g. to close the vault). return fn(encVault, insecure, corrupt) diff --git a/internal/bridge/bug_report.go b/internal/bridge/bug_report.go index 49675337..2bcfba71 100644 --- a/internal/bridge/bug_report.go +++ b/internal/bridge/bug_report.go @@ -34,7 +34,7 @@ const ( ) func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, title, description, username, email, client string, attachLogs bool) error { - var account string + var account = username if info, err := bridge.QueryUserInfo(username); err == nil { account = info.Username diff --git a/internal/certs/cert_store_darwin.go b/internal/certs/cert_store_darwin.go index 97a79fa5..c6642645 100644 --- a/internal/certs/cert_store_darwin.go +++ b/internal/certs/cert_store_darwin.go @@ -23,71 +23,200 @@ package certs #import #import +// Memory management rules: +// Foundation object (Objective-C prefixed with `NS`) get ARC (Automatic Reference Counting), and do not need to be released manually. +// Core Foundation objects (C), prefixed with need to be released manually using CFRelease() unless: +// - They're obtained using a CF method containing the word Get (a.k.a. the Get Rule). +// - They're obtained using toll-free bridging from a Foundation Object (using the __bridge keyword). -int installTrustedCert(char const *bytes, unsigned long long length) { - if (length == 0) { - return errSecInvalidData; - } - - NSData *der = [NSData dataWithBytes:bytes length:length]; - - // Step 1. Import the certificate in the keychain. - SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef) der); - NSDictionary* addQuery = @{ - (id)kSecValueRef: (__bridge id) cert, - (id)kSecClass: (id)kSecClassCertificate, - }; - - OSStatus status = SecItemAdd((__bridge CFDictionaryRef) addQuery, NULL); - if ((errSecSuccess != status) && (errSecDuplicateItem != status)) { - CFRelease(cert); - return status; - } - - // Step 2. Set the trust for the certificate. - SecPolicyRef policy = SecPolicyCreateSSL(true, NULL); // we limit our trust to SSL - NSDictionary *trustSettings = @{ - (id)kSecTrustSettingsResult: [NSNumber numberWithInt:kSecTrustSettingsResultTrustRoot], - (id)kSecTrustSettingsPolicy: (__bridge id) policy, - }; - status = SecTrustSettingsSetTrustSettings(cert, kSecTrustSettingsDomainUser, (__bridge CFTypeRef)(trustSettings)); - CFRelease(policy); - CFRelease(cert); - - return status; +//**************************************************************************************************************************************************** +/// \brief Create a certificate object from DER-encoded data. +/// +/// \return The certifcation. The caller is responsible for releasing the object using CFRelease. +/// \return NULL if data is not a valid DER-encoded certificate. +//**************************************************************************************************************************************************** +SecCertificateRef certFromData(char const* data, uint64_t length) { + NSData *der = [NSData dataWithBytes:data length:length]; + return SecCertificateCreateWithData(NULL, (__bridge CFDataRef)der); } -int removeTrustedCert(char const *bytes, unsigned long long length) { - if (0 == length) { - return errSecInvalidData; - } +//**************************************************************************************************************************************************** +/// \brief Check if a certificate is in the user's keychain. +/// +/// \param[in] cert The certificate. +/// \return true iff the certificate is in the user's keychain. +//**************************************************************************************************************************************************** +bool _isCertificateInKeychain(SecCertificateRef const cert) { + NSDictionary *attrs = @{ + (id)kSecMatchItemList: @[(__bridge id)cert], + (id)kSecClass: (id)kSecClassCertificate, + (id)kSecReturnData: @YES + }; + return errSecSuccess == SecItemCopyMatching((__bridge CFDictionaryRef)attrs, NULL); +} - NSData *der = [NSData dataWithBytes: bytes length: length]; - SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef) der); +//**************************************************************************************************************************************************** +/// \brief Check if a certificate is in the user's keychain. +/// +/// \param[in] certData The certificate data in DER encoded format. +/// \param[in] certSize The size of the certData in bytes. +/// \return true iff the certificate is in the user's keychain. +//**************************************************************************************************************************************************** +bool isCertificateInKeychain(char const* certData, uint64_t certSize) { + return _isCertificateInKeychain(certFromData(certData, certSize)); +} - // Step 1. Unset the trust for the certificate. - SecPolicyRef policy = SecPolicyCreateSSL(true, NULL); - NSDictionary * trustSettings = @{ - (id)kSecTrustSettingsResult: [NSNumber numberWithInt:kSecTrustSettingsResultUnspecified], - (id)kSecTrustSettingsPolicy: (__bridge id) policy, - }; - OSStatus status = SecTrustSettingsSetTrustSettings(cert, kSecTrustSettingsDomainUser, (__bridge CFTypeRef)(trustSettings)); - CFRelease(policy); - if (errSecSuccess != status) { - CFRelease(cert); - return status; - } - // Step 2. Remove the certificate from the keychain. - NSDictionary *query = @{ (id)kSecClass: (id)kSecClassCertificate, - (id)kSecMatchItemList: @[(__bridge id)cert], - (id)kSecMatchLimit: (id)kSecMatchLimitOne, - }; - status = SecItemDelete((__bridge CFDictionaryRef) query); +//**************************************************************************************************************************************************** +/// \brief Add a certificate to the user's keychain. +/// +/// \param[in] cert The certificate. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus _addCertificateToKeychain(SecCertificateRef const cert) { + NSDictionary* addQuery = @{ + (id)kSecValueRef: (__bridge id) cert, + (id)kSecClass: (id)kSecClassCertificate, + }; + return SecItemAdd((__bridge CFDictionaryRef) addQuery, NULL); +} - CFRelease(cert); - return status; +//**************************************************************************************************************************************************** +/// \brief Add a certificate to the user's keychain. +/// +/// \param[in] certData The certificate data in DER encoded format. +/// \param[in] certSize The size of the certData in bytes. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus addCertificateToKeychain(char const* certData, uint64_t certSize) { + return _addCertificateToKeychain(certFromData(certData, certSize)); +} + +//**************************************************************************************************************************************************** +/// \brief Add a certificate to the user's keychain. +/// +/// \param[in] cert The certificate. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus _removeCertificateFromKeychain(SecCertificateRef const cert) { + NSDictionary *query = @{ (id)kSecClass: (id)kSecClassCertificate, + (id)kSecMatchItemList: @[(__bridge id)cert], + (id)kSecMatchLimit: (id)kSecMatchLimitOne, + }; + return SecItemDelete((__bridge CFDictionaryRef) query); +} + +//**************************************************************************************************************************************************** +/// \brief Add a certificate to the user's keychain. +/// +/// \param[in] certData The certificate data in DER encoded format. +/// \param[in] certSize The size of the certData in bytes. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus removeCertificateFromKeychain(char const* certData, uint64_t certSize) { + return _removeCertificateFromKeychain(certFromData(certData, certSize)); +} + +//**************************************************************************************************************************************************** +/// \brief Check if a certificate is trusted in the user's keychain. +/// +/// \param[in] cert The certificate. +/// \return true iff the certificate is trusted in the user's keychain. +//**************************************************************************************************************************************************** +bool _isCertificateTrusted(SecCertificateRef const cert) { + CFArrayRef trustSettings = NULL; + OSStatus status = SecTrustSettingsCopyTrustSettings(cert, kSecTrustSettingsDomainUser, &trustSettings); + if (status != errSecSuccess) { + return false; + } + CFIndex count = CFArrayGetCount(trustSettings); + bool result = false; + for (CFIndex index = 0; index < count; ++index) { + CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, index); + if (!dict) { + continue; + } + CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(dict, kSecTrustSettingsResult); + int value; + if (num && CFNumberGetValue(num, kCFNumberSInt32Type, &value) && (value == kSecTrustSettingsResultTrustRoot)) { + result = true; + break; + } + } + CFRelease(trustSettings); + return result; +} + +//**************************************************************************************************************************************************** +/// \brief Check if a certificate is trusted in the user's keychain. +/// +/// \param[in] certData The certificate data in DER encoded format. +/// \param[in] certSize The size of the certData in bytes. +/// \return true iff the certificate is trusted in the user's keychain. +//**************************************************************************************************************************************************** +bool isCertificateTrusted(char const* certData, uint64_t certSize) { + return _isCertificateTrusted(certFromData(certData, certSize)); +} + +//**************************************************************************************************************************************************** +/// \brief Set the trust level for a certificate in the user's keychain. This call will trigger a security prompt. +/// +/// \param[in] cert The certificate. +/// \param[in] trustLevel The trust level. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus _setCertificateTrustLevel(SecCertificateRef const cert, int trustLevel) { + SecPolicyRef policy = SecPolicyCreateSSL(true, NULL); // we limit our trust to SSL + NSDictionary *trustSettings = @{ + (id)kSecTrustSettingsResult: [NSNumber numberWithInt:trustLevel], + (id)kSecTrustSettingsPolicy: (__bridge id) policy, + }; + OSStatus status = SecTrustSettingsSetTrustSettings(cert, kSecTrustSettingsDomainUser, (__bridge CFTypeRef)(trustSettings)); + CFRelease(policy); + return status; +} + +//**************************************************************************************************************************************************** +/// \brief Set a certificate as trusted in the user's keychain. This call will trigger a security prompt. +/// +/// \param[in] cert The certificate. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus _setCertificateTrusted(SecCertificateRef cert) { + return _setCertificateTrustLevel(cert, kSecTrustSettingsResultTrustRoot); +} + +//**************************************************************************************************************************************************** +/// \brief Set a certificate as trusted in the user's keychain. This call will trigger a security prompt. +/// +/// \param[in] certData The certificate data in DER encoded format. +/// \param[in] certSize The size of the certData in bytes. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus setCertificateTrusted(char const* certData, uint64_t certSize) { + return _setCertificateTrusted(certFromData(certData, certSize)); +} + +//**************************************************************************************************************************************************** +/// \brief Remove the trust level of a certificate in the user's keychain. +/// +/// \param[in] cert The certificate. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus _removeCertificateTrust(SecCertificateRef cert) { + return _setCertificateTrustLevel(cert, kSecTrustSettingsResultUnspecified); +} + +//**************************************************************************************************************************************************** +/// \brief Remove the trust level of a certificate in the user's keychain. +/// +/// \param[in] certData The certificate data in DER encoded format. +/// \param[in] certSize The size of the certData in bytes. +/// \return The status for the operation. +//**************************************************************************************************************************************************** +OSStatus removeCertificateTrust(char const* certData, uint64_t certSize) { + return _removeCertificateTrust(certFromData(certData, certSize)); } */ import "C" @@ -119,6 +248,116 @@ func certPEMToDER(certPEM []byte) ([]byte, error) { return block.Bytes, nil } +// wrapCGoCertCallReturningBool wrap call to a CGo function returning a bool. +// if the certificate is invalid the call will return false. +func wrapCGoCertCallReturningBool(certPEM []byte, fn func(*C.char, C.ulonglong) bool) bool { + certDER, err := certPEMToDER(certPEM) + if err != nil { + return false // error are ignored + } + + buffer := C.CBytes(certDER) + defer C.free(unsafe.Pointer(buffer)) //nolint:unconvert + + return fn((*C.char)(buffer), C.ulonglong(len(certDER))) +} + +// wrapCGoCertCallReturningBool wrap call to a CGo function returning an error +func wrapCGoCertCallReturningError(certPEM []byte, fn func(*C.char, C.ulonglong) error) error { + certDER, err := certPEMToDER(certPEM) + if err != nil { + return err + } + + buffer := C.CBytes(certDER) + defer C.free(unsafe.Pointer(buffer)) //nolint:unconvert + + return fn((*C.char)(buffer), C.ulonglong(len(certDER))) +} + +// isCertInKeychain returns true if the given certificate is stored in the user's keychain. +func isCertInKeychain(certPEM []byte) bool { + return wrapCGoCertCallReturningBool(certPEM, isCertInKeychainCGo) +} + +func isCertInKeychainCGo(buffer *C.char, size C.ulonglong) bool { + return bool(C.isCertificateInKeychain(buffer, size)) +} + +// addCertToKeychain adds a certificate to the user's keychain. +// Trying to add a certificate that is already in the keychain will result in an error. +func addCertToKeychain(certPEM []byte) error { + return wrapCGoCertCallReturningError(certPEM, addCertToKeychainCGo) +} + +func addCertToKeychainCGo(buffer *C.char, size C.ulonglong) error { + if errCode := C.addCertificateToKeychain(buffer, size); errCode != errSecSuccess { + return fmt.Errorf("could not add certificate to keychain (error %v)", errCode) + } + + return nil +} + +// removeCertFromKeychain removes a certificate from the user's keychain. +// Trying to remove a certificate that is not in the keychain will result in an error. +func removeCertFromKeychain(certPEM []byte) error { + return wrapCGoCertCallReturningError(certPEM, removeCertFromKeychainCGo) +} + +func removeCertFromKeychainCGo(buffer *C.char, size C.ulonglong) error { + if errCode := C.removeCertificateFromKeychain(buffer, size); errCode != errSecSuccess { + return fmt.Errorf("could not remove certificate from keychain (error %v)", errCode) + } + return nil +} + +// isCertTrusted check if a certificate is trusted in the user's keychain. +func isCertTrusted(certPEM []byte) bool { + return wrapCGoCertCallReturningBool(certPEM, isCertTrustedCGo) +} + +func isCertTrustedCGo(buffer *C.char, size C.ulonglong) bool { + return bool(C.isCertificateTrusted(buffer, size)) +} + +// setCertTrusted sets a certificate as trusted in the user's keychain. +// This function will trigger a security prompt from the system. +func setCertTrusted(certPEM []byte) error { + return wrapCGoCertCallReturningError(certPEM, setCertTrustedCGo) +} + +func setCertTrustedCGo(buffer *C.char, size C.ulonglong) error { + errCode := C.setCertificateTrusted(buffer, size) + switch errCode { + case errSecSuccess: + return nil + case errAuthorizationCanceled: + return ErrUserCanceledCertificateInstall + default: + return fmt.Errorf("could not set certificate trust in keychain (error %v)", errCode) + } +} + +// removeCertTrust remove the trust level of the certificated from the user's keychain. +// This function will trigger a security prompt from the system. +func removeCertTrust(certPEM []byte) error { + return wrapCGoCertCallReturningError(certPEM, removeCertTrustCGo) +} + +func removeCertTrustCGo(buffer *C.char, size C.ulonglong) error { + errCode := C.removeCertificateTrust(buffer, size) + switch errCode { + case errSecSuccess: + return nil + case errAuthorizationCanceled: + return ErrUserCanceledCertificateInstall + default: + return fmt.Errorf("could not set certificate trust in keychain (error %v)", errCode) + } +} + +// installCert installs a certificate in the keychain. The certificate is added to the keychain and it is set as trusted. +// This function will trigger a security prompt from the system, unless the certificate is already trusted in the user keychain. func installCert(certPEM []byte) error { certDER, err := certPEMToDER(certPEM) if err != nil { @@ -127,18 +366,24 @@ func installCert(certPEM []byte) error { p := C.CBytes(certDER) defer C.free(unsafe.Pointer(p)) //nolint:unconvert + buffer := (*C.char)(p) + size := C.ulonglong(len(certDER)) - errCode := C.installTrustedCert((*C.char)(p), (C.ulonglong)(len(certDER))) - switch errCode { - case errSecSuccess: - return nil - case errAuthorizationCanceled: - return fmt.Errorf("the user cancelled the authorization dialog") - default: - return fmt.Errorf("could not install certification into keychain (error %v)", errCode) + if !isCertInKeychainCGo(buffer, size) { + if err := addCertToKeychainCGo(buffer, size); err != nil { + return err + } } + + if !isCertTrustedCGo(buffer, size) { + return setCertTrustedCGo(buffer, size) + } + + return nil } +// uninstallCert uninstalls a certificate in the keychain. The certificate trust is removed and the certificated is deleted from the keychain. +// This function will trigger a security prompt from the system, unless the certificate is not trusted in the user keychain. func uninstallCert(certPEM []byte) error { certDER, err := certPEMToDER(certPEM) if err != nil { @@ -147,10 +392,32 @@ func uninstallCert(certPEM []byte) error { p := C.CBytes(certDER) defer C.free(unsafe.Pointer(p)) //nolint:unconvert + buffer := (*C.char)(p) + size := C.ulonglong(len(certDER)) - if errCode := C.removeTrustedCert((*C.char)(p), (C.ulonglong)(len(certDER))); errCode != 0 { - return fmt.Errorf("could not install certificate from keychain (error %v)", errCode) + if isCertTrustedCGo(buffer, size) { + if err := removeCertTrustCGo(buffer, size); err != nil { + return err + } + } + + if isCertInKeychainCGo(buffer, size) { + return removeCertFromKeychainCGo(buffer, size) } return nil } + +func isCertInstalled(certPEM []byte) bool { + certDER, err := certPEMToDER(certPEM) + if err != nil { + return false + } + + p := C.CBytes(certDER) + defer C.free(unsafe.Pointer(p)) //nolint:unconvert + buffer := (*C.char)(p) + size := C.ulonglong(len(certDER)) + + return isCertInKeychainCGo(buffer, size) && isCertTrustedCGo(buffer, size) +} diff --git a/internal/certs/cert_store_darwin_test.go b/internal/certs/cert_store_darwin_test.go index 3b1d419f..21cddbac 100644 --- a/internal/certs/cert_store_darwin_test.go +++ b/internal/certs/cert_store_darwin_test.go @@ -25,20 +25,73 @@ import ( "github.com/stretchr/testify/require" ) -// This test implies human interactions to enter password and is disabled by default. -func _TestTrustedCertsDarwin(t *testing.T) { //nolint:unused +func TestCertInKeychain(t *testing.T) { + // no trust settings change is performed, so this test will not trigger an OS security prompt. + certPEM := generatePEMCertificate(t) + require.False(t, isCertInKeychain(certPEM)) + require.NoError(t, addCertToKeychain(certPEM)) + require.True(t, isCertInKeychain(certPEM)) + require.Error(t, addCertToKeychain(certPEM)) + require.True(t, isCertInKeychain(certPEM)) + require.NoError(t, removeCertFromKeychain(certPEM)) + require.False(t, isCertInKeychain(certPEM)) + require.Error(t, removeCertFromKeychain(certPEM)) + require.False(t, isCertInKeychain(certPEM)) +} + +// This test require human interaction (macOS security prompts), and is disabled by default. +func _TestCertificateTrust(t *testing.T) { //nolint:unused + certPEM := generatePEMCertificate(t) + require.False(t, isCertTrusted(certPEM)) + require.NoError(t, addCertToKeychain(certPEM)) + require.NoError(t, setCertTrusted(certPEM)) + require.True(t, isCertTrusted(certPEM)) + require.NoError(t, removeCertTrust(certPEM)) + require.False(t, isCertTrusted(certPEM)) + require.NoError(t, removeCertFromKeychain(certPEM)) +} + +// This test require human interaction (macOS security prompts), and is disabled by default. +func _TestInstallAndRemove(t *testing.T) { //nolint:unused + certPEM := generatePEMCertificate(t) + + // fresh install + require.False(t, isCertInstalled(certPEM)) + require.NoError(t, installCert(certPEM)) + require.True(t, isCertInKeychain(certPEM)) + require.True(t, isCertTrusted(certPEM)) + require.True(t, isCertInstalled(certPEM)) + require.NoError(t, uninstallCert(certPEM)) + require.False(t, isCertInKeychain(certPEM)) + require.False(t, isCertTrusted(certPEM)) + require.False(t, isCertInstalled(certPEM)) + + // Install where certificate is already in Keychain, but not trusted. + require.NoError(t, addCertToKeychain(certPEM)) + require.False(t, isCertInstalled(certPEM)) + require.NoError(t, installCert(certPEM)) + require.True(t, isCertInstalled(certPEM)) + + // Install where certificate is already installed + require.NoError(t, installCert(certPEM)) + + // Remove when certificate is not trusted. + require.NoError(t, removeCertTrust(certPEM)) + require.NoError(t, uninstallCert(certPEM)) + require.False(t, isCertInstalled(certPEM)) + + // Remove when certificate has already been removed. + require.NoError(t, uninstallCert(certPEM)) + require.False(t, isCertTrusted(certPEM)) + require.False(t, isCertInKeychain(certPEM)) +} + +func generatePEMCertificate(t *testing.T) []byte { template, err := NewTLSTemplate() require.NoError(t, err) certPEM, _, err := GenerateCert(template) require.NoError(t, err) - require.Error(t, installCert([]byte{0})) // Cannot install an invalid cert. - require.Error(t, uninstallCert(certPEM)) // Cannot uninstall a cert that is not installed. - require.NoError(t, installCert(certPEM)) // Can install a valid cert. - require.NoError(t, installCert(certPEM)) // Can install an already installed cert. - require.NoError(t, uninstallCert(certPEM)) // Can uninstall an installed cert. - require.Error(t, uninstallCert(certPEM)) // Cannot uninstall an already uninstalled cert. - require.NoError(t, installCert(certPEM)) // Can reinstall an uninstalled cert. - require.NoError(t, uninstallCert(certPEM)) // Can uninstall a reinstalled cert. + return certPEM } diff --git a/internal/certs/cert_store_linux.go b/internal/certs/cert_store_linux.go index ed66925b..072816ee 100644 --- a/internal/certs/cert_store_linux.go +++ b/internal/certs/cert_store_linux.go @@ -24,3 +24,7 @@ func installCert([]byte) error { func uninstallCert([]byte) error { return nil // Linux doesn't have a root cert store. } + +func isCertInstalled([]byte) bool { + return false +} diff --git a/internal/certs/cert_store_windows.go b/internal/certs/cert_store_windows.go index 797559ad..fd647f5a 100644 --- a/internal/certs/cert_store_windows.go +++ b/internal/certs/cert_store_windows.go @@ -24,3 +24,7 @@ func installCert([]byte) error { func uninstallCert([]byte) error { return nil // NOTE(GODT-986): Uninstall certs from root cert store? } + +func isCertInstalled([]byte) bool { + return false +} diff --git a/internal/certs/installer.go b/internal/certs/installer.go index 25f09e3d..39ab0cf5 100644 --- a/internal/certs/installer.go +++ b/internal/certs/installer.go @@ -17,16 +17,50 @@ package certs -type Installer struct{} +import ( + "errors" + + "github.com/sirupsen/logrus" +) + +var ( + ErrUserCanceledCertificateInstall = errors.New("the user cancelled the authorization dialog") +) + +type Installer struct { + log *logrus.Entry +} func NewInstaller() *Installer { - return &Installer{} + return &Installer{ + log: logrus.WithField("pkg", "certs"), + } } func (installer *Installer) InstallCert(certPEM []byte) error { - return installCert(certPEM) + installer.log.Info("Installing the Bridge TLS certificate in the OS keychain") + + if err := installCert(certPEM); err != nil { + installer.log.WithError(err).Error("The Bridge TLS certificate could not be installed in the OS keychain") + return err + } + + installer.log.Info("The Bridge TLS certificate was successfully installed in the OS keychain") + return nil } func (installer *Installer) UninstallCert(certPEM []byte) error { - return uninstallCert(certPEM) + installer.log.Info("Uninstalling the Bridge TLS certificate from the OS keychain") + + if err := uninstallCert(certPEM); err != nil { + installer.log.WithError(err).Error("The Bridge TLS certificate could not be uninstalled from the OS keychain") + return err + } + + installer.log.Info("The Bridge TLS certificate was successfully uninstalled from the OS keychain") + return nil +} + +func (installer *Installer) IsCertInstalled(certPEM []byte) bool { + return isCertInstalled(certPEM) } diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.cpp index 205e0bbd..d7c0eb7c 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.cpp @@ -42,6 +42,7 @@ void GRPCQtProxy::connectSignals() { connect(this, &GRPCQtProxy::setIsTelemetryDisabledReceived, &settingsTab, &SettingsTab::setIsTelemetryDisabled); connect(this, &GRPCQtProxy::setColorSchemeNameReceived, &settingsTab, &SettingsTab::setColorSchemeName); connect(this, &GRPCQtProxy::reportBugReceived, &settingsTab, &SettingsTab::setBugReport); + connect(this, &GRPCQtProxy::installTLSCertificateReceived, &settingsTab, &SettingsTab::installTLSCertificate); connect(this, &GRPCQtProxy::exportTLSCertificatesReceived, &settingsTab, &SettingsTab::exportTLSCertificates); connect(this, &GRPCQtProxy::setIsStreamingReceived, &settingsTab, &SettingsTab::setIsStreaming); connect(this, &GRPCQtProxy::setClientPlatformReceived, &settingsTab, &SettingsTab::setClientPlatform); @@ -119,6 +120,13 @@ void GRPCQtProxy::reportBug(QString const &osType, QString const &osVersion, QSt } +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void GRPCQtProxy::installTLSCertificate() { + emit installTLSCertificateReceived(); +} + //**************************************************************************************************************************************************** /// \param[in] folderPath The folder path. //**************************************************************************************************************************************************** diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.h b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.h index 2f799eae..9d3ad154 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.h +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.h @@ -45,6 +45,7 @@ public: // member functions. void setColorSchemeName(QString const &name); ///< Forward a SetColorSchemeName call via a Qt Signal void reportBug(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, QString const &description, bool includeLogs); ///< Forwards a ReportBug call via a Qt signal. + void installTLSCertificate(); ///< Forwards a InstallTLScertificate call via a Qt signal. void exportTLSCertificates(QString const &folderPath); //< Forward an 'ExportTLSCertificates' call via a Qt signal. void setIsStreaming(bool isStreaming); ///< Forward a isStreaming internal messages via a Qt signal. void setClientPlatform(QString const &clientPlatform); ///< Forward a setClientPlatform call via a Qt signal. @@ -67,6 +68,7 @@ signals: void setColorSchemeNameReceived(QString const &name); ///< Forward a SetColorScheme call via a Qt Signal void reportBugReceived(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, QString const &description, bool includeLogs); ///< Signal for the ReportBug gRPC call + void installTLSCertificateReceived(); ///< Signal for the InstallTLSCertificate gRPC call. void exportTLSCertificatesReceived(QString const &folderPath); ///< Signal for the ExportTLSCertificates gRPC call. void setIsStreamingReceived(bool isStreaming); ///< Signal for the IsStreaming internal message. void setClientPlatformReceived(QString const &clientPlatform); ///< Signal for the SetClientPlatform gRPC call. diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp index dc0cfb75..ac171993 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp @@ -214,6 +214,16 @@ grpc::Status GRPCService::IsTelemetryDisabled(::grpc::ServerContext *, ::google: } +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::GoOs(ServerContext *, Empty const*, StringValue *response) { + response->set_value(app().mainWindow().settingsTab().os().toStdString()); + return Status::OK; +} + + //**************************************************************************************************************************************************** /// \return The status for the call. //**************************************************************************************************************************************************** @@ -361,22 +371,6 @@ Status GRPCService::ReportBug(ServerContext *, ReportBugRequest const *request, } -//**************************************************************************************************************************************************** -/// \param[in] request The request -//**************************************************************************************************************************************************** -Status GRPCService::ExportTLSCertificates(ServerContext *, StringValue const *request, Empty *response) { - SettingsTab &tab = app().mainWindow().settingsTab(); - if (!tab.nextTLSCertExportWillSucceed()) { - qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_CERT_EXPORT_ERROR)); - } - if (!tab.nextTLSKeyExportWillSucceed()) { - qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_KEY_EXPORT_ERROR)); - } - qtProxy_.exportTLSCertificates(QString::fromStdString(request->value())); - return Status::OK; -} - - //**************************************************************************************************************************************************** /// \param[in] request The request. /// \return The status for the call. @@ -406,7 +400,7 @@ Status GRPCService::Login(ServerContext *, LoginRequest const *request, Empty *) return Status::OK; } if (usersTab.nextUserTwoPasswordsRequired()) { - qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent()); + qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent(loginUsername_)); return Status::OK; } @@ -431,7 +425,7 @@ Status GRPCService::Login2FA(ServerContext *, LoginRequest const *request, Empty return Status::OK; } if (usersTab.nextUserTwoPasswordsRequired()) { - qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent()); + qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent(loginUsername_)); return Status::OK; } @@ -758,9 +752,86 @@ Status GRPCService::ConfigureUserAppleMail(ServerContext *, ConfigureAppleMailRe //**************************************************************************************************************************************************** /// \param[in] request The request -/// \param[in] writer The writer /// \return The status for the call. //**************************************************************************************************************************************************** +Status GRPCService::ExportTLSCertificates(ServerContext *, StringValue const *request, Empty *response) { + app().log().debug(__FUNCTION__); + SettingsTab &tab = app().mainWindow().settingsTab(); + if (!tab.nextTLSCertExportWillSucceed()) { + qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_CERT_EXPORT_ERROR)); + } + if (!tab.nextTLSKeyExportWillSucceed()) { + qtProxy_.sendDelayedEvent(newGenericErrorEvent(grpc::TLS_KEY_EXPORT_ERROR)); + } + qtProxy_.exportTLSCertificates(QString::fromStdString(request->value())); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] response The reponse. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::IsTLSCertificateInstalled(ServerContext *, const Empty *request, BoolValue *response) { + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().isTLSCertificateInstalled()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +Status GRPCService::InstallTLSCertificate(ServerContext *, Empty const *, Empty *) { + app().log().debug(__FUNCTION__); + SPStreamEvent event; + qtProxy_.installTLSCertificate(); + switch (app().mainWindow().settingsTab().nextTLSCertIntallResult()) { + case SettingsTab::TLSCertInstallResult::Success: + event = newCertificateInstallSuccessEvent(); + break; + case SettingsTab::TLSCertInstallResult::Canceled: + event = newCertificateInstallCanceledEvent(); + break; + default: + event = newCertificateInstallFailedEvent(); + break; + } + qtProxy_.sendDelayedEvent(event); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +//**************************************************************************************************************************************************** +Status GRPCService::KBArticleClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) { + app().log().debug(QString("%1 - URL = %2").arg(__FUNCTION__, QString::fromStdString(request->value()))); + return Status::OK; +} + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +Status GRPCService::ReportBugClicked(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) { + app().log().debug(__FUNCTION__); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +//**************************************************************************************************************************************************** +Status GRPCService::AutoconfigClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) { + app().log().debug(QString("%1 - Client = %2").arg(__FUNCTION__, QString::fromStdString(request->value()))); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request +/// \param[in] writer The writer +//**************************************************************************************************************************************************** Status GRPCService::RunEventStream(ServerContext *ctx, EventStreamRequest const *request, ServerWriter *writer) { app().log().debug(__FUNCTION__); { @@ -850,4 +921,3 @@ void GRPCService::finishLogin() { qtProxy_.sendDelayedEvent(newLoginFinishedEvent(user->id(), alreadyExist)); } - diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h index da5aa6c2..fe7ec6ab 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h @@ -53,6 +53,7 @@ public: // member functions. grpc::Status IsAllMailVisible(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; grpc::Status SetIsTelemetryDisabled(::grpc::ServerContext *, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override; grpc::Status IsTelemetryDisabled(::grpc::ServerContext *, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; + grpc::Status GoOs(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::StringValue *response) override; grpc::Status TriggerReset(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override; grpc::Status Version(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; grpc::Status LogsPath(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; @@ -64,7 +65,6 @@ public: // member functions. grpc::Status ColorSchemeName(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; grpc::Status CurrentEmailClient(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; grpc::Status ReportBug(::grpc::ServerContext *, ::grpc::ReportBugRequest const *request, ::google::protobuf::Empty *) override; - grpc::Status ExportTLSCertificates(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) override; grpc::Status ForceLauncher(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; grpc::Status SetMainExecutable(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; grpc::Status Login(::grpc::ServerContext *, ::grpc::LoginRequest const *request, ::google::protobuf::Empty *) override; @@ -93,6 +93,12 @@ public: // member functions. grpc::Status LogoutUser(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; grpc::Status RemoveUser(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; grpc::Status ConfigureUserAppleMail(::grpc::ServerContext *, ::grpc::ConfigureAppleMailRequest const *request, ::google::protobuf::Empty *) override; + grpc::Status IsTLSCertificateInstalled(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::BoolValue *response) override; + grpc::Status InstallTLSCertificate(::grpc::ServerContext *, ::google::protobuf::Empty const*, ::google::protobuf::Empty *) override; + grpc::Status ExportTLSCertificates(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; + grpc::Status ReportBugClicked(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Empty *) override; + grpc::Status AutoconfigClicked(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; + grpc::Status KBArticleClicked(::grpc::ServerContext *, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *) override; grpc::Status RunEventStream(::grpc::ServerContext *ctx, ::grpc::EventStreamRequest const *request, ::grpc::ServerWriter<::grpc::StreamEvent> *writer) override; grpc::Status StopEventStream(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override; bool sendEvent(bridgepp::SPStreamEvent const &event); ///< Queue an event for sending through the event stream. diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.cpp index a3d56a5a..4e839225 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.cpp @@ -285,11 +285,20 @@ void SettingsTab::setBugReport(QString const &osType, QString const &osVersion, } +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void SettingsTab::installTLSCertificate() { + ui_.labelLastTLSCertInstall->setText(QString("Last install: %1").arg(QDateTime::currentDateTime().toString(Qt::ISODateWithMs))); + ui_.checkTLSCertIsInstalled->setChecked(this->nextTLSCertIntallResult() == TLSCertInstallResult::Success); +} + + //**************************************************************************************************************************************************** /// \param[in] folderPath The folder path. //**************************************************************************************************************************************************** void SettingsTab::exportTLSCertificates(QString const &folderPath) { - ui_.labeLastTLSCertsExport->setText(QString("%1 Export to %2") + ui_.labeLastTLSCertExport->setText(QString("%1 Export to %2") .arg(QDateTime::currentDateTime().toString(Qt::ISODateWithMs)) .arg(folderPath)); } @@ -303,6 +312,22 @@ bool SettingsTab::nextBugReportWillSucceed() const { } +//**************************************************************************************************************************************************** +/// \return the state of the 'TLS Certificate is installed' check box. +//**************************************************************************************************************************************************** +bool SettingsTab::isTLSCertificateInstalled() const { + return ui_.checkTLSCertIsInstalled->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return The value for the 'Next TLS cert install result'. +//**************************************************************************************************************************************************** +SettingsTab::TLSCertInstallResult SettingsTab::nextTLSCertIntallResult() const { + return TLSCertInstallResult(ui_.comboNextTLSCertInstallResult->currentIndex()); +} + + //**************************************************************************************************************************************************** /// \return true if the 'Next TLS key export will succeed' check box is checked //**************************************************************************************************************************************************** @@ -505,4 +530,11 @@ void SettingsTab::resetUI() { ui_.comboCacheError->setCurrentIndex(0); ui_.checkAutomaticUpdate->setChecked(true); + + ui_.checkTLSCertIsInstalled->setChecked(false); + ui_.comboNextTLSCertInstallResult->setCurrentIndex(0); + ui_.checkTLSCertExportWillSucceed->setChecked(true); + ui_.checkTLSKeyExportWillSucceed->setChecked(true); + ui_.labeLastTLSCertExport->setText("Last export: never"); + ui_.labelLastTLSCertInstall->setText("Last install: never"); } diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.h b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.h index 2d082fc8..8412082d 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.h +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.h @@ -28,6 +28,13 @@ //**************************************************************************************************************************************************** class SettingsTab : public QWidget { Q_OBJECT +public: // data types. + enum class TLSCertInstallResult { + Success = 0, + Canceled = 1, + Failure = 2 + }; ///< Enumberation for the result of a TLS certificate installation. + public: // member functions. explicit SettingsTab(QWidget *parent = nullptr); ///< Default constructor. SettingsTab(SettingsTab const &) = delete; ///< Disabled copy-constructor. @@ -54,6 +61,8 @@ public: // member functions. QString dependencyLicenseLink() const; ///< Get the content of the 'Dependency License Link' edit. QString landingPageLink() const; ///< Get the content of the 'Landing Page Link' edit. bool nextBugReportWillSucceed() const; ///< Get the status of the 'Next Bug Report Will Fail' check box. + bool isTLSCertificateInstalled() const; ///< Get the status of the 'TLS Certificate is installed' check box. + TLSCertInstallResult nextTLSCertIntallResult() const; ///< Get the value of the 'Next TLS Certificate install result' combo box. bool nextTLSCertExportWillSucceed() const; ///< Get the status of the 'Next TLS Cert export will succeed' check box. bool nextTLSKeyExportWillSucceed() const; ///< Get the status of the 'Next TLS Key export will succeed' check box. QString hostname() const; ///< Get the value of the 'Hostname' edit. @@ -79,6 +88,7 @@ public slots: void setColorSchemeName(QString const &name); ///< Set the value for the 'Use Dark Theme' check box. void setBugReport(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, QString const &description, bool includeLogs); ///< Set the content of the bug report box. + void installTLSCertificate(); ///< Install the TLS certificate. void exportTLSCertificates(QString const &folderPath); ///< Export the TLS certificates. void setMailServerSettings(qint32 imapPort, qint32 smtpPort, bool useSSLForIMAP, bool useSSLForSMTP); ///< Change the mail server settings. void setIsDoHEnabled(bool enabled); ///< Set the value for the 'DoH Enabled' check box. diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.ui b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.ui index 7fb5b74e..964e0ce3 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.ui +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.ui @@ -370,7 +370,7 @@ - + 0 @@ -380,34 +380,81 @@ TLS Certficates - - - + + + - Last Export: Never + Certificate is installed + + + false - + - TLS certificate export will succeed + Certificate export will succeed true - + - TLS private key export will succeed + Key export will succeed true + + + + Last Export: never + + + + + + + Last install: never + + + + + + + + + Next install will + + + + + + + + Succeed + + + + + Be Canceled + + + + + Fail + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp index a3984ca9..d7b60760 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp @@ -40,7 +40,8 @@ using namespace bridgepp; namespace { - QString const bugReportFile = ":qml/Resources/bug_report_flow.json"; +QString const bugReportFile = ":qml/Resources/bug_report_flow.json"; +QString const bridgeKBUrl = "https://proton.me/support/bridge"; ///< The URL for the root of the bridge knowledge base. } @@ -278,6 +279,30 @@ void QMLBackend::clearAnswers() { } +//**************************************************************************************************************************************************** +/// \return true iff the Bridge TLS certificate is installed. +//**************************************************************************************************************************************************** +bool QMLBackend::isTLSCertificateInstalled() { + HANDLE_EXCEPTION_RETURN_BOOL( + bool v = false; + app().grpc().isTLSCertificateInstalled(v); + return v; + ) +} + + +//**************************************************************************************************************************************************** +/// \param[in] url The URL of the knowledge base article. If empty/invalid, the home page for the Bridge knowledge base is opened. +//**************************************************************************************************************************************************** +void QMLBackend::openKBArticle(QString const &url) { + HANDLE_EXCEPTION( + QString const u = url.isEmpty() ? bridgeKBUrl : url; + QDesktopServices::openUrl(u); + emit notifyKBArticleClicked(u); + ) +} + + //**************************************************************************************************************************************************** /// \return The value for the 'showOnStartup' property. //**************************************************************************************************************************************************** @@ -941,6 +966,15 @@ void QMLBackend::reportBug(QString const &category, QString const &description, } +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void QMLBackend::installTLSCertificate() { + HANDLE_EXCEPTION( + app().grpc().installTLSCertificate(); + ) +} + //**************************************************************************************************************************************************** // //**************************************************************************************************************************************************** @@ -1267,6 +1301,9 @@ void QMLBackend::connectGrpcEvents() { connect(client, &GRPCClient::reportBugSuccess, this, &QMLBackend::bugReportSendSuccess); connect(client, &GRPCClient::reportBugFallback, this, &QMLBackend::bugReportSendFallback); connect(client, &GRPCClient::reportBugError, this, &QMLBackend::bugReportSendError); + connect(client, &GRPCClient::certificateInstallSuccess, this, &QMLBackend::certificateInstallSuccess); + connect(client, &GRPCClient::certificateInstallCanceled, this, &QMLBackend::certificateInstallCanceled); + connect(client, &GRPCClient::certificateInstallFailed, this, &QMLBackend::certificateInstallFailed); connect(client, &GRPCClient::showMainWindow, [&]() { this->showMainWindow("gRPC showMainWindow event"); }); // cache events diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index f16e9c59..775d3e7e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -64,6 +64,8 @@ public: // member functions. Q_INVOKABLE QString getQuestionAnswer(quint8 questionId) const; ///< Get the answer for a given question. Q_INVOKABLE QString collectAnswers(quint8 categoryId) const; ///< Collect answer for a given set of questions. Q_INVOKABLE void clearAnswers(); ///< Clear all collected answers. + Q_INVOKABLE bool isTLSCertificateInstalled(); ///< Check if the bridge certificate is installed in the OS keychain. + Q_INVOKABLE void openKBArticle(QString const & url = QString()); ///< Open a knowledge base article. public: // Qt/QML properties. Note that the NOTIFY-er signal is required even for read-only properties (QML warning otherwise) Q_PROPERTY(bool showOnStartup READ showOnStartup NOTIFY showOnStartupChanged) @@ -195,6 +197,7 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge void installUpdate() const; ///< Slot for the update install. void triggerReset() const; ///< Slot for the triggering of reset. void reportBug(QString const &category, QString const &description, QString const &address, QString const &emailClient, bool includeLogs) const; ///< Slot for the bug report. + void installTLSCertificate(); ///< Installs the Bridge TLS certificate in the Keychain. void exportTLSCertificates() const; ///< Slot for the export of the TLS certificates. void onResetFinished(); ///< Slot for the reset finish signal. void onVersionChanged(); ///< Slot for the version change signal. @@ -231,7 +234,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML void login2FARequested(QString const &username); ///< Signal for the 'login2FARequested' gRPC stream event. void login2FAError(QString const &errorMsg); ///< Signal for the 'login2FAError' gRPC stream event. void login2FAErrorAbort(QString const &errorMsg); ///< Signal for the 'login2FAErrorAbort' gRPC stream event. - void login2PasswordRequested(); ///< Signal for the 'login2PasswordRequested' gRPC stream event. + void login2PasswordRequested(QString const &username); ///< Signal for the 'login2PasswordRequested' gRPC stream event. void login2PasswordError(QString const &errorMsg); ///< Signal for the 'login2PasswordError' gRPC stream event. void login2PasswordErrorAbort(QString const &errorMsg); ///< Signal for the 'login2PasswordErrorAbort' gRPC stream event. void loginFinished(int index, bool wasSignedOut); ///< Signal for the 'loginFinished' gRPC stream event. @@ -268,10 +271,13 @@ signals: // Signals received from the Go backend, to be forwarded to QML void bugReportSendSuccess(); ///< Signal for the 'bugReportSendSuccess' gRPC stream event. void bugReportSendFallback(); ///< Signal for the 'bugReportSendFallback' gRPC stream event. void bugReportSendError(); ///< Signal for the 'bugReportSendError' gRPC stream event. + void certificateInstallSuccess(); ///< Signal for the 'certificateInstallSuccess' gRPC stream event. + void certificateInstallCanceled(); ///< Signal for the 'certificateInstallCanceled' gRPC stream event. + void certificateInstallFailed(); /// Signal for the 'certificateInstallFailed' gRPC stream event. void showMainWindow(); ///< Signal for the 'showMainWindow' gRPC stream event. void hideMainWindow(); ///< Signal for the 'hideMainWindow' gRPC stream event. void showHelp(); ///< Signal for the 'showHelp' event (from the context menu). - void showSettings(); ///< Signal for the 'showHelp' event (from the context menu). + void showSettings(); ///< Signal for the 'showSettings' event (from the context menu). void selectUser(QString const& userID, bool forceShowWindow); ///< Signal emitted in order to selected a user with a given ID in the list. void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event. void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account. diff --git a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc index 8eed89d2..9403ac97 100644 --- a/internal/frontend/bridge-gui/bridge-gui/Resources.qrc +++ b/internal/frontend/bridge-gui/bridge-gui/Resources.qrc @@ -5,7 +5,6 @@ qml/AccountView.qml qml/Banner.qml qml/Bridge.qml - qml/bridgeqml.qmlproject qml/BugCategoryView.qml qml/BugQuestionView.qml qml/BugReportFlow.qml @@ -20,9 +19,11 @@ qml/icons/ic-alert.svg qml/icons/ic-apple-mail.svg qml/icons/ic-arrow-left.svg + qml/icons/ic-bridge.svg qml/icons/ic-card-identity.svg qml/icons/ic-check.svg qml/icons/ic-chevron-down.svg + qml/icons/ic-chevron-left.svg qml/icons/ic-chevron-right.svg qml/icons/ic-chevron-up.svg qml/icons/ic-cog-wheel.svg @@ -49,13 +50,18 @@ qml/icons/ic-success.svg qml/icons/ic-three-dots-vertical.svg qml/icons/ic-trash.svg + qml/icons/ic-warning-orange.svg + qml/icons/img-client-config-selector.svg + qml/icons/img-client-config-success.svg + qml/icons/img-macos-cert-screenshot.png + qml/icons/img-macos-profile-screenshot.png + qml/icons/img-mail-clients.svg + qml/icons/img-mail-logo-wordmark-dark.svg + qml/icons/img-mail-logo-wordmark.svg qml/icons/img-proton-logos.png qml/icons/img-proton-logos.svg qml/icons/img-splash.png qml/icons/img-splash.svg - qml/icons/img-welcome-dark.png - qml/icons/img-welcome-dark.svg - qml/icons/img-welcome.png qml/icons/img-welcome.svg qml/icons/Loader_16.svg qml/icons/Loader_48.svg @@ -75,6 +81,7 @@ qml/KeychainSettings.qml qml/LocalCacheSettings.qml qml/MainWindow.qml + qml/NoAccountView.qml qml/NotificationDialog.qml qml/NotificationPopups.qml qml/Notifications/Notification.qml @@ -90,6 +97,7 @@ qml/Proton/ComboBox.qml qml/Proton/Dialog.qml qml/Proton/Label.qml + qml/Proton/LinkLabel.qml qml/Proton/Menu.qml qml/Proton/MenuItem.qml qml/Proton/Popup.qml @@ -101,14 +109,26 @@ qml/Proton/TextField.qml qml/Proton/Toggle.qml qml/QuestionItem.qml - qml/Resources/bug_report_flow.json + qml/Resources/bug_report_flow.json + qml/Resources/Help/Template.html + qml/Resources/Help/WhyBridge.html + qml/Resources/Help/WhyCertificate.html + qml/Resources/Help/WhyProfileWarning.html qml/SettingsItem.qml qml/SettingsView.qml - qml/SetupGuide.qml - qml/SignIn.qml + qml/SetupWizard/ClientListItem.qml + qml/SetupWizard/LeftPane.qml + qml/SetupWizard/ClientConfigAppleMail.qml + qml/SetupWizard/ClientConfigEnd.qml + qml/SetupWizard/ClientConfigParameters.qml + qml/SetupWizard/ClientConfigSelector.qml + qml/SetupWizard/HelpButton.qml + qml/SetupWizard/SetupWizard.qml + qml/SetupWizard/Login.qml + qml/SetupWizard/Onboarding.qml + qml/SetupWizard/StepDescriptionBox.qml qml/ConnectionModeSettings.qml qml/SplashScreen.qml qml/Status.qml - qml/WelcomeGuide.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/UserList.cpp b/internal/frontend/bridge-gui/bridge-gui/UserList.cpp index 059d0b1e..b8a9946b 100644 --- a/internal/frontend/bridge-gui/bridge-gui/UserList.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/UserList.cpp @@ -262,7 +262,7 @@ void UserList::onUsedBytesChanged(QString const &userID, qint64 usedBytes) { void UserList::onSyncStarted(QString const &userID) { int const index = this->rowOfUserID(userID); if (index < 0) { - app().log().error(QString("Received onSyncStarted event for unknown userID %1").arg(userID)); + app().log().error(QString("Received syncStarted event for unknown userID %1").arg(userID)); return; } users_[index]->setIsSyncing(true); @@ -275,7 +275,7 @@ void UserList::onSyncStarted(QString const &userID) { void UserList::onSyncFinished(QString const &userID) { int const index = this->rowOfUserID(userID); if (index < 0) { - app().log().error(QString("Received onSyncFinished event for unknown userID %1").arg(userID)); + app().log().error(QString("Received syncFinished event for unknown userID %1").arg(userID)); return; } users_[index]->setIsSyncing(false); @@ -293,7 +293,7 @@ void UserList::onSyncProgress(QString const &userID, double progress, float elap Q_UNUSED(remainingMs) int const index = this->rowOfUserID(userID); if (index < 0) { - app().log().error(QString("Received onSyncFinished event for unknown userID %1").arg(userID)); + app().log().error(QString("Received syncProgress event for unknown userID %1").arg(userID)); return; } users_[index]->setSyncProgress(progress); diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml b/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml index 3f4bdfea..3b14bca6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml @@ -22,7 +22,7 @@ Item { LargeView } - property var _spacing: 12 * ProtonStyle.px + property var _spacing: 12 property ColorScheme colorScheme property color progressColor: { if (!root.enabled) @@ -154,7 +154,7 @@ Item { } } Item { - implicitHeight: root.type === AccountDelegate.LargeView ? 6 * ProtonStyle.px : 0 + implicitHeight: root.type === AccountDelegate.LargeView ? 6 : 0 } RowLayout { spacing: 0 @@ -222,15 +222,15 @@ Item { } } Item { - implicitHeight: root.type === AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0 + implicitHeight: root.type === AccountDelegate.LargeView ? 3 : 0 } Rectangle { id: progress_bar color: root.colorScheme.border_weak - height: 4 * ProtonStyle.px + height: 4 radius: ProtonStyle.progress_bar_radius visible: root.user ? root.type === AccountDelegate.LargeView : false - width: 140 * ProtonStyle.px + width: 140 Rectangle { id: progress_bar_filled diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml index ff196d43..d5469435 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/AccountView.qml @@ -23,13 +23,14 @@ Item { property int _detailsMargin: 25 property int _lineThickness: 1 property int _spacing: 20 + property int _buttonSpacing: 8 property int _topMargin: 32 property ColorScheme colorScheme property var notifications property var user - signal showSetupGuide(var user, string address) - signal showSignIn + signal showClientConfigurator(var user, string address, bool justLoggedIn) + signal showLogin(var username) Rectangle { anchors.fill: parent @@ -63,7 +64,7 @@ Item { // account delegate with action buttons Layout.fillWidth: true Layout.topMargin: _topMargin - + spacing: _buttonSpacing AccountDelegate { Layout.fillWidth: true colorScheme: root.colorScheme @@ -92,9 +93,9 @@ Item { visible: root.user ? (root.user.state === EUserState.SignedOut) : false onClicked: { - if (!root.user) - return; - root.showSignIn(); + if (user) { + root.showLogin(user.primaryEmailOrUsername()); + } } } Button { @@ -118,18 +119,18 @@ Item { } SettingsItem { Layout.fillWidth: true - actionText: qsTr("Configure") + actionText: qsTr("Configure email client") colorScheme: root.colorScheme description: qsTr("Using the mailbox details below (re)configure your client.") showSeparator: splitMode.visible text: qsTr("Email clients") - type: SettingsItem.Button - visible: _connected && (!root.user.splitMode) || (root.user.addresses.length === 1) + type: SettingsItem.PrimaryButton + visible: _connected && ((!root.user.splitMode) || (root.user.addresses.length === 1)) onClicked: { if (!root.user) return; - root.showSetupGuide(root.user, user.addresses[0]); + root.showClientConfigurator(root.user, user.addresses[0], false); } } SettingsItem { @@ -165,13 +166,13 @@ Item { } Button { colorScheme: root.colorScheme - secondary: true - text: qsTr("Configure") + secondary: false + text: qsTr("Configure email client") onClicked: { if (!root.user) return; - root.showSetupGuide(root.user, addressSelector.displayText); + root.showClientConfigurator(root.user, addressSelector.displayText, false); } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml index 1f1a6c4c..7bd9f4b6 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml @@ -40,7 +40,6 @@ QtObject { function onHideMainWindow() { mainWindow.hide(); } - target: Backend } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Configuration.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Configuration.qml index c162dcfc..5d63ef9e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Configuration.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Configuration.qml @@ -21,6 +21,7 @@ Rectangle { property int _margin: 24 property ColorScheme colorScheme + property bool highlightPassword property string hostname property string password property string port @@ -68,7 +69,8 @@ Rectangle { } ConfigurationItem { colorScheme: root.colorScheme - label: qsTr("Password") + label: highlightPassword ? qsTr("Use this password") : qsTr("Password") + labelColor: highlightPassword ? colorScheme.signal_warning_active : colorScheme.text_norm value: root.password } ConfigurationItem { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ConfigurationItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ConfigurationItem.qml index 55cf2634..dc52cf20 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ConfigurationItem.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ConfigurationItem.qml @@ -21,6 +21,7 @@ Item { property var colorScheme property string label + property string labelColor: root.colorScheme.text_norm property string value Layout.fillWidth: true @@ -35,9 +36,10 @@ Item { ColumnLayout { Label { + color: labelColor colorScheme: root.colorScheme text: root.label - type: Label.Body + type: Label.Body_semibold } TextEdit { id: valueText diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml index b5224262..e37e2596 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml @@ -24,7 +24,8 @@ Item { signal closeWindow signal quitBridge - signal showSetupGuide(var user, string address) + signal showClientConfigurator(var user, string address, bool justLoggedIn) + signal showLogin(var username) function selectUser(userID) { const users = Backend.users; @@ -35,11 +36,14 @@ Item { } accounts.currentIndex = i; if (user.state === EUserState.SignedOut) - showSignIn(user.primaryEmailOrUsername()); + showLogin(user.primaryEmailOrUsername()); return; } console.error("User with ID ", userID, " was not found in the account list"); } + function showBugReport() { + rightContent.showBugReport(); + } function showHelp() { rightContent.showHelpView(); } @@ -49,9 +53,9 @@ Item { function showSettings() { rightContent.showGeneralSettings(); } - function showSignIn(username) { - signIn.username = username; - rightContent.showSignIn(); + + function hasAccount() { + return Backend.users.count > 0 } RowLayout { @@ -190,6 +194,41 @@ Item { Layout.minimumHeight: 1 color: leftBar.colorScheme.border_weak } + Item { + id: noAccountBox + + Layout.fillHeight: true + Layout.fillWidth: true + Layout.topMargin: 24 + visible: !hasAccount() + + ColumnLayout { + anchors.fill: parent + spacing: 8 + + Label { + colorScheme: leftBar.colorScheme + color: colorScheme.text_weak + Layout.alignment: Qt.AlignHCenter + text: qsTr("No accounts") + } + Button { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + colorScheme: leftBar.colorScheme + text: qsTr("Add an account") + secondary: true + onClicked: root.showLogin("") + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + + ListView { id: accounts @@ -206,7 +245,7 @@ Item { clip: true model: Backend.users spacing: 12 - + visible: hasAccount() delegate: Item { implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin @@ -233,8 +272,7 @@ Item { if (user.state !== EUserState.SignedOut) { rightContent.showAccount(); } else { - signIn.username = user.primaryEmailOrUsername(); - rightContent.showSignIn(); + showLogin(user.primaryEmailOrUsername()); } } } @@ -282,8 +320,7 @@ Item { width: 36 onClicked: { - signIn.username = ""; - rightContent.showSignIn(); + root.showLogin(""); } } } @@ -323,65 +360,42 @@ Item { function showPortSettings() { rightContent.currentIndex = 4; } - function showSignIn() { - rightContent.currentIndex = 1; - signIn.focus = true; - } anchors.fill: parent - AccountView { + StackLayout { // 0 - colorScheme: root.colorScheme - notifications: root.notifications - user: { - if (accounts.currentIndex < 0) - return undefined; - if (Backend.users.count === 0) - return undefined; - return Backend.users.get(accounts.currentIndex); - } - - onShowSetupGuide: function (user, address) { - root.showSetupGuide(user, address); - } - onShowSignIn: { - const user = this.user; - signIn.username = user ? user.primaryEmailOrUsername() : ""; - rightContent.showSignIn(); - } - } - GridLayout { - // 1 Sign In - columns: 2 - - Button { - id: backButton - Layout.alignment: Qt.AlignTop - Layout.leftMargin: 18 - Layout.topMargin: 10 + currentIndex: hasAccount() ? 1 : 0 + NoAccountView { colorScheme: root.colorScheme - horizontalPadding: 8 - icon.source: "/qml/icons/ic-arrow-left.svg" - secondary: true - - onClicked: { - signIn.abort(); - rightContent.showAccount(); + onLinkClicked: function() { + root.showLogin("") } } - SignIn { - id: signIn - Layout.bottomMargin: 68 - Layout.fillHeight: true - Layout.fillWidth: true - Layout.leftMargin: 80 - backButton.width - 18 - Layout.preferredWidth: 320 - Layout.rightMargin: 80 - Layout.topMargin: 68 + AccountView { colorScheme: root.colorScheme + notifications: root.notifications + user: { + if (accounts.currentIndex < 0) + return undefined; + if (Backend.users.count === 0) + return undefined; + return Backend.users.get(accounts.currentIndex); + } + + onShowClientConfigurator: function (user, address, justLoggedIn) { + root.showClientConfigurator(user, address, justLoggedIn); + } + onShowLogin: function (username) { + root.showLogin(username); + } } } + Rectangle { + Layout.fillHeight: true + Layout.fillWidth: true + color: "#ff9900" + } GeneralSettings { // 2 colorScheme: root.colorScheme diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml index 546f947d..3f2199b1 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/HelpView.qml @@ -36,8 +36,7 @@ SettingsView { type: SettingsItem.PrimaryButton onClicked: { - Backend.notifyKBArticleClicked("https://proton.me/support/bridge"); - Qt.openUrlExternally("https://proton.me/support/bridge"); + Backend.openKBArticle(); } } SettingsItem { @@ -104,7 +103,7 @@ SettingsView { type: Label.Caption onLinkActivated: function (link) { - Qt.openUrlExternally(link); + Qt.openUrlExternally(link) } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index a16ebc52..a1e06bbd 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -17,14 +17,29 @@ import QtQuick.Layouts import QtQuick.Controls import Proton import Notifications +import "SetupWizard" ApplicationWindow { id: root - property int _defaultHeight: 780 - property int _defaultWidth: 1080 property var notifications + function layoutForUserCount(userCount) { + if (userCount === 0) { + contentLayout.currentIndex = 1; + setupWizard.showOnboarding(); + return; + } + const u = Backend.users.get(0); + if (!u) { + console.trace(); + return; + } + if ((userCount === 1) && (u.state === EUserState.SignedOut)) { + contentLayout.currentIndex = 1; + setupWizard.showLogin(u.primaryEmailOrUsername()); + } + } function selectUser(userID) { contentWrapper.selectUser(userID); } @@ -35,42 +50,42 @@ ApplicationWindow { root.requestActivate(); } } + function showClientConfigurator(user, address, justLoggedIn) { + contentLayout.currentIndex = 1; + setupWizard.showClientConfig(user, address, justLoggedIn); + } function showHelp() { contentWrapper.showHelp(); } function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings(); } + function showLogin(username = "") { + contentLayout.currentIndex = 1; + setupWizard.showLogin(username); + } function showSettings() { contentWrapper.showSettings(); } - function showSetup(user, address) { - setupGuide.user = user; - setupGuide.address = address; - setupGuide.reset(); - contentLayout._showSetup = !!setupGuide.user; - } - function showSignIn(username) { - if (contentLayout.currentIndex === 1) - return; - contentWrapper.showSignIn(username); - } colorScheme: ProtonStyle.currentStyle - height: _defaultHeight - minimumWidth: _defaultWidth + height: ProtonStyle.window_default_height + minimumHeight:ProtonStyle.window_minimum_height + minimumWidth: ProtonStyle.window_minimum_width visible: true - width: _defaultWidth + width: ProtonStyle.window_default_width + + Component.onCompleted: { + layoutForUserCount(Backend.users.count); + } // show Setup Guide on every new user Connections { function onRowsAboutToBeRemoved(parent, first, last) { for (let i = first; i <= last; i++) { const user = Backend.users.get(i); - if (setupGuide.user === user) { - setupGuide.user = null; - contentLayout._showSetup = false; - return; + if (setupWizard.user === user) { + setupWizard.closeWizard(); } } } @@ -83,65 +98,53 @@ ApplicationWindow { if (user.setupGuideSeen) { return; } - root.showSetup(user, user.addresses[0]); + root.showClientConfigurator(user, user.addresses[0], false); } target: Backend.users } Connections { - function onLoginFinished(index, wasSignedOut) { - const user = Backend.users.get(index); - if (user && !wasSignedOut) { - root.showSetup(user, user.addresses[0]); - } - console.debug("Login finished", index); - } function onSelectUser(userID, forceShowWindow) { contentWrapper.selectUser(userID); + if (setupWizard.visible) { + setupWizard.closeWizard() + } if (forceShowWindow) { root.showAndRise(); } } function onShowHelp() { root.showHelp(); + if (setupWizard.visible) { + setupWizard.closeWizard() + } + root.showAndRise(); } function onShowMainWindow() { root.showAndRise(); } function onShowSettings() { + if (setupWizard.visible) { + setupWizard.closeWizard() + } root.showSettings(); root.showAndRise(); } target: Backend } + Connections { + function onCountChanged(count) { + layoutForUserCount(count); + } + + target: Backend.users + } StackLayout { id: contentLayout - - property bool _showSetup: false - anchors.fill: parent - currentIndex: { - // show welcome when there are no users - if (Backend.users.count === 0) { - return 1; - } - const u = Backend.users.get(0); - if (!u) { - console.trace(); - console.log("empty user"); - return 1; - } - if ((Backend.users.count === 1) && (u.state === EUserState.SignedOut)) { - showSignIn(u.primaryEmailOrUsername()); - return 0; - } - if (contentLayout._showSetup) { - return 2; - } - return 0; - } + currentIndex: 0 ContentWrapper { // 0 @@ -160,30 +163,24 @@ ApplicationWindow { root.close(); Backend.quit(); } - onShowSetupGuide: function (user, address) { - root.showSetup(user, address); + onShowClientConfigurator: function (user, address, justLoggedIn) { + root.showClientConfigurator(user, address, justLoggedIn); + } + onShowLogin: function (username) { + root.showLogin(username); } } - WelcomeGuide { - Layout.fillHeight: true - Layout.fillWidth: true // 1 - colorScheme: root.colorScheme - } - SetupGuide { - // 2 - id: setupGuide + SetupWizard { + id: setupWizard Layout.fillHeight: true Layout.fillWidth: true colorScheme: root.colorScheme - onDismissed: { - root.showSetup(null, ""); + onBugReportRequested: { + contentWrapper.showBugReport(); } - onFinished: { - // TODO: Do not close window. Trigger Backend to check that - // there is a successfully connected client. Then Backend - // should send another signal to close the setup guide. - root.showSetup(null, ""); + onWizardEnded: { + contentLayout.currentIndex = 0; } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/NoAccountView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/NoAccountView.qml new file mode 100644 index 00000000..0dab4de4 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/NoAccountView.qml @@ -0,0 +1,56 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Proton +import "SetupWizard" + +Rectangle { + id: root + + property ColorScheme colorScheme + + color: root.colorScheme.background_norm + + signal linkClicked() + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + // we use the setup wizard left pane (onboarding version) + LeftPane { + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: true + Layout.preferredWidth: ProtonStyle.wizard_pane_width + colorScheme: root.colorScheme + wizard: setupWizard + + Component.onCompleted: { + showOnboarding(); + link1.setCallback(root.linkClicked, "Start setup", false) + } + } + Image { + id: mailLogoWithWordmark + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: ProtonStyle.wizard_window_margin + height: sourceSize.height + source: root.colorScheme.mail_logo_with_wordmark + sourceSize.height: 36 + sourceSize.width: 134 + width: sourceSize.width + } + } +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml index 94fcc9e6..4aacc22a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml @@ -453,7 +453,7 @@ QtObject { brief: title description: qsTr("Changing between split and combined address mode will require you to delete your account(s) from your email client and begin the setup process from scratch.") group: Notifications.Group.Configuration | Notifications.Group.Dialogs - icon: "/qml/icons/ic-question-circle.svg" + icon: "./icons/ic-question-circle.svg" title: qsTr("Enable split mode?") type: Notification.NotificationType.Warning @@ -788,8 +788,6 @@ QtObject { } } property Notification rebuildKeychain: Notification { - property var supportLink: "https://proton.me/support/bridge" - brief: title description: qsTr("Bridge is not able to access your macOS keychain. Please consult the instructions on our support page.") group: Notifications.Group.Dialogs | Notifications.Group.Configuration @@ -802,8 +800,7 @@ QtObject { text: qsTr("Open the support page") onTriggered: { - Backend.notifyKBArticleClicked(root.rebuildKeychain.supportLink); - Qt.openUrlExternally(root.rebuildKeychain.supportLink); + Backend.openKBArticle(); Backend.quit(); } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml index af085093..82817c76 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Button.qml @@ -23,11 +23,13 @@ T.Button { property bool borderless: false property ColorScheme colorScheme readonly property bool hasTextAndIcon: (control.text !== "") && (iconImage.source.toString().length > 0) + property bool iconOnTheLeft: false readonly property bool isIcon: control.text === "" property int labelType: Proton.Label.LabelType.Body property bool loading: false readonly property bool primary: !secondary property alias secondary: control.flat + property bool secondaryIsOpaque: false property alias textHorizontalAlignment: label.horizontalAlignment property alias textVerticalAlignment: label.verticalAlignment @@ -77,7 +79,7 @@ T.Button { if (control.loading) { return control.colorScheme.interaction_default_hover; } - return control.colorScheme.interaction_default; + return secondaryIsOpaque ? control.colorScheme.background_norm : control.colorScheme.interaction_default; } } else { if (primary) { @@ -103,7 +105,7 @@ T.Button { if (control.loading) { return control.colorScheme.interaction_default_hover; } - return control.colorScheme.interaction_default; + return secondaryIsOpaque ? control.colorScheme.background_norm : control.colorScheme.interaction_default; } } } @@ -115,6 +117,7 @@ T.Button { } contentItem: RowLayout { id: _contentItem + layoutDirection: iconOnTheLeft ? Qt.RightToLeft : Qt.LeftToRight spacing: control.hasTextAndIcon ? control.spacing : 0 Proton.Label { @@ -128,12 +131,13 @@ T.Button { return control.colorScheme.text_norm; } } - colorScheme: root.colorScheme + colorScheme: control.colorScheme elide: Text.ElideRight horizontalAlignment: Qt.AlignHCenter opacity: control.enabled || control.loading ? 1.0 : 0.5 text: control.text type: labelType + verticalAlignment: Text.AlignVCenter visible: !control.isIcon } ColorImage { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml index d795b7f4..b9f27354 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/ColorScheme.qml @@ -48,6 +48,7 @@ QtObject { property color interaction_weak_active property color interaction_weak_hover property string logo_img + property string mail_logo_with_wordmark // Primary property color primary_norm @@ -82,7 +83,4 @@ QtObject { // Text property color text_norm property color text_weak - - // Images - property string welcome_img } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml new file mode 100644 index 00000000..49db82ed --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/LinkLabel.qml @@ -0,0 +1,88 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +RowLayout { + id: root + + property var callback: null + property ColorScheme colorScheme + property bool external: false + property string link: "#" + property string text: "" + + function clear() { + root.callback = null; + root.text = ""; + root.link = ""; + root.external = false; + } + function link(url, text) { + return label.link(url, text); + } + function setCallback(callback, linkText, external) { + root.callback = callback; + root.text = linkText; + root.link = "#"; // Cannot be empty, otherwise the text is not an hyperlink. + root.external = external; + } + function setLink(linkURL, linkText, external) { + root.callback = null; + root.text = linkText; + root.link = linkURL; + root.external = external; + } + + Label { + id: label + Layout.alignment: Qt.AlignVCenter + colorScheme: root.colorScheme + text: label.link(root.link, root.text) + type: Label.LabelType.Body + + onLinkActivated: function (link) { + if ((link !== "#") && (link.length > 0)) { + Qt.openUrlExternally(link); + } + if (callback) { + callback(); + } + } + } + ColorImage { + Layout.alignment: Qt.AlignVCenter + color: label.linkColor + height: sourceSize.height + source: "/qml/icons/ic-external-link.svg" + sourceSize.height: 16 + sourceSize.width: 16 + visible: external + width: sourceSize.width + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + + onClicked: { + label.onLinkActivated(root.link); + } + } + } + HoverHandler { + acceptedDevices: PointerDevice.Mouse + cursorShape: Qt.PointingHandCursor + enabled: true + } +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml index 3e6379a3..95c6d431 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml @@ -20,21 +20,21 @@ import "." QtObject { id: root - property real account_hover_radius: 12 * root.px // px - property real account_row_radius: 12 * root.px // px - property real avatar_radius: 8 * root.px // px - property real banner_radius: 12 * root.px // px - property real big_avatar_radius: 12 * root.px // px + property int account_hover_radius: 12 + property int account_row_radius: 12 + property int avatar_radius: 8 + property int banner_radius: 12 + property int big_avatar_radius: 12 property int body_font_size: 14 - property real body_letter_spacing: 0.2 * root.px + property real body_letter_spacing: 0.2 property int body_line_height: 20 - property real button_radius: 8 * root.px // px + property int button_radius: 8 property int caption_font_size: 12 - property real caption_letter_spacing: 0.4 * root.px + property real caption_letter_spacing: 0.4 property int caption_line_height: 16 - property real card_radius: 12 * root.px // px - property real checkbox_radius: 4 * root.px // px - property real context_item_radius: 8 * root.px // px + property int card_radius: 12 + property int checkbox_radius: 4 + property int context_item_radius: 8 property ColorScheme currentStyle: lightStyle property ColorScheme darkProminentStyle: ColorScheme { id: _darkProminentStyle @@ -72,6 +72,7 @@ QtObject { interaction_weak_active: "#6D697D" interaction_weak_hover: "#5B576B" logo_img: "/qml/icons/product_logos_dark.svg" + mail_logo_with_wordmark: "/qml/icons/img-mail-logo-wordmark-dark.svg" // Primary primary_norm: "#8A6EFF" @@ -105,9 +106,6 @@ QtObject { // Text text_norm: "#FFFFFF" text_weak: "#A7A4B5" - - // Images - welcome_img: "/qml/icons/img-welcome-dark.png" } property ColorScheme darkStyle: ColorScheme { id: _darkStyle @@ -145,6 +143,7 @@ QtObject { interaction_weak_active: "#6D697D" interaction_weak_hover: "#5B576B" logo_img: "/qml/icons/product_logos_dark.svg" + mail_logo_with_wordmark: "/qml/icons/img-mail-logo-wordmark-dark.svg" // Primary primary_norm: "#8A6EFF" @@ -178,11 +177,8 @@ QtObject { // Text text_norm: "#FFFFFF" text_weak: "#A7A4B5" - - // Images - welcome_img: "/qml/icons/img-welcome-dark.png" } - property real dialog_radius: 12 * root.px // px + property int dialog_radius: 12 property int fontWeight_100: Font.Thin property int fontWeight_200: Font.Light property int fontWeight_300: Font.ExtraLight @@ -206,7 +202,7 @@ QtObject { } property int heading_font_size: 28 property int heading_line_height: 36 - property real input_radius: 8 * root.px // px + property int input_radius: 8 property int lead_font_size: 18 property int lead_line_height: 26 property ColorScheme lightProminentStyle: ColorScheme { @@ -245,6 +241,7 @@ QtObject { interaction_weak_active: "#8A6EFF" interaction_weak_hover: "#6D4AFF" logo_img: "/qml/icons/product_logos_dark.svg" + mail_logo_with_wordmark: "/qml/icons/img-mail-logo-wordmark-dark.svg" // Primary primary_norm: "#8A6EFF" @@ -278,9 +275,6 @@ QtObject { // Text text_norm: "#FFFFFF" text_weak: "#9282D4" - - // Images - welcome_img: "/qml/icons/img-welcome-dark.png" } // TODO: Once we will use Qt >=5.15 this should be refactored with inline components as follows: // https://doc.qt.io/qt-5/qtqml-documents-definetypes.html#inline-components @@ -325,6 +319,7 @@ QtObject { interaction_weak_active: "#A8A6A3" interaction_weak_hover: "#C2BFBC" logo_img: "/qml/icons/product_logos.svg" + mail_logo_with_wordmark: "/qml/icons/img-mail-logo-wordmark.svg" // Primary primary_norm: "#6D4AFF" @@ -358,13 +353,35 @@ QtObject { // Text text_norm: "#0C0C14" text_weak: "#706D6B" - - // Images - welcome_img: "/qml/icons/img-welcome.png" } - property real progress_bar_radius: 3 * root.px // px - property real px: 1.00 // px + property int progress_bar_radius: 3 property int title_font_size: 20 property int title_line_height: 24 - property real tooltip_radius: 8 * root.px // px + property int tooltip_radius: 8 + + // WebView overlay styling + property int web_view_button_width: 320 + property int web_view_corner_radius: 10 + property int web_view_overlay_button_vertical_margin: 10 + property int web_view_overlay_horizontal_padding: 10 + property int web_view_overlay_horizontal_margin: 250 + property int web_view_overlay_vertical_margin: 50 + property real web_view_overlay_opacity: 0.6 + property int web_view_overlay_vertical_padding: web_view_corner_radius + property int web_view_overley_border_width: 1 + + property int window_default_height: 780 + property int window_default_width: 1080 + property int window_minimum_height: 650 + property int window_minimum_width: window_default_width + + // setup wizard constant + property int wizard_pane_bottomMargin: 92 + property int wizard_pane_width: 364 + property int wizard_window_margin: 40 + property int wizard_spacing_extra_large: 32 + property int wizard_spacing_extra_small: 4 + property int wizard_spacing_large: 24 + property int wizard_spacing_medium: 16 + property int wizard_spacing_small: 8 } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextArea.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextArea.qml index 28679000..d0551c57 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextArea.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/TextArea.qml @@ -238,12 +238,12 @@ FocusScope { bottomPadding: 8 color: { if (!control.enabled) { - return root.colorScheme.text_disabled + return root.colorScheme.text_disabled; } if (control.readOnly) { - return root.colorScheme.text_hint + return root.colorScheme.text_hint; } - return root.colorScheme.text_norm + return root.colorScheme.text_norm; } // enforcing default focus here within component diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir index b8750786..a13b58f2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/qmldir @@ -28,6 +28,7 @@ CheckBox 4.0 CheckBox.qml ComboBox 4.0 ComboBox.qml Dialog 4.0 Dialog.qml Label 4.0 Label.qml +LinkLabel 4.0 LinkLabel.qml Menu 4.0 Menu.qml MenuItem 4.0 MenuItem.qml Popup 4.0 Popup.qml @@ -36,3 +37,4 @@ Switch 4.0 Switch.qml TextArea 4.0 TextArea.qml TextField 4.0 TextField.qml Toggle 4.0 Toggle.qml +WebFrame 4.0 WebFrame.qml diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/Template.html b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/Template.html new file mode 100644 index 00000000..b9fad96e --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/Template.html @@ -0,0 +1,16 @@ + + + + + + + + +%1 + + \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyBridge.html b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyBridge.html new file mode 100644 index 00000000..a26f4486 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyBridge.html @@ -0,0 +1,19 @@ +

Why do I need bridge?

+

+ Proton does not have access to the content of your messages, so it cannot share your unencrypted messages with your email client from the + Proton servers. +

+

+ Email clients such as Microsoft Outlook, Mozilla Thunderbird and Apple Mail use standard protocols named IMAP and SMTP to receive and send emails. +

+

+ Even though the IMAP and SMTP protocols can use secure channels (using SSL/TLS), they do not offer support for encrypted messages. + Because Proton does not have access to the content of your messages, it is not possible to configure your email client to connect directly to + Proton servers. +

+

+ The key to solving this problem is Bridge. Once installed on your computer and connected to your Proton account, Bridge can access your + encrypted messages stored on the Proton servers. Bridge integrates an IMAP and a SMTP server that run on your computer and are accessible only + to applications executing on your machine. Your email client connects to these local servers and Bridge is responsible for seamlessly encrypting + and decrypting the messages that you send and receive. +

diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyCertificate.html b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyCertificate.html new file mode 100644 index 00000000..045ab10b --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyCertificate.html @@ -0,0 +1,19 @@ +

Why do I need to install a certificate when configuring Apple Mail with Bridge?

+

+ Apple Mail requires a secure channel for communications with email servers, and the server needs to be acknowledged as trusted. +

+

+ In order to communicate with Bridge, Apple Mail requires secure connections using SSL/TLS. This cryptographic protocol includes an identity + verification system using certificates. For publicly available servers, certificates are normally issued and digitally signed by a certificate + authority, such as Let's Encrypt. This is not possible for Bridge, as the IMAP and SMTP servers are running on your own computer, and are not + accessible from any network (local or internet). +

+

+ The solution is to use a self-signed certificate. When setting up an email account where the server provides a self-signed certificate, most + email clients will issue a warning asking you whether you trust the server or not, because the certificate was not issued by a certificate + authority. +

+

+ Apple Mail requires an extra step. It will simply refuse to connect if the certificate is not set as trusted. Bridge solves this by storing this + certificate in the macOS keychain. This operation requires that you provide your macOS account password. +

diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyProfileWarning.html b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyProfileWarning.html new file mode 100644 index 00000000..3b426460 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/Help/WhyProfileWarning.html @@ -0,0 +1,21 @@ +

Why is there a warning sign when installing the Bridge profile on macOS?

+

+ This warning indicates that the certificate used to secure the communication channel between Bridge and your email client is not signed by a + trusted third party. +

+

+ In order to communicate with Bridge, Apple Mail requires secure connections using SSL/TLS. This cryptographic protocol includes an identity + verification system using certificates. For publicly available servers, certificates are normally issued and digitally signed by a certificate + authority, such as Let's Encrypt. This is not possible for Bridge, as the IMAP and SMTP servers are running on your own computer, and are not + accessible from any network (local or internet). +

+

+ The solution is to use a self-signed certificate. When setting up an email account where the server provides a self-signed certificate, most + email clients will issue a warning asking you whether you trust the server or not, because the certificate was not issued by a certificate + authority. The client has no way of verifying that the server is who it pretends to be. +

+

+ You can safely ignore this warning. The check concerns only the communication between your email client and Bridge, which occurs within your + computer. On the other end, the communication between Bridge and the Proton servers uses the HTTPS protocol, and the identity of the remote + server is verified by Bridge. +

diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml index ea0e6fc0..ecfa7621 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SettingsItem.qml @@ -90,7 +90,7 @@ Item { icon.source: root.actionIcon loading: root.loading secondary: root.type !== SettingsItem.PrimaryButton - text: root.actionText + (root.actionIcon !== "" ? " " : "") + text: root.actionText visible: root.type === SettingsItem.Button || root.type === SettingsItem.PrimaryButton onClicked: { diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupGuide.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupGuide.qml deleted file mode 100644 index 4cdfe43f..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SetupGuide.qml +++ /dev/null @@ -1,293 +0,0 @@ -// Copyright (c) 2023 Proton AG -// This file is part of Proton Mail Bridge. -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Controls.impl -import Proton - -Item { - id: root - - property string address - property ColorScheme colorScheme - property var user - - signal dismissed - signal finished - - function reset() { - guidePages.currentIndex = 0; - clientList.currentIndex = -1; - actionList.currentIndex = -1; - } - function setupAction(actionID, clientID) { - if (user) { - user.setupGuideSeen = true; - } - switch (actionID) { - case -1: - root.dismissed(); - break; // dismiss - case 0 // automatic - : - if (user) { - switch (clientID) { - case 0: - root.user.configureAppleMail(root.address); - Backend.notifyAutoconfigClicked("AppleMail"); - break; - } - } - root.finished(); - break; - case 1 // manual - : - let clientObj = clients.get(clientID); - if (clientObj !== undefined && clientObj.link !== "") { - Qt.openUrlExternally(clientObj.link); - Backend.notifyKBArticleClicked(clientObj.link); - } else { - console.log("unexpected client index", actionID, clientID); - } - root.finished(); - break; - default: - console.log("unexpected client setup action", actionID, clientID); - } - } - - implicitHeight: children[0].implicitHeight - implicitWidth: children[0].implicitWidth - - ListModel { - id: clients - - property bool haveAutoSetup: true - property string iconSource: "/qml/icons/ic-apple-mail.svg" - property string link: "https://proton.me/support/protonmail-bridge-clients-apple-mail" - property string name: "Apple Mail" - - Component.onCompleted: { - if (Backend.goos === "darwin") { - append({ - "name": "Apple Mail", - "iconSource": "/qml/icons/ic-apple-mail.svg", - "haveAutoSetup": true, - "link": "https://proton.me/support/protonmail-bridge-clients-apple-mail" - }); - append({ - "name": "Microsoft Outlook", - "iconSource": "/qml/icons/ic-microsoft-outlook.svg", - "haveAutoSetup": false, - "link": "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2019" - }); - } - if (Backend.goos === "windows") { - append({ - "name": "Microsoft Outlook", - "iconSource": "/qml/icons/ic-microsoft-outlook.svg", - "haveAutoSetup": false, - "link": "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019" - }); - } - append({ - "name": "Mozilla Thunderbird", - "iconSource": "/qml/icons/ic-mozilla-thunderbird.svg", - "haveAutoSetup": false, - "link": "https://proton.me/support/protonmail-bridge-clients-windows-thunderbird" - }); - append({ - "name": "Other", - "iconSource": "/qml/icons/ic-other-mail-clients.svg", - "haveAutoSetup": false, - "link": "https://proton.me/support/protonmail-bridge-configure-client" - }); - } - } - Rectangle { - anchors.fill: root - color: root.colorScheme.background_norm - } - StackLayout { - id: guidePages - anchors.bottomMargin: 70 - anchors.fill: parent - anchors.leftMargin: 80 - anchors.rightMargin: 80 - anchors.topMargin: 30 - - ColumnLayout { - // 0: Client selection - id: clientView - - property int columnWidth: 268 - - Layout.fillHeight: true - spacing: 8 - - Label { - colorScheme: root.colorScheme - text: qsTr("Setting up email client") - type: Label.LabelType.Heading - } - Label { - color: root.colorScheme.text_weak - colorScheme: root.colorScheme - text: address - type: Label.LabelType.Lead - } - RowLayout { - Layout.topMargin: 32 - clientView.spacing - spacing: 24 - - ColumnLayout { - id: clientColumn - Layout.alignment: Qt.AlignTop - - Label { - id: labelA - colorScheme: root.colorScheme - text: qsTr("Choose an email client") - type: Label.LabelType.Body_semibold - } - ListView { - id: clientList - Layout.fillHeight: true - model: clients - width: clientView.columnWidth - - delegate: Item { - implicitHeight: clientRow.height - implicitWidth: clientRow.width - - ColumnLayout { - id: clientRow - width: clientList.width - - RowLayout { - Layout.bottomMargin: 12 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.topMargin: 12 - - ColorImage { - height: 36 - source: model.iconSource - sourceSize.height: 36 - } - Label { - Layout.leftMargin: 12 - colorScheme: root.colorScheme - text: model.name - type: Label.LabelType.Body - } - } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: root.colorScheme.border_weak - } - } - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - - onClicked: { - clientList.currentIndex = index; - if (!model.haveAutoSetup) { - root.setupAction(1, index); - } - } - } - } - highlight: Rectangle { - color: root.colorScheme.interaction_default_active - radius: ProtonStyle.context_item_radius - } - } - } - ColumnLayout { - id: actionColumn - Layout.alignment: Qt.AlignTop - visible: clientList.currentIndex >= 0 && clients.get(clientList.currentIndex).haveAutoSetup - - Label { - colorScheme: root.colorScheme - text: qsTr("Choose configuration mode") - type: Label.LabelType.Body_semibold - } - ListView { - id: actionList - Layout.fillHeight: true - model: [qsTr("Configure automatically"), qsTr("Configure manually")] - width: clientView.columnWidth - - delegate: Item { - implicitHeight: children[0].height - implicitWidth: children[0].width - - ColumnLayout { - width: actionList.width - - Label { - Layout.bottomMargin: 20 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.topMargin: 20 - colorScheme: root.colorScheme - text: modelData - type: Label.LabelType.Body - } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: root.colorScheme.border_weak - } - } - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - - onClicked: { - actionList.currentIndex = index; - root.setupAction(index, clientList.currentIndex); - } - } - } - highlight: Rectangle { - color: root.colorScheme.interaction_default_active - radius: ProtonStyle.context_item_radius - } - } - } - } - Item { - Layout.fillHeight: true - } - Button { - colorScheme: root.colorScheme - flat: true - text: qsTr("Set up later") - - onClicked: { - root.setupAction(-1, -1); - if (user) { - user.setupGuideSeen = true; - } - root.dismissed(); - } - } - } - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml new file mode 100644 index 00000000..b2487d7a --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigAppleMail.qml @@ -0,0 +1,274 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +Item { + id: root + enum Screen { + CertificateInstall, + ProfileInstall + } + + property var wizard + + signal appleMailAutoconfigCertificateInstallPageShown + signal appleMailAutoconfigProfileInstallPageShow + + function showAutoconfig() { + if (Backend.isTLSCertificateInstalled()) { + showProfileInstall(); + } else { + showCertificateInstall(); + } + } + function showCertificateInstall() { + certificateInstall.reset(); + stack.currentIndex = ClientConfigAppleMail.Screen.CertificateInstall; + appleMailAutoconfigCertificateInstallPageShown(); + } + function showProfileInstall() { + profileInstall.reset(); + stack.currentIndex = ClientConfigAppleMail.Screen.ProfileInstall; + appleMailAutoconfigProfileInstallPageShow(); + } + + StackLayout { + id: stack + anchors.fill: parent + + // stack index 0 + Item { + id: certificateInstall + + property string errorString: "" + property bool showBugReportLink: false + property bool waitingForCert: false + + function clearError() { + errorString = ""; + showBugReportLink = false; + } + function reset() { + waitingForCert = false; + clearError(); + } + + Layout.fillHeight: true + Layout.fillWidth: true + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: ProtonStyle.wizard_spacing_large + + Connections { + function onCertificateInstallCanceled() { + certificateInstall.waitingForCert = false; + certificateInstall.errorString = qsTr("Apple Mail cannot be configured if you do not install the certificate. Please retry."); + certificateInstall.showBugReportLink = false; + } + function onCertificateInstallFailed() { + certificateInstall.waitingForCert = false; + certificateInstall.errorString = qsTr("An error occurred while installing the certificate."); + certificateInstall.showBugReportLink = true; + } + function onCertificateInstallSuccess() { + certificateInstall.reset(); + root.showAutoconfig(); + } + + target: Backend + } + ColumnLayout { + Layout.fillWidth: true + spacing: ProtonStyle.wizard_spacing_medium + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Install the bridge certificate") + type: Label.LabelType.Title + wrapMode: Text.WordWrap + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + color: colorScheme.text_weak + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("After clicking on the button below, a system pop-up will ask you for your credentials, please enter your macOS user credentials (not your Proton account’s) and validate.") + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + } + Image { + Layout.alignment: Qt.AlignHCenter + height: 182 + opacity: certificateInstall.waitingForCert ? 0.3 : 1.0 + source: "/qml/icons/img-macos-cert-screenshot.png" + width: 140 + } + ColumnLayout { + Layout.fillWidth: true + spacing: ProtonStyle.wizard_spacing_medium + + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !certificateInstall.waitingForCert + loading: certificateInstall.waitingForCert + text: qsTr("Install the certificate") + + onClicked: { + certificateInstall.clearError(); + certificateInstall.waitingForCert = true; + Backend.installTLSCertificate(); + } + } + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !certificateInstall.waitingForCert + secondary: true + text: qsTr("Cancel") + + onClicked: { + wizard.closeWizard(); + } + } + ColumnLayout { + Layout.fillWidth: true + spacing: ProtonStyle.wizard_spacing_small + + RowLayout { + Layout.fillWidth: true + spacing: ProtonStyle.wizard_spacing_extra_small + + ColorImage { + color: wizard.colorScheme.signal_danger + height: errorLabel.lineHeight + source: "/qml/icons/ic-exclamation-circle-filled.svg" + sourceSize.height: errorLabel.lineHeight + visible: certificateInstall.errorString.length > 0 + } + Label { + id: errorLabel + Layout.fillWidth: true + color: wizard.colorScheme.signal_danger + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: certificateInstall.errorString + type: Label.LabelType.Body_semibold + wrapMode: Text.WordWrap + } + } + LinkLabel { + Layout.alignment: Qt.AlignHCenter + callback: wizard.showBugReport + colorScheme: wizard.colorScheme + link: "#" + text: qsTr("Report the problem") + visible: certificateInstall.showBugReportLink + } + } + } + } + } + // stack index 1 + Item { + id: profileInstall + + property bool profilePaneLaunched: false + + function reset() { + profilePaneLaunched = false; + } + + Layout.fillHeight: true + Layout.fillWidth: true + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: ProtonStyle.wizard_spacing_large + + ColumnLayout { + Layout.fillWidth: true + spacing: ProtonStyle.wizard_spacing_medium + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Install the profile") + type: Label.LabelType.Title + wrapMode: Text.WordWrap + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + color: colorScheme.text_weak + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("A system pop-up will appear. Double click on the entry with your email, and click ’Install’ in the dialog that appears.") + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + } + Image { + Layout.alignment: Qt.AlignHCenter + height: 102 + source: "/qml/icons/img-macos-profile-screenshot.png" + width: 364 + } + ColumnLayout { + Layout.fillWidth: true + spacing: ProtonStyle.wizard_spacing_medium + + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + text: profileInstall.profilePaneLaunched ? qsTr("I have installed the profile") : qsTr("Install the profile") + + onClicked: { + if (profileInstall.profilePaneLaunched) { + wizard.showClientConfigEnd(); + } else { + wizard.user.configureAppleMail(wizard.address); + profileInstall.profilePaneLaunched = true; + } + } + } + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + secondary: true + text: qsTr("Cancel") + + onClicked: { + wizard.closeWizard(); + } + } + } + } + } + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml new file mode 100644 index 00000000..7302fb6f --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigEnd.qml @@ -0,0 +1,99 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +Rectangle { + id: root + + property ColorScheme colorScheme: wizard.colorScheme + property var wizard + + clip: true + color: colorScheme.background_norm + + Item { + id: centeredContainer + anchors.bottom: parent.bottom + anchors.bottomMargin: 84 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 32 + clip: true + width: ProtonStyle.wizard_pane_width + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: ProtonStyle.wizard_spacing_medium + + Image { + Layout.alignment: Qt.AlignHCenter + Layout.preferredHeight: sourceSize.height + Layout.preferredWidth: sourceSize.width + source: "/qml/icons/img-client-config-success.svg" + sourceSize.height: 104 + sourceSize.width: 190 + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Congratulations! You're all setup") + type: Label.LabelType.Title + wrapMode: Text.WordWrap + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + color: colorScheme.text_weak + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: wizard.address + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Your client has been configured. While complete synchronization might take some time, you can already send encrypted emails.") + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + Button { + Layout.fillWidth: true + colorScheme: root.colorScheme + text: qsTr("Done") + + onClicked: wizard.closeWizard() + } + } + } + Image { + id: mailLogoWithWordmark + anchors.bottom: parent.bottom + anchors.bottomMargin: 32 + anchors.horizontalCenter: parent.horizontalCenter + height: 36 + source: root.colorScheme.mail_logo_with_wordmark + sourceSize.height: height + sourceSize.width: width + width: 134 + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml new file mode 100644 index 00000000..924c4f1a --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigParameters.qml @@ -0,0 +1,163 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import ".." + +Rectangle { + id: root + + property ColorScheme colorScheme: wizard.colorScheme + readonly property bool genericClient: SetupWizard.Client.Generic === wizard.client + property var wizard + + clip: true + color: colorScheme.background_weak + + Item { + id: centeredContainer + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + width: 640 + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: ProtonStyle.wizard_spacing_medium + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Configure %1").arg(wizard.clientName()) + type: Label.LabelType.Title + wrapMode: Text.WordWrap + } + Rectangle { + Layout.fillWidth: true + border.color: colorScheme.border_norm + border.width: 1 + color: "transparent" + height: childrenRect.height + 2 * ProtonStyle.wizard_spacing_medium + radius: 12 + + RowLayout { + anchors.left: parent.left + anchors.margins: ProtonStyle.wizard_spacing_medium + anchors.right: parent.right + anchors.top: parent.top + spacing: ProtonStyle.wizard_spacing_small + + Label { + Layout.fillHeight: true + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignLeft + text: (SetupWizard.Client.MicrosoftOutlook === wizard.client) ? qsTr("Are you unsure about your Outlook version or do you need assistance in configuring Outlook?") : qsTr("Do you need assistance in configuring %1?".arg(wizard.clientName())) + type: Label.LabelType.Body + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + } + Button { + colorScheme: root.colorScheme + icon.source: "/qml/icons/ic-external-link.svg" + text: qsTr("Open guide") + + onClicked: function () { + Backend.openKBArticle(wizard.setupGuideLink()); + } + } + } + } + Rectangle { + Layout.fillWidth: true + border.color: colorScheme.signal_warning + border.width: 1 + color: "transparent" + height: childrenRect.height + 2 * ProtonStyle.wizard_spacing_medium + radius: ProtonStyle.banner_radius + + RowLayout { + anchors.left: parent.left + anchors.margins: ProtonStyle.wizard_spacing_medium + anchors.right: parent.right + anchors.top: parent.top + spacing: ProtonStyle.wizard_spacing_medium + + ColorImage { + id: image + height: 36 + source: "/qml/icons/ic-warning-orange.svg" + sourceSize.height: height + sourceSize.width: width + width: height + } + Label { + Layout.fillHeight: true + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignLeft + text: qsTr("Copy paste the provided configuration parameters. Use the password below (not your Proton password), when adding your Proton account to %1.".arg(wizard.clientName())) + type: Label.LabelType.Body + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + } + } + } + RowLayout { + id: configuration + Layout.fillWidth: true + spacing: ProtonStyle.wizard_spacing_extra_large + + Configuration { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + highlightPassword: true + hostname: Backend.hostname + password: wizard.user ? wizard.user.password : "" + port: Backend.imapPort.toString() + security: Backend.useSSLForIMAP ? "SSL" : "STARTTLS" + title: "IMAP" + username: wizard.address + } + Configuration { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + highlightPassword: true + hostname: Backend.hostname + password: wizard.user ? wizard.user.password : "" + port: Backend.smtpPort.toString() + security: Backend.useSSLForSMTP ? "SSL" : "STARTTLS" + title: "SMTP" + username: wizard.address + } + } + Button { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 304 + colorScheme: root.colorScheme + secondary: true + secondaryIsOpaque: true + text: qsTr("Continue") + + onClicked: wizard.showClientConfigEnd() + } + } + } +} + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml new file mode 100644 index 00000000..de062f35 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientConfigSelector.qml @@ -0,0 +1,101 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +Item { + id: root + + readonly property bool onMacOS: (Backend.goos === "darwin") + readonly property bool onWindows: (Backend.goos === "windows") + property var wizard + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: ProtonStyle.wizard_spacing_medium + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: ProtonStyle.wizard_spacing_medium + colorScheme: wizard.colorScheme + horizontalAlignment: Qt.AlignHCenter + text: qsTr("Select your email client") + type: Label.LabelType.Title + wrapMode: Text.WordWrap + } + ClientListItem { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + iconSource: "/qml/icons/ic-apple-mail.svg" + text: "Apple Mail" + visible: root.onMacOS + + onClicked: { + wizard.client = SetupWizard.Client.AppleMail; + wizard.showAppleMailAutoConfig(); + } + } + ClientListItem { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + iconSource: "/qml/icons/ic-microsoft-outlook.svg" + text: "Microsoft Outlook" + visible: root.onMacOS || root.onWindows + + onClicked: { + wizard.client = SetupWizard.Client.MicrosoftOutlook; + wizard.showClientParams(); + } + } + ClientListItem { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + iconSource: "/qml/icons/ic-mozilla-thunderbird.svg" + text: "Mozilla Thunderbird" + + onClicked: { + wizard.client = SetupWizard.Client.MozillaThunderbird; + wizard.showClientParams(); + } + } + ClientListItem { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + iconSource: "/qml/icons/ic-other-mail-clients.svg" + text: qsTr("Other") + + onClicked: { + wizard.client = SetupWizard.Client.Generic; + wizard.showClientParams(); + } + } + Button { + Layout.fillWidth: true + Layout.topMargin: 20 + colorScheme: wizard.colorScheme + secondary: true + secondaryIsOpaque: true + text: qsTr("Setup later") + + onClicked: { + root.wizard.closeWizard(); + } + } + } +} + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml new file mode 100644 index 00000000..1ffe6157 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/ClientListItem.qml @@ -0,0 +1,70 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +Rectangle { + id: root + + property ColorScheme colorScheme + property string iconSource + property string text + + signal clicked + + border.color: colorScheme.border_norm + border.width: 1 + color: { + if (mouseArea.pressed) { + return colorScheme.interaction_default_active; + } + if (mouseArea.containsMouse) { + return colorScheme.interaction_default_hover; + } + return colorScheme.background_norm; + } + height: 68 + radius: ProtonStyle.banner_radius + + RowLayout { + anchors.fill: parent + anchors.margins: ProtonStyle.wizard_spacing_medium + + ColorImage { + height: sourceSize.height + source: iconSource + sourceSize.height: 36 + } + Label { + Layout.fillWidth: true + Layout.leftMargin: 12 + colorScheme: root.colorScheme + horizontalAlignment: Text.AlignLeft + text: root.text + type: Label.LabelType.Body + verticalAlignment: Text.AlignVCenter + } + } + MouseArea { + id: mouseArea + acceptedButtons: Qt.LeftButton + anchors.fill: parent + hoverEnabled: true + + onClicked: { + root.clicked(); + } + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml new file mode 100644 index 00000000..8040793b --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/HelpButton.qml @@ -0,0 +1,65 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +Button { + id: root + + property var wizard + readonly property int _iconPadding: 8 // The SVG image we use has internal padding that we need to compensate for alignment. + readonly property int _iconSize: 24 + + anchors.bottom: parent.bottom + anchors.bottomMargin: ProtonStyle.wizard_window_margin - _iconPadding + anchors.right: parent.right + anchors.rightMargin: ProtonStyle.wizard_window_margin - _iconPadding + colorScheme: wizard.colorScheme + horizontalPadding: 0 + icon.color: wizard.colorScheme.text_weak + icon.height: _iconSize + icon.source: "/qml/icons/ic-question-circle.svg" + icon.width: _iconSize + verticalPadding: 0 + + onClicked: { + menu.popup(-menu.width + root.width, -menu.height); + } + + Menu { + id: menu + colorScheme: root.colorScheme + modal: true + + MenuItem { + id: getHelpItem + colorScheme: root.colorScheme + text: qsTr("Get help") + + onClicked: { + Backend.openKBArticle(); + } + } + MenuItem { + id: reportAProblemItem + colorScheme: root.colorScheme + text: qsTr("Report a problem") + + onClicked: { + wizard.showBugReport(); + } + } + } +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml new file mode 100644 index 00000000..e809aa98 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/LeftPane.qml @@ -0,0 +1,127 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +Item { + id: root + + property int iconHeight + property string iconSource + property int iconWidth + property var wizard + property ColorScheme colorScheme + property var _colorScheme: wizard ? wizard.colorScheme : colorScheme + property var link1: linkLabel1 + property var link2: linkLabel2 + + function showAppleMailAutoconfigCertificateInstall() { + showAppleMailAutoconfigCommon(); + descriptionLabel.text = qsTr("Apple Mail configuration is mostly automated, but in order to work, Bridge needs to install a certificate in your keychain."); + linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/apple-mail-certificate"); }, qsTr("Why is this certificate needed?"), true); + linkLabel2.clear(); + } + function showAppleMailAutoconfigCommon() { + titleLabel.text = ""; + linkLabel1.clear(); + linkLabel2.clear(); + iconSource = wizard.clientIconSource(); + iconHeight = 80; + iconWidth = 80; + } + function showAppleMailAutoconfigProfileInstall() { + showAppleMailAutoconfigCommon(); + descriptionLabel.text = qsTr("The final step before you can start using Apple Mail is to install the Bridge server profile in the system preferences.\n\nAdding a server profile is necessary to ensure that your Mac can receive and send Proton Mails."); + linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/macos-certificate-warning"); }, qsTr("Why is there a yellow warning sign?"), true); + linkLabel2.setCallback(wizard.showClientParams, qsTr("Configure Apple Mail manually"), false); + } + function showClientSelector(newAccount = true) { + titleLabel.text = ""; + descriptionLabel.text = newAccount ? qsTr("Bridge is now connected to Proton, and has already started downloading your messages. Let’s now connect your email client to Bridge.") : qsTr("Let’s connect your email client to Bridge."); + linkLabel1.clear(); + linkLabel2.clear(); + iconSource = "/qml/icons/img-client-config-selector.svg"; + iconHeight = 104; + iconWidth = 266; + } + function showLogin() { + showOnboarding(); + } + function showLogin2FA() { + showOnboarding(); + } + function showLoginMailboxPassword() { + showOnboarding(); + } + function showOnboarding() { + titleLabel.text = (Backend.users.count === 0) ? qsTr("Welcome to\nProton Mail Bridge") : qsTr("Add a Proton Mail account"); + descriptionLabel.text = qsTr("Bridge is the gateway between your Proton account and your email client. It runs in the background and encrypts and decrypts your messages seamlessly. "); + linkLabel1.setCallback(function() { Backend.openKBArticle("https://proton.me/support/why-you-need-bridge"); }, qsTr("Why do I need Bridge?"), true); + linkLabel2.clear(); + root.iconSource = "/qml/icons/img-welcome.svg"; + root.iconHeight = 148; + root.iconWidth = 265; + } + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: ProtonStyle.wizard_spacing_medium + + Image { + id: icon + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.preferredHeight: root.iconHeight + Layout.preferredWidth: root.iconWidth + source: root.iconSource + sourceSize.height: root.iconHeight + sourceSize.width: root.iconWidth + } + Label { + id: titleLabel + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: _colorScheme + horizontalAlignment: Text.AlignHCenter + text: "" + type: Label.LabelType.Heading + visible: text.length !== 0 + wrapMode: Text.WordWrap + } + Label { + id: descriptionLabel + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: _colorScheme + horizontalAlignment: Text.AlignHCenter + text: "" + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + LinkLabel { + id: linkLabel1 + Layout.alignment: Qt.AlignHCenter + colorScheme: _colorScheme + visible: (text !== "") + } + LinkLabel { + id: linkLabel2 + Layout.alignment: Qt.AlignHCenter + colorScheme: _colorScheme + visible: (text !== "") + } + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml new file mode 100644 index 00000000..75cd78e0 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Login.qml @@ -0,0 +1,477 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +FocusScope { + id: root + enum RootStack { + Login, + TOTP, + MailboxPassword + } + + property alias currentIndex: stackLayout.currentIndex + property alias username: usernameTextField.text + property var wizard + + signal loginAbort(string username, bool wasSignedOut) + + function abort() { + root.reset(); + loginAbort(usernameTextField.text, false); + Backend.loginAbort(usernameTextField.text); + } + function reset(clearUsername = false) { + stackLayout.currentIndex = Login.RootStack.Login; + loginLayout.reset(clearUsername); + totpLayout.reset(); + mailboxPasswordLayout.reset(); + if (username.length === 0) { + usernameTextField.forceActiveFocus(); + } else { + passwordTextField.forceActiveFocus(); + } + } + + StackLayout { + id: stackLayout + function loginFailed() { + signInButton.loading = false; + usernameTextField.enabled = true; + usernameTextField.error = true; + passwordTextField.enabled = true; + passwordTextField.error = true; + } + + anchors.fill: parent + + Connections { + function onLogin2FAError(_) { + console.assert(stackLayout.currentIndex === Login.RootStack.TOTP, "Unexpected login2FAError"); + twoFAButton.loading = false; + twoFactorPasswordTextField.enabled = true; + twoFactorPasswordTextField.error = true; + twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect"); + twoFactorPasswordTextField.focus = true; + } + function onLogin2FAErrorAbort(_) { + console.assert(stackLayout.currentIndex === Login.RootStack.TOTP, "Unexpected login2FAErrorAbort"); + root.reset(); + errorLabel.text = qsTr("Incorrect login credentials. Please try again."); + } + function onLogin2FARequested(username) { + console.assert(stackLayout.currentIndex === Login.RootStack.Login, "Unexpected login2FARequested"); + twoFactorUsernameLabel.text = username; + stackLayout.currentIndex = Login.RootStack.TOTP; + twoFactorPasswordTextField.focus = true; + } + function onLogin2PasswordError(_) { + console.assert(stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected login2PasswordError"); + secondPasswordButton.loading = false; + secondPasswordTextField.enabled = true; + secondPasswordTextField.error = true; + secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect"); + secondPasswordTextField.focus = true; + } + function onLogin2PasswordErrorAbort(_) { + console.assert(stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected login2PasswordErrorAbort"); + root.reset(); + errorLabel.text = qsTr("Incorrect login credentials. Please try again."); + } + function onLogin2PasswordRequested(username) { + console.assert(stackLayout.currentIndex === Login.RootStack.Login || stackLayout.currentIndex === Login.RootStack.TOTP, "Unexpected login2PasswordRequested"); + stackLayout.currentIndex = Login.RootStack.MailboxPassword; + mailboxPasswordUsernameLabel.text = username; + secondPasswordTextField.focus = true; + } + function onLoginAlreadyLoggedIn(_) { + stackLayout.currentIndex = Login.RootStack.Login; + root.reset(); + } + function onLoginConnectionError(_) { + if (stackLayout.currentIndex === Login.RootStack.Login) { + stackLayout.loginFailed(); + } + } + function onLoginFinished(_) { + stackLayout.currentIndex = Login.RootStack.Login; + root.reset(); + } + function onLoginFreeUserError() { + console.assert(stackLayout.currentIndex === Login.RootStack.Login, "Unexpected loginFreeUserError"); + stackLayout.loginFailed(); + } + function onLoginUsernamePasswordError(errorMsg) { + console.assert(stackLayout.currentIndex === Login.RootStack.Login, "Unexpected loginUsernamePasswordError"); + stackLayout.loginFailed(); + if (errorMsg !== "") + errorLabel.text = errorMsg; + else + errorLabel.text = qsTr("Incorrect login credentials"); + } + + target: Backend + } + Item { + ColumnLayout { + id: loginLayout + function clearErrors() { + usernameTextField.error = false; + usernameTextField.errorString = ""; + passwordTextField.error = false; + passwordTextField.errorString = ""; + errorLabel.text = ""; + } + function reset(clearUsername = false) { + signInButton.loading = false; + errorLabel.text = ""; + usernameTextField.enabled = true; + usernameTextField.focus = true; + if (clearUsername) { + usernameTextField.text = ""; + } + passwordTextField.enabled = true; + passwordTextField.text = ""; + clearErrors(); + } + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: ProtonStyle.wizard_spacing_medium + + ColumnLayout { + Layout.fillWidth: true + spacing: ProtonStyle.wizard_spacing_small + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Sign in") + type: Label.LabelType.Title + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + color: wizard.colorScheme.text_weak + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Enter your Proton Account details.") + type: Label.LabelType.Body + } + } + RowLayout { + Layout.fillWidth: true + spacing: 0 + + ColorImage { + color: wizard.colorScheme.signal_danger + height: errorLabel.lineHeight + source: "/qml/icons/ic-exclamation-circle-filled.svg" + sourceSize.height: errorLabel.lineHeight + visible: errorLabel.text.length > 0 + } + Label { + id: errorLabel + Layout.fillWidth: true + Layout.leftMargin: 4 + color: wizard.colorScheme.signal_danger + colorScheme: wizard.colorScheme + type: Label.LabelType.Caption_semibold + wrapMode: Text.WordWrap + } + } + TextField { + id: usernameTextField + Layout.fillWidth: true + colorScheme: wizard.colorScheme + focus: true + label: qsTr("Email or username") + validateOnEditingFinished: false + validator: function (str) { + if (str.length === 0) { + return qsTr("Enter email or username"); + } + } + + onAccepted: passwordTextField.forceActiveFocus() + onTextChanged: { + loginLayout.clearErrors(); + } + } + TextField { + id: passwordTextField + Layout.fillWidth: true + colorScheme: wizard.colorScheme + echoMode: TextInput.Password + label: qsTr("Password") + validateOnEditingFinished: false + validator: function (str) { + if (str.length === 0) { + return qsTr("Enter password"); + } + } + + onAccepted: signInButton.checkAndSignIn() + onTextChanged: { + loginLayout.clearErrors(); + } + } + Button { + id: signInButton + function checkAndSignIn() { + usernameTextField.validate(); + passwordTextField.validate(); + if (usernameTextField.error || passwordTextField.error) { + return; + } + usernameTextField.enabled = false; + passwordTextField.enabled = false; + loading = true; + Backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text)); + } + + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !loading + text: loading ? qsTr("Signing in") : qsTr("Sign in") + + onClicked: { + checkAndSignIn(); + } + } + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !signInButton.loading + secondary: true + secondaryIsOpaque: true + text: qsTr("Cancel") + + onClicked: { + root.abort(); + } + } + LinkLabel { + Layout.alignment: Qt.AlignHCenter + colorScheme: wizard.colorScheme + external: true + link: "https://proton.me/mail/pricing" + text: qsTr("Create or upgrade your account") + } + } + } + Item { + ColumnLayout { + id: totpLayout + function reset() { + twoFAButton.loading = false; + twoFactorPasswordTextField.enabled = true; + twoFactorPasswordTextField.error = false; + twoFactorPasswordTextField.errorString = ""; + twoFactorPasswordTextField.text = ""; + } + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: ProtonStyle.wizard_spacing_medium + + ColumnLayout { + Layout.fillWidth: true + spacing: ProtonStyle.wizard_spacing_small + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Two-factor authentication") + type: Label.LabelType.Title + } + Label { + id: twoFactorUsernameLabel + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + color: wizard.colorScheme.text_weak + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: "" + type: Label.LabelType.Body + } + } + Label { + id: descriptionLabel + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("You have enabled two-factor authentication. Please enter the 6-digit code provided by your authenticator application.") + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + TextField { + id: twoFactorPasswordTextField + Layout.fillWidth: true + colorScheme: wizard.colorScheme + label: qsTr("Two-factor code") + validateOnEditingFinished: false + validator: function (str) { + if (str.length === 0) { + return qsTr("Enter the 6-digit code"); + } + } + + onAccepted: { + twoFAButton.onClicked(); + } + onTextChanged: { + if (text.length >= 6) { + twoFAButton.onClicked(); + } + } + } + Button { + id: twoFAButton + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !loading + text: loading ? qsTr("Authenticating") : qsTr("Authenticate") + + onClicked: { + twoFactorPasswordTextField.validate(); + if (twoFactorPasswordTextField.error) { + return; + } + twoFactorPasswordTextField.enabled = false; + loading = true; + Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text)); + } + } + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !twoFAButton.loading + secondary: true + secondaryIsOpaque: true + text: qsTr("Cancel") + + onClicked: { + root.abort(); + } + } + } + } + Item { + ColumnLayout { + id: mailboxPasswordLayout + function reset() { + secondPasswordButton.loading = false; + secondPasswordTextField.enabled = true; + secondPasswordTextField.error = false; + secondPasswordTextField.errorString = ""; + secondPasswordTextField.text = ""; + } + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: ProtonStyle.wizard_spacing_medium + + ColumnLayout { + Layout.fillWidth: true + spacing: ProtonStyle.wizard_spacing_small + + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("Unlock your mailbox") + type: Label.LabelType.Title + } + Label { + id: mailboxPasswordUsernameLabel + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + color: wizard.colorScheme.text_weak + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: "" + type: Label.LabelType.Body + } + } + Label { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + horizontalAlignment: Text.AlignHCenter + text: qsTr("You have secured your account with a separate mailbox password.") + type: Label.LabelType.Body + wrapMode: Text.WordWrap + } + TextField { + id: secondPasswordTextField + Layout.fillWidth: true + colorScheme: wizard.colorScheme + echoMode: TextInput.Password + label: qsTr("Mailbox password") + validateOnEditingFinished: false + validator: function (str) { + if (str.length === 0) { + return qsTr("Enter password"); + } + } + + onAccepted: { + secondPasswordButton.onClicked(); + } + } + Button { + id: secondPasswordButton + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !loading + text: loading ? qsTr("Unlocking") : qsTr("Unlock") + + onClicked: { + secondPasswordTextField.validate(); + if (secondPasswordTextField.error) { + return; + } + secondPasswordTextField.enabled = false; + loading = true; + Backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text)); + } + } + Button { + Layout.fillWidth: true + colorScheme: wizard.colorScheme + enabled: !secondPasswordButton.loading + secondary: true + secondaryIsOpaque: true + text: qsTr("Cancel") + + onClicked: { + root.abort(); + } + } + } + } + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml new file mode 100644 index 00000000..e3ea88ae --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/Onboarding.qml @@ -0,0 +1,52 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +Item { + id: root + + property var wizard + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: ProtonStyle.wizard_spacing_large + + StepDescriptionBox { + colorScheme: wizard.colorScheme + description: qsTr("Connect Bridge to your Proton account") + icon: "/qml/icons/ic-bridge.svg" + iconSize: 48 + title: qsTr("Step 1") + } + StepDescriptionBox { + colorScheme: wizard.colorScheme + description: qsTr("Connect your email client to Bridge") + icon: "/qml/icons/img-mail-clients.svg" + iconSize: 48 + title: qsTr("Step 2") + } + Button { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + colorScheme: wizard.colorScheme + text: qsTr("Start setup") + + onClicked: wizard.showLogin() + } + } +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml new file mode 100644 index 00000000..ae384e0e --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/SetupWizard.qml @@ -0,0 +1,300 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +Item { + id: root + enum Client { + AppleMail, + MicrosoftOutlook, + MozillaThunderbird, + Generic + } + enum ContentStack { + Onboarding, + Login, + ClientConfigSelector, + ClientConfigAppleMail + } + enum RootStack { + TwoPanesView, + ClientConfigParameters, + ClientConfigEnd + } + + property string address + property var backAction: null + property int client + property ColorScheme colorScheme + property var user + + signal bugReportRequested + signal wizardEnded + + function _showClientConfig() { + showClientConfig(root.user, root.address, false); + } + function clientIconSource() { + switch (client) { + case SetupWizard.Client.AppleMail: + return "/qml/icons/ic-apple-mail.svg"; + case SetupWizard.Client.MicrosoftOutlook: + return "/qml/icons/ic-microsoft-outlook.svg"; + case SetupWizard.Client.MozillaThunderbird: + return "/qml/icons/ic-mozilla-thunderbird.svg"; + case SetupWizard.Client.Generic: + return "/qml/icons/ic-other-mail-clients.svg"; + default: + console.error("Unknown mail client " + client); + return "/qml/icons/ic-other-mail-clients.svg"; + } + } + function clientName() { + switch (client) { + case SetupWizard.Client.AppleMail: + return "Apple Mail"; + case SetupWizard.Client.MicrosoftOutlook: + return "Outlook"; + case SetupWizard.Client.MozillaThunderbird: + return "Thunderbird"; + case SetupWizard.Client.Generic: + return qsTr("your email client"); + default: + console.error("Unknown mail client " + client); + return qsTr("your email client"); + } + } + function closeWizard() { + wizardEnded(); + } + function setupGuideLink() { + switch (client) { + case SetupWizard.Client.AppleMail: + return "https://proton.me/support/protonmail-bridge-clients-apple-mail"; + case SetupWizard.Client.MicrosoftOutlook: + return (Backend.goos === "darwin") ? "https://proton.me/support/protonmail-bridge-clients-macos-outlook-2019" : "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019"; + case SetupWizard.Client.MozillaThunderbird: + return "https://proton.me/support/protonmail-bridge-clients-windows-thunderbird"; + default: + return "https://proton.me/support/protonmail-bridge-configure-client"; + } + } + function showAppleMailAutoConfig() { + backAction = _showClientConfig; + rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; + rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigAppleMail; + clientConfigAppleMail.showAutoconfig(); // This will trigger signals that will display the appropriate left content. + } + function showBugReport() { + closeWizard(); + bugReportRequested(); + } + function showClientConfig(user, address, justLoggedIn) { + backAction = null; + root.user = user; + root.address = address; + rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; + leftContent.showClientSelector(justLoggedIn); + rightContent.currentIndex = SetupWizard.ContentStack.ClientConfigSelector; + } + function showClientConfigEnd() { + backAction = null; + rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigEnd; + } + function showClientParams() { + backAction = _showClientConfig; + rootStackLayout.currentIndex = SetupWizard.RootStack.ClientConfigParameters; + } + function showLogin(username = "") { + backAction = null; + rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; + root.address = ""; + leftContent.showLogin(); + rightContent.currentIndex = SetupWizard.ContentStack.Login; + login.username = username; + login.reset(false); + } + function showOnboarding() { + backAction = null; + rootStackLayout.currentIndex = SetupWizard.RootStack.TwoPanesView; + root.address = ""; + root.user = null; + leftContent.showOnboarding(); + rightContent.currentIndex = SetupWizard.ContentStack.Onboarding; + } + + Connections { + function onLoginFinished(userIndex, wasSignedOut) { + if (wasSignedOut) { + closeWizard(); + return; + } + let user = Backend.users.get(userIndex); + let address = user ? user.addresses[0] : ""; + showClientConfig(user, address, true); + } + + target: Backend + } + StackLayout { + id: rootStackLayout + anchors.fill: parent + + // rootStackLayout index 0 + RowLayout { + Layout.fillHeight: true + Layout.fillWidth: true + spacing: 0 + + Rectangle { + id: leftHalf + Layout.fillHeight: true + Layout.fillWidth: true + color: root.colorScheme.background_norm + + LeftPane { + id: leftContent + anchors.bottom: parent.bottom + anchors.bottomMargin: ProtonStyle.wizard_pane_bottomMargin + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: ProtonStyle.wizard_window_margin + clip: true + width: ProtonStyle.wizard_pane_width + wizard: root + + Connections { + function onAppleMailAutoconfigCertificateInstallPageShown() { + leftContent.showAppleMailAutoconfigCertificateInstall(); + } + function onAppleMailAutoconfigProfileInstallPageShow() { + leftContent.showAppleMailAutoconfigProfileInstall(); + } + + target: clientConfigAppleMail + } + + Connections { + function onLogin2FARequested() { + leftContent.showLogin2FA(); + } + function onLogin2PasswordRequested() { + leftContent.showLoginMailboxPassword(); + } + + target: Backend + } + } + Image { + id: mailLogoWithWordmark + anchors.bottom: parent.bottom + anchors.bottomMargin: ProtonStyle.wizard_window_margin + anchors.horizontalCenter: parent.horizontalCenter + height: sourceSize.height + source: root.colorScheme.mail_logo_with_wordmark + sourceSize.height: 36 + sourceSize.width: 134 + width: sourceSize.width + } + } + Rectangle { + id: rightHalf + Layout.fillHeight: true + Layout.fillWidth: true + color: root.colorScheme.background_weak + + StackLayout { + id: rightContent + anchors.bottom: parent.bottom + anchors.bottomMargin: ProtonStyle.wizard_pane_bottomMargin + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: ProtonStyle.wizard_window_margin + clip: true + currentIndex: 0 + width: ProtonStyle.wizard_pane_width + + // rightContent stack index 0 + Onboarding { + wizard: root + } + + // rightContent tack index 1 + Login { + id: login + wizard: root + + onLoginAbort: { + root.closeWizard(); + } + } + + // rightContent stack index 2 + ClientConfigSelector { + id: clientConfigSelector + wizard: root + } + // rightContent stack index 3 + ClientConfigAppleMail { + id: clientConfigAppleMail + wizard: root + } + } + } + } + + // rootStackLayout index 1 + ClientConfigParameters { + id: clientConfigParameters + Layout.fillHeight: true + Layout.fillWidth: true + wizard: root + } + + // rootStackLayout index 2 + ClientConfigEnd { + id: clientConfigEnd + Layout.fillHeight: true + Layout.fillWidth: true + wizard: root + } + } + HelpButton { + wizard: root + } + Button { + id: backButton + anchors.left: parent.left + anchors.leftMargin: ProtonStyle.wizard_window_margin + anchors.top: parent.top + anchors.topMargin: ProtonStyle.wizard_window_margin + colorScheme: root.colorScheme + icon.source: "/qml/icons/ic-chevron-left.svg" + iconOnTheLeft: true + secondary: true + secondaryIsOpaque: true + spacing: ProtonStyle.wizard_spacing_small + text: qsTr("Back") + visible: backAction != null + + onClicked: { + if (backAction) { + backAction(); + } + } + } +} + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml new file mode 100644 index 00000000..375a279f --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/SetupWizard/StepDescriptionBox.qml @@ -0,0 +1,59 @@ +// Copyright (c) 2023 Proton AG +// This file is part of Proton Mail Bridge. +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . +import QtQml +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +RowLayout { + id: root + + property ColorScheme colorScheme + property string description + property string icon + property int iconSize: 64 + property string title + + spacing: ProtonStyle.wizard_spacing_large + + Image { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.preferredHeight: iconSize + Layout.preferredWidth: iconSize + mipmap: true + source: root.icon + } + ColumnLayout { + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true + spacing: ProtonStyle.wizard_spacing_small + + Label { + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillHeight: false + Layout.fillWidth: true + colorScheme: root.colorScheme + text: root.title + type: Label.LabelType.Body_bold + } + Label { + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillHeight: true + Layout.fillWidth: true + colorScheme: root.colorScheme + text: root.description + type: Label.LabelType.Body + verticalAlignment: Text.AlignTop + } + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/SignIn.qml b/internal/frontend/bridge-gui/bridge-gui/qml/SignIn.qml deleted file mode 100644 index 35278df0..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/SignIn.qml +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright (c) 2023 Proton AG -// This file is part of Proton Mail Bridge. -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . -import QtQml -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Controls.impl -import Proton - -FocusScope { - id: root - - property ColorScheme colorScheme - property alias currentIndex: stackLayout.currentIndex - property alias username: usernameTextField.text - - function abort() { - root.reset(); - Backend.loginAbort(usernameTextField.text); - } - function reset() { - stackLayout.currentIndex = 0; - loginNormalLayout.reset(); - login2FALayout.reset(); - login2PasswordLayout.reset(); - } - - implicitHeight: children[0].implicitHeight - implicitWidth: children[0].implicitWidth - state: "Page 1" - - states: [ - State { - name: "Page 1" - - PropertyChanges { - currentIndex: 0 - target: stackLayout - } - }, - State { - name: "Page 2" - - PropertyChanges { - currentIndex: 1 - target: stackLayout - } - }, - State { - name: "Page 3" - - PropertyChanges { - currentIndex: 2 - target: stackLayout - } - } - ] - - StackLayout { - id: stackLayout - function loginFailed() { - signInButton.loading = false; - usernameTextField.enabled = true; - usernameTextField.error = true; - passwordTextField.enabled = true; - passwordTextField.error = true; - } - - anchors.fill: parent - - Connections { - function onLogin2FAError(_) { - console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAError"); - twoFAButton.loading = false; - twoFactorPasswordTextField.enabled = true; - twoFactorPasswordTextField.error = true; - twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect"); - twoFactorPasswordTextField.focus = true; - } - function onLogin2FAErrorAbort(_) { - console.assert(stackLayout.currentIndex === 1, "Unexpected login2FAErrorAbort"); - root.reset(); - errorLabel.text = qsTr("Incorrect login credentials. Please try again."); - } - function onLogin2FARequested(username) { - console.assert(stackLayout.currentIndex === 0, "Unexpected login2FARequested"); - twoFactorUsernameLabel.text = username; - stackLayout.currentIndex = 1; - twoFactorPasswordTextField.focus = true; - } - function onLogin2PasswordError(_) { - console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordError"); - secondPasswordButton.loading = false; - secondPasswordTextField.enabled = true; - secondPasswordTextField.error = true; - secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect"); - secondPasswordTextField.focus = true; - } - function onLogin2PasswordErrorAbort(_) { - console.assert(stackLayout.currentIndex === 2, "Unexpected login2PasswordErrorAbort"); - root.reset(); - errorLabel.text = qsTr("Incorrect login credentials. Please try again."); - } - function onLogin2PasswordRequested() { - console.assert(stackLayout.currentIndex === 0 || stackLayout.currentIndex === 1, "Unexpected login2PasswordRequested"); - stackLayout.currentIndex = 2; - secondPasswordTextField.focus = true; - } - function onLoginAlreadyLoggedIn(_) { - stackLayout.currentIndex = 0; - root.reset(); - } - function onLoginConnectionError(_) { - if (stackLayout.currentIndex === 0) { - stackLayout.loginFailed(); - } - } - function onLoginFinished(_) { - stackLayout.currentIndex = 0; - root.reset(); - } - function onLoginFreeUserError() { - console.assert(stackLayout.currentIndex === 0, "Unexpected loginFreeUserError"); - stackLayout.loginFailed(); - } - function onLoginUsernamePasswordError(errorMsg) { - console.assert(stackLayout.currentIndex === 0, "Unexpected loginUsernamePasswordError"); - stackLayout.loginFailed(); - if (errorMsg !== "") - errorLabel.text = errorMsg; - else - errorLabel.text = qsTr("Incorrect login credentials"); - } - - target: Backend - } - ColumnLayout { - id: loginNormalLayout - function reset() { - signInButton.loading = false; - errorLabel.text = ""; - usernameTextField.enabled = true; - usernameTextField.error = false; - usernameTextField.errorString = ""; - usernameTextField.focus = true; - passwordTextField.enabled = true; - passwordTextField.error = false; - passwordTextField.errorString = ""; - passwordTextField.text = ""; - } - - spacing: 0 - - Label { - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 16 - colorScheme: root.colorScheme - text: qsTr("Sign in") - type: Label.LabelType.Title - } - Label { - id: subTitle - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 8 - color: root.colorScheme.text_weak - colorScheme: root.colorScheme - text: qsTr("Enter your Proton Account details.") - type: Label.LabelType.Body - } - RowLayout { - Layout.fillWidth: true - Layout.topMargin: 36 - spacing: 0 - visible: errorLabel.text.length > 0 - - ColorImage { - color: root.colorScheme.signal_danger - height: errorLabel.lineHeight - source: "/qml/icons/ic-exclamation-circle-filled.svg" - sourceSize.height: errorLabel.lineHeight - } - Label { - id: errorLabel - Layout.fillWidth: true - Layout.leftMargin: 4 - color: root.colorScheme.signal_danger - colorScheme: root.colorScheme - type: root.error ? Label.LabelType.Caption_semibold : Label.LabelType.Caption - wrapMode: Text.WordWrap - } - } - TextField { - id: usernameTextField - Layout.fillWidth: true - Layout.topMargin: 24 - colorScheme: root.colorScheme - focus: true - label: qsTr("Email or username") - validateOnEditingFinished: false - validator: function (str) { - if (str.length === 0) { - return qsTr("Enter email or username"); - } - } - - onAccepted: passwordTextField.forceActiveFocus() - onTextChanged: { - // remove "invalid username / password error" - if (error || errorLabel.text.length > 0) { - errorLabel.text = ""; - usernameTextField.error = false; - passwordTextField.error = false; - } - } - } - TextField { - id: passwordTextField - Layout.fillWidth: true - Layout.topMargin: 8 - colorScheme: root.colorScheme - echoMode: TextInput.Password - label: qsTr("Password") - validateOnEditingFinished: false - validator: function (str) { - if (str.length === 0) { - return qsTr("Enter password"); - } - } - - onAccepted: signInButton.checkAndSignIn() - onTextChanged: { - // remove "invalid username / password error" - if (error || errorLabel.text.length > 0) { - errorLabel.text = ""; - usernameTextField.error = false; - passwordTextField.error = false; - } - } - } - Button { - id: signInButton - function checkAndSignIn() { - usernameTextField.validate(); - passwordTextField.validate(); - if (usernameTextField.error || passwordTextField.error) { - return; - } - usernameTextField.enabled = false; - passwordTextField.enabled = false; - loading = true; - Backend.login(usernameTextField.text, Qt.btoa(passwordTextField.text)); - } - - Layout.fillWidth: true - Layout.topMargin: 24 - colorScheme: root.colorScheme - enabled: !loading - text: loading ? qsTr("Signing in") : qsTr("Sign in") - - onClicked: { - checkAndSignIn(); - } - } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 24 - colorScheme: root.colorScheme - text: link("https://proton.me/mail/pricing", qsTr("Create or upgrade your account")) - textFormat: Text.StyledText - type: Label.LabelType.Body - - onLinkActivated: { - Qt.openUrlExternally(link); - } - } - } - ColumnLayout { - id: login2FALayout - function reset() { - twoFAButton.loading = false; - twoFactorPasswordTextField.enabled = true; - twoFactorPasswordTextField.error = false; - twoFactorPasswordTextField.errorString = ""; - twoFactorPasswordTextField.text = ""; - } - - spacing: 0 - - Label { - Layout.alignment: Qt.AlignCenter - Layout.topMargin: 16 - colorScheme: root.colorScheme - text: qsTr("Two-factor authentication") - type: Label.LabelType.Heading - } - Label { - id: twoFactorUsernameLabel - Layout.alignment: Qt.AlignCenter - Layout.topMargin: 8 - color: root.colorScheme.text_weak - colorScheme: root.colorScheme - type: Label.LabelType.Lead - } - TextField { - id: twoFactorPasswordTextField - Layout.fillWidth: true - Layout.topMargin: 32 - assistiveText: qsTr("Enter the 6-digit code") - colorScheme: root.colorScheme - label: qsTr("Two-factor code") - validateOnEditingFinished: false - validator: function (str) { - if (str.length === 0) { - return qsTr("Enter the 6-digit code"); - } - } - - onAccepted: { - twoFAButton.onClicked(); - } - onTextChanged: { - if (text.length >= 6) { - twoFAButton.onClicked(); - } - } - } - Button { - id: twoFAButton - Layout.fillWidth: true - Layout.topMargin: 24 - colorScheme: root.colorScheme - enabled: !loading - text: loading ? qsTr("Authenticating") : qsTr("Authenticate") - - onClicked: { - twoFactorPasswordTextField.validate(); - if (twoFactorPasswordTextField.error) { - return; - } - twoFactorPasswordTextField.enabled = false; - loading = true; - Backend.login2FA(usernameTextField.text, Qt.btoa(twoFactorPasswordTextField.text)); - } - } - } - ColumnLayout { - id: login2PasswordLayout - function reset() { - secondPasswordButton.loading = false; - secondPasswordTextField.enabled = true; - secondPasswordTextField.error = false; - secondPasswordTextField.errorString = ""; - secondPasswordTextField.text = ""; - } - - spacing: 0 - - Label { - Layout.alignment: Qt.AlignCenter - Layout.topMargin: 16 - colorScheme: root.colorScheme - text: qsTr("Unlock your mailbox") - type: Label.LabelType.Heading - } - TextField { - id: secondPasswordTextField - Layout.fillWidth: true - Layout.topMargin: 8 + implicitHeight + 24 + subTitle.implicitHeight - colorScheme: root.colorScheme - echoMode: TextInput.Password - label: qsTr("Mailbox password") - validateOnEditingFinished: false - validator: function (str) { - if (str.length === 0) { - return qsTr("Enter password"); - } - } - - onAccepted: { - secondPasswordButton.onClicked(); - } - } - Button { - id: secondPasswordButton - Layout.fillWidth: true - Layout.topMargin: 24 - colorScheme: root.colorScheme - enabled: !loading - text: loading ? qsTr("Unlocking") : qsTr("Unlock") - - onClicked: { - secondPasswordTextField.validate(); - if (secondPasswordTextField.error) { - return; - } - secondPasswordTextField.enabled = false; - loading = true; - Backend.login2Password(usernameTextField.text, Qt.btoa(secondPasswordTextField.text)); - } - } - } - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml b/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml deleted file mode 100644 index 8be55925..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/WelcomeGuide.qml +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) 2023 Proton AG -// This file is part of Proton Mail Bridge. -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . -import QtQml -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import Proton - -Item { - id: root - - property ColorScheme colorScheme - - implicitHeight: children[0].implicitHeight - implicitWidth: children[0].implicitWidth - - RowLayout { - anchors.fill: parent - spacing: 0 - - states: [ - State { - name: "Page 1" - - PropertyChanges { - currentIndex: 0 - target: signInItem - } - }, - State { - name: "Page 2" - - PropertyChanges { - currentIndex: 1 - target: signInItem - } - }, - State { - name: "Page 3" - - PropertyChanges { - currentIndex: 2 - target: signInItem - } - } - ] - - Rectangle { - Layout.fillHeight: true - Layout.fillWidth: true - color: root.colorScheme.background_norm - implicitHeight: children[0].implicitHeight - implicitWidth: children[0].implicitWidth - visible: signInItem.currentIndex === 0 - - GridLayout { - anchors.fill: parent - columnSpacing: 0 - columns: 3 - rowSpacing: 0 - - // top margin - Item { - Layout.columnSpan: 3 - Layout.fillWidth: true - - // Using binding component here instead of direct binding to avoid binding loop during construction of element - Binding on Layout.preferredHeight { - value: (parent.height - welcomeContentItem.height) / 4 - } - } - - // left margin - Item { - Layout.fillWidth: true - Layout.maximumWidth: 80 - Layout.minimumWidth: 48 - Layout.preferredHeight: welcomeContentItem.height - } - ColumnLayout { - id: welcomeContentItem - Layout.fillWidth: true - spacing: 0 - - Image { - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 16 - source: colorScheme.welcome_img - sourceSize.height: 148 - sourceSize.width: 264 - } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - Layout.topMargin: 16 - colorScheme: root.colorScheme - horizontalAlignment: Text.AlignHCenter - text: qsTr("Welcome to\nProton Mail Bridge") - type: Label.LabelType.Heading - } - Label { - id: longTextLabel - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - Layout.preferredWidth: 320 - Layout.topMargin: 16 - colorScheme: root.colorScheme - horizontalAlignment: Text.AlignHCenter - text: qsTr("Add your Proton Mail account to securely access and manage your messages in your favorite email client. Bridge runs in the background and encrypts and decrypts your messages seamlessly.") - type: Label.LabelType.Body - wrapMode: Text.WordWrap - } - } - - // Right margin - Item { - Layout.fillWidth: true - Layout.maximumWidth: 80 - Layout.minimumWidth: 48 - Layout.preferredHeight: welcomeContentItem.height - } - - // bottom margin - Item { - Layout.columnSpan: 3 - Layout.fillHeight: true - Layout.fillWidth: true - implicitHeight: children[0].implicitHeight + children[0].anchors.bottomMargin + children[0].anchors.topMargin - implicitWidth: children[0].implicitWidth - - Image { - id: logoImage - anchors.bottom: parent.bottom - anchors.bottomMargin: 48 - anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 48 - source: colorScheme.logo_img - sourceSize.height: 25 - sourceSize.width: 200 - } - } - } - } - Rectangle { - Layout.fillHeight: true - Layout.fillWidth: true - color: (signInItem.currentIndex == 0) ? root.colorScheme.background_weak : root.colorScheme.background_norm - implicitHeight: children[0].implicitHeight - implicitWidth: children[0].implicitWidth - - RowLayout { - anchors.fill: parent - spacing: 0 - - Item { - Layout.fillHeight: true - Layout.fillWidth: true - Layout.preferredWidth: signInItem.currentIndex == 0 ? 0 : parent.width / 4 - implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin - implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin - - Button { - anchors.bottom: parent.bottom - anchors.bottomMargin: 80 - anchors.left: parent.left - anchors.leftMargin: 80 - anchors.rightMargin: 80 - anchors.topMargin: 80 - colorScheme: root.colorScheme - secondary: true - text: qsTr("Back") - visible: signInItem.currentIndex != 0 - - onClicked: { - signInItem.abort(); - } - } - } - GridLayout { - Layout.fillHeight: true - Layout.fillWidth: true - columnSpacing: 0 - columns: 3 - rowSpacing: 0 - - // top margin - Item { - Layout.columnSpan: 3 - Layout.fillWidth: true - - // Using binding component here instead of direct binding to avoid binding loop during construction of element - Binding on Layout.preferredHeight { - value: (parent.height - signInItem.height) / 4 - } - } - - // left margin - Item { - Layout.fillWidth: true - Layout.maximumWidth: 80 - Layout.minimumWidth: 48 - Layout.preferredHeight: signInItem.height - } - SignIn { - id: signInItem - Layout.fillWidth: true - Layout.preferredWidth: 320 - colorScheme: root.colorScheme - focus: true - username: Backend.users.count === 1 && Backend.users.get(0) && (Backend.users.get(0).state === EUserState.SignedOut) ? Backend.users.get(0).username : "" - } - - // Right margin - Item { - Layout.fillWidth: true - Layout.maximumWidth: 80 - Layout.minimumWidth: 48 - Layout.preferredHeight: signInItem.height - } - - // bottom margin - Item { - Layout.columnSpan: 3 - Layout.fillHeight: true - Layout.fillWidth: true - } - } - Item { - Layout.fillHeight: true - Layout.preferredWidth: signInItem.currentIndex === 0 ? 0 : parent.width / 4 - } - } - } - } -} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-bridge.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-bridge.svg new file mode 100644 index 00000000..b2aa5fe6 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-bridge.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-chevron-left.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-chevron-left.svg new file mode 100644 index 00000000..13be3148 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-chevron-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-external-link.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-external-link.svg index e2abee05..f55efec2 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-external-link.svg +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-external-link.svg @@ -1,3 +1,4 @@ - + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg index 83759ef0..8489b92d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-mozilla-thunderbird.svg @@ -1,112 +1,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-warning-orange.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-warning-orange.svg new file mode 100644 index 00000000..9b96d79d --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/ic-warning-orange.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-selector.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-selector.svg new file mode 100644 index 00000000..0213b922 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-selector.svg @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-success.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-success.svg new file mode 100644 index 00000000..1ba691e3 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-client-config-success.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-macos-cert-screenshot.png b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-macos-cert-screenshot.png new file mode 100644 index 00000000..e0ac953d Binary files /dev/null and b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-macos-cert-screenshot.png differ diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-macos-profile-screenshot.png b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-macos-profile-screenshot.png new file mode 100644 index 00000000..68a87341 Binary files /dev/null and b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-macos-profile-screenshot.png differ diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-clients.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-clients.svg new file mode 100644 index 00000000..52925e21 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-clients.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark-dark.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark-dark.svg new file mode 100644 index 00000000..8e9e0d41 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark-dark.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark.svg new file mode 100644 index 00000000..30212b3c --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-mail-logo-wordmark.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.png b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.png deleted file mode 100644 index d1b39165..00000000 Binary files a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.png and /dev/null differ diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.svg deleted file mode 100644 index 2e3e9272..00000000 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome-dark.svg +++ /dev/null @@ -1,331 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.png b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.png deleted file mode 100644 index 90fff1b9..00000000 Binary files a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.png and /dev/null differ diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.svg b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.svg index dc6e069d..9bedcc34 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.svg +++ b/internal/frontend/bridge-gui/bridge-gui/qml/icons/img-welcome.svg @@ -1,331 +1,34 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp index 7457e17e..dd6f270a 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp @@ -202,6 +202,39 @@ SPStreamEvent newReportBugErrorEvent() { } +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newCertificateInstallSuccessEvent() { + auto event = new grpc::CertificateInstallSuccessEvent; + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_certificateinstallsuccess(event); + return wrapAppEvent(appEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newCertificateInstallCanceledEvent() { + auto event = new grpc::CertificateInstallCanceledEvent; + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_certificateinstallcanceled(event); + return wrapAppEvent(appEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newCertificateInstallFailedEvent() { + auto event = new grpc::CertificateInstallFailedEvent; + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_certificateinstallfailed(event); + return wrapAppEvent(appEvent); +} + + //**************************************************************************************************************************************************** /// \return The event. //**************************************************************************************************************************************************** @@ -245,8 +278,9 @@ SPStreamEvent newLoginTfaRequestedEvent(QString const &username) { /// \param[in] username The username. /// \return The event. //**************************************************************************************************************************************************** -SPStreamEvent newLoginTwoPasswordsRequestedEvent() { +SPStreamEvent newLoginTwoPasswordsRequestedEvent(QString const &username) { auto event = new ::grpc::LoginTwoPasswordsRequestedEvent; + event->set_username(username.toStdString()); auto loginEvent = new grpc::LoginEvent; loginEvent->set_allocated_twopasswordrequested(event); return wrapLoginEvent(loginEvent); diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h index 44a6deae..32f57c7f 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h @@ -34,12 +34,15 @@ SPStreamEvent newResetFinishedEvent(); ///< Create a new ResetFinishedEvent even SPStreamEvent newReportBugFinishedEvent(); ///< Create a new ReportBugFinishedEvent event. SPStreamEvent newReportBugSuccessEvent(); ///< Create a new ReportBugSuccessEvent event. SPStreamEvent newReportBugErrorEvent(); ///< Create a new ReportBugErrorEvent event. +SPStreamEvent newCertificateInstallSuccessEvent(); ///< Create a new CertificateInstallSuccessEvent event. +SPStreamEvent newCertificateInstallCanceledEvent(); ///< Create a new CertificateInstallCanceledEvent event. +SPStreamEvent newCertificateInstallFailedEvent(); ///< Create anew CertificateInstallFailedEvent event. SPStreamEvent newShowMainWindowEvent(); ///< Create a new ShowMainWindowEvent event. // Login events SPStreamEvent newLoginError(grpc::LoginErrorType error, QString const &message); ///< Create a new LoginError event. SPStreamEvent newLoginTfaRequestedEvent(QString const &username); ///< Create a new LoginTfaRequestedEvent event. -SPStreamEvent newLoginTwoPasswordsRequestedEvent(); ///< Create a new LoginTwoPasswordsRequestedEvent event. +SPStreamEvent newLoginTwoPasswordsRequestedEvent(QString const &username); ///< Create a new LoginTwoPasswordsRequestedEvent event. SPStreamEvent newLoginFinishedEvent(QString const &userID, bool wasSignedOut); ///< Create a new LoginFinishedEvent event. SPStreamEvent newLoginAlreadyLoggedInEvent(QString const &userID); ///< Create a new LoginAlreadyLoggedInEvent event. diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp index 767c184d..3c66c5aa 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp @@ -373,14 +373,6 @@ grpc::Status GRPCClient::reportBug(QString const &category, QString const &descr } -//**************************************************************************************************************************************************** -/// \param[in] folderPath of the folder where the TLS files should be stored. -//**************************************************************************************************************************************************** -grpc::Status GRPCClient::exportTLSCertificates(QString const &folderPath) { - return this->logGRPCCallStatus(this->setString(&Bridge::Stub::ExportTLSCertificates, folderPath), __FUNCTION__); -} - - //**************************************************************************************************************************************************** /// \param[out] outIMAPPort The IMAP port. /// \param[out] outSMTPPort The SMTP port. @@ -811,6 +803,32 @@ grpc::Status GRPCClient::setCurrentKeychain(QString const &keychain) { } +//**************************************************************************************************************************************************** +/// \param[out] outIsInstalled is The Bridge certificate installed in the keychain. +/// \return The status for the call +//**************************************************************************************************************************************************** +grpc::Status GRPCClient::isTLSCertificateInstalled(bool &outIsInstalled) { + return this->logGRPCCallStatus(this->getBool(&Bridge::Stub::IsTLSCertificateInstalled, outIsInstalled), __FUNCTION__); +} + + +//**************************************************************************************************************************************************** +/// \return The status for the gRPC call. +//**************************************************************************************************************************************************** +grpc::Status GRPCClient::installTLSCertificate() { + return this->logGRPCCallStatus(this->simpleMethod(&Bridge::Stub::InstallTLSCertificate), __FUNCTION__); +} + + +//**************************************************************************************************************************************************** +/// \param[in] folderPath of the folder where the TLS files should be stored. +/// \return The status for the gRPC call. +//**************************************************************************************************************************************************** +grpc::Status GRPCClient::exportTLSCertificates(QString const &folderPath) { + return this->logGRPCCallStatus(this->setString(&Bridge::Stub::ExportTLSCertificates, folderPath), __FUNCTION__); +} + + //**************************************************************************************************************************************************** /// \return true iff the event stream is active. //**************************************************************************************************************************************************** @@ -1134,6 +1152,18 @@ void GRPCClient::processAppEvent(AppEvent const &event) { this->logTrace("App event received: ReportBugFallback."); emit reportBugFallback(); break; + case AppEvent::kCertificateInstallSuccess: + this->logTrace("App event received: CertificateInstallSuccess."); + emit certificateInstallSuccess(); + break; + case AppEvent::kCertificateInstallCanceled: + this->logTrace("App event received: CertificateInstallCanceled."); + emit certificateInstallCanceled(); + break; + case AppEvent::kCertificateInstallFailed: + this->logTrace("App event received: CertificateInstallFailed."); + emit certificateInstallFailed(); + break; default: this->logError("Unknown App event received."); } @@ -1182,7 +1212,7 @@ void GRPCClient::processLoginEvent(LoginEvent const &event) { break; case LoginEvent::kTwoPasswordRequested: this->logTrace("Login event received: TwoPasswordRequested."); - emit login2PasswordRequested(); + emit login2PasswordRequested(QString::fromStdString(event.twopasswordrequested().username())); break; case LoginEvent::kFinished: { this->logTrace("Login event received: Finished."); @@ -1517,4 +1547,5 @@ grpc::Status GRPCClient::KBArticleClicked(QString const &article) { return this->logGRPCCallStatus(stub_->KBArticleClicked(this->clientContext().get(), s, &empty), __FUNCTION__); } + } // namespace bridgepp diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h index af3f8c16..f77acf84 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h @@ -78,7 +78,6 @@ public: // member functions. grpc::Status setColorSchemeName(QString const &name); ///< Performs the "setColorSchemeName' gRPC call. grpc::Status currentEmailClient(QString &outName); ///< Performs the 'currentEmailClient' gRPC call. grpc::Status reportBug(QString const &category, QString const &description, QString const &address, QString const &emailClient, bool includeLogs); ///< Performs the 'ReportBug' gRPC call. - grpc::Status exportTLSCertificates(QString const &folderPath); ///< Performs the 'ExportTLSCertificates' gRPC call. grpc::Status quit(); ///< Perform the "Quit" gRPC call. grpc::Status restart(); ///< Performs the Restart gRPC call. grpc::Status triggerReset(); ///< Performs the triggerReset gRPC call. @@ -103,6 +102,9 @@ signals: // app related signals void reportBugSuccess(); void reportBugError(); void reportBugFallback(); + void certificateInstallSuccess(); + void certificateInstallCanceled(); + void certificateInstallFailed(); void showMainWindow(); // cache related calls @@ -144,10 +146,10 @@ signals: void loginUsernamePasswordError(QString const &errMsg); void loginFreeUserError(); void loginConnectionError(QString const &errMsg); - void login2FARequested(QString const &userName); + void login2FARequested(QString const &username); void login2FAError(QString const &errMsg); void login2FAErrorAbort(QString const &errMsg); - void login2PasswordRequested(); + void login2PasswordRequested(QString const &username); void login2PasswordError(QString const &errMsg); void login2PasswordErrorAbort(QString const &errMsg); void loginFinished(QString const &userID, bool wasSignedOut); @@ -201,6 +203,11 @@ public: // keychain related calls grpc::Status currentKeychain(QString &outKeychain); grpc::Status setCurrentKeychain(QString const &keychain); +public: // cert related calls + grpc::Status isTLSCertificateInstalled(bool &outIsInstalled); ///< Perform the 'IsTLSCertificateInstalled' gRPC call. + grpc::Status installTLSCertificate(); ///< Perform the 'InstallTLSCertificate' gRPC call. + grpc::Status exportTLSCertificates(QString const &folderPath); ///< Performs the 'ExportTLSCertificates' gRPC call. + signals: void changeKeychainFinished(); void hasNoKeychain(); diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp index 0059f46a..85b0b7f4 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp @@ -325,6 +325,10 @@ float User::syncProgress() const { /// \param[in] progress The progress ratio. //**************************************************************************************************************************************************** void User::setSyncProgress(float progress) { + // In some cases, we may have missed the syncStarted event because it was sent by bridge before the userChanged event, + // so we force the state to 'syncing' (GODT-2932). + this->setIsSyncing(true); + if (qAbs(syncProgress_ - progress) < 0.00001) { return; } diff --git a/internal/frontend/cli/accounts.go b/internal/frontend/cli/accounts.go index cabb7413..bd2a1fa8 100644 --- a/internal/frontend/cli/accounts.go +++ b/internal/frontend/cli/accounts.go @@ -23,6 +23,7 @@ import ( "github.com/ProtonMail/go-proton-api" "github.com/ProtonMail/proton-bridge/v3/internal/bridge" + "github.com/ProtonMail/proton-bridge/v3/internal/certs" "github.com/ProtonMail/proton-bridge/v3/internal/constants" "github.com/ProtonMail/proton-bridge/v3/internal/vault" "github.com/abiosoft/ishell" @@ -297,6 +298,17 @@ func (f *frontendCLI) configureAppleMail(c *ishell.Context) { return } + cert, _ := f.bridge.GetBridgeTLSCert() + installer := certs.NewInstaller() + if !installer.IsCertInstalled(cert) { + f.Println("Apple Mail requires that a TLS certificate for bridge IMAP and SMTP server is installed in your system keychain.") + f.Println("Please provide your credentials in the system popup dialog in order to continue.") + if err := installer.InstallCert(cert); err != nil { + f.printAndLogError(err) + return + } + } + if err := f.bridge.ConfigureAppleMail(context.Background(), user.UserID, user.Addresses[0]); err != nil { f.printAndLogError(err) return diff --git a/internal/frontend/cli/frontend.go b/internal/frontend/cli/frontend.go index 3e84ca68..a3ff74b7 100644 --- a/internal/frontend/cli/frontend.go +++ b/internal/frontend/cli/frontend.go @@ -21,6 +21,7 @@ package cli import ( "errors" "os" + "runtime" "github.com/ProtonMail/gluon/async" "github.com/ProtonMail/proton-bridge/v3/internal/bridge" @@ -145,25 +146,52 @@ func New( }) fe.AddCmd(dohCmd) - // Apple Mail commands. - configureCmd := &ishell.Cmd{ - Name: "configure-apple-mail", - Help: "Configures Apple Mail to use ProtonMail Bridge", - Func: fe.configureAppleMail, + //goland:noinspection GoBoolExpressions + if runtime.GOOS == "darwin" { + // Apple Mail commands. + configureCmd := &ishell.Cmd{ + Name: "configure-apple-mail", + Help: "Configures Apple Mail to use ProtonMail Bridge", + Func: fe.configureAppleMail, + } + fe.AddCmd(configureCmd) } - fe.AddCmd(configureCmd) // TLS commands. - fe.AddCmd(&ishell.Cmd{ - Name: "export-tls-cert", - Help: "Export the TLS certificate used by the Bridge", + certCmd := &ishell.Cmd{ + Name: "cert", + Help: "Manage the TLS certificate used by Bridge", + } + + //goland:noinspection GoBoolExpressions + if runtime.GOOS == "darwin" { + certCmd.AddCmd(&ishell.Cmd{ + Name: "status", + Help: "Check if the TLS certificate used by Bridge is installed in the OS keychain", + Func: fe.tlsCertStatus, + }) + certCmd.AddCmd(&ishell.Cmd{ + Name: "install", + Help: "Install TLS certificate used by Bridge in the OS keychain", + Func: fe.installTLSCert, + }) + certCmd.AddCmd(&ishell.Cmd{ + Name: "uninstall", + Help: "Uninstall the TLS certificate used by Bridge from the OS keychain", + Func: fe.uninstallTLSCert, + }) + } + certCmd.AddCmd(&ishell.Cmd{ + Name: "export", + Help: "Export the TLS certificate used by Bridge", Func: fe.exportTLSCerts, }) - fe.AddCmd(&ishell.Cmd{ - Name: "import-tls-cert", - Help: "Import a TLS certificate to be used by the Bridge", + certCmd.AddCmd(&ishell.Cmd{ + Name: "import", + Help: "Import a TLS certificate to be used by Bridge", Func: fe.importTLSCerts, }) + fe.AddCmd(certCmd) // All mail visibility commands. allMailCmd := &ishell.Cmd{ diff --git a/internal/frontend/cli/system.go b/internal/frontend/cli/system.go index d163db1f..be6d03e4 100644 --- a/internal/frontend/cli/system.go +++ b/internal/frontend/cli/system.go @@ -27,6 +27,7 @@ import ( "strings" "github.com/ProtonMail/proton-bridge/v3/internal/bridge" + "github.com/ProtonMail/proton-bridge/v3/internal/certs" "github.com/ProtonMail/proton-bridge/v3/pkg/ports" "github.com/abiosoft/ishell" ) @@ -240,6 +241,50 @@ func (f *frontendCLI) setGluonLocation(c *ishell.Context) { } } +func (f *frontendCLI) tlsCertStatus(_ *ishell.Context) { + cert, _ := f.bridge.GetBridgeTLSCert() + installer := certs.NewInstaller() + if installer.IsCertInstalled(cert) { + f.Println("The Bridge TLS certificate is already installed in the OS keychain.") + } else { + f.Println("The Bridge TLS certificate is not installed in the OS keychain.") + } +} + +func (f *frontendCLI) installTLSCert(_ *ishell.Context) { + cert, _ := f.bridge.GetBridgeTLSCert() + installer := certs.NewInstaller() + if installer.IsCertInstalled(cert) { + f.printAndLogError(errors.New("the Bridge TLS certificate is already installed in the OS keychain")) + return + } + + f.Println("Please provide your credentials in the system popup dialog in order to continue.") + if err := installer.InstallCert(cert); err != nil { + f.printAndLogError(err) + return + } + + f.Println("The Bridge TLS certificate was successfully installed in the OS keychain.") +} + +func (f *frontendCLI) uninstallTLSCert(_ *ishell.Context) { + cert, _ := f.bridge.GetBridgeTLSCert() + installer := certs.NewInstaller() + if !installer.IsCertInstalled(cert) { + f.printAndLogError(errors.New("the Bridge TLS certificate is not installed in the OS keychain")) + return + } + + f.Println("Please provide your credentials in the system popup dialog in order to continue.") + if err := installer.UninstallCert(cert); err != nil { + f.printAndLogError(err) + return + } + + f.Println("The Bridge TLS certificate was successfully uninstalled from the OS keychain.") +} + func (f *frontendCLI) exportTLSCerts(c *ishell.Context) { if location := f.readStringInAttempts("Enter a path to which to export the TLS certificate used for IMAP and SMTP", c.ReadLine, f.isCacheLocationUsable); location != "" { cert, key := f.bridge.GetBridgeTLSCert() diff --git a/internal/frontend/grpc/bridge.pb.go b/internal/frontend/grpc/bridge.pb.go index 357fd816..c3cdf92b 100644 --- a/internal/frontend/grpc/bridge.pb.go +++ b/internal/frontend/grpc/bridge.pb.go @@ -1427,6 +1427,9 @@ type AppEvent struct { // *AppEvent_ReportBugError // *AppEvent_ShowMainWindow // *AppEvent_ReportBugFallback + // *AppEvent_CertificateInstallSuccess + // *AppEvent_CertificateInstallCanceled + // *AppEvent_CertificateInstallFailed Event isAppEvent_Event `protobuf_oneof:"event"` } @@ -1525,6 +1528,27 @@ func (x *AppEvent) GetReportBugFallback() *ReportBugFallbackEvent { return nil } +func (x *AppEvent) GetCertificateInstallSuccess() *CertificateInstallSuccessEvent { + if x, ok := x.GetEvent().(*AppEvent_CertificateInstallSuccess); ok { + return x.CertificateInstallSuccess + } + return nil +} + +func (x *AppEvent) GetCertificateInstallCanceled() *CertificateInstallCanceledEvent { + if x, ok := x.GetEvent().(*AppEvent_CertificateInstallCanceled); ok { + return x.CertificateInstallCanceled + } + return nil +} + +func (x *AppEvent) GetCertificateInstallFailed() *CertificateInstallFailedEvent { + if x, ok := x.GetEvent().(*AppEvent_CertificateInstallFailed); ok { + return x.CertificateInstallFailed + } + return nil +} + type isAppEvent_Event interface { isAppEvent_Event() } @@ -1561,6 +1585,18 @@ type AppEvent_ReportBugFallback struct { ReportBugFallback *ReportBugFallbackEvent `protobuf:"bytes,8,opt,name=reportBugFallback,proto3,oneof"` } +type AppEvent_CertificateInstallSuccess struct { + CertificateInstallSuccess *CertificateInstallSuccessEvent `protobuf:"bytes,9,opt,name=certificateInstallSuccess,proto3,oneof"` +} + +type AppEvent_CertificateInstallCanceled struct { + CertificateInstallCanceled *CertificateInstallCanceledEvent `protobuf:"bytes,10,opt,name=certificateInstallCanceled,proto3,oneof"` +} + +type AppEvent_CertificateInstallFailed struct { + CertificateInstallFailed *CertificateInstallFailedEvent `protobuf:"bytes,11,opt,name=certificateInstallFailed,proto3,oneof"` +} + func (*AppEvent_InternetStatus) isAppEvent_Event() {} func (*AppEvent_ToggleAutostartFinished) isAppEvent_Event() {} @@ -1577,6 +1613,12 @@ func (*AppEvent_ShowMainWindow) isAppEvent_Event() {} func (*AppEvent_ReportBugFallback) isAppEvent_Event() {} +func (*AppEvent_CertificateInstallSuccess) isAppEvent_Event() {} + +func (*AppEvent_CertificateInstallCanceled) isAppEvent_Event() {} + +func (*AppEvent_CertificateInstallFailed) isAppEvent_Event() {} + type InternetStatusEvent struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1890,6 +1932,120 @@ func (*ReportBugFallbackEvent) Descriptor() ([]byte, []int) { return file_bridge_proto_rawDescGZIP(), []int{22} } +type CertificateInstallSuccessEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CertificateInstallSuccessEvent) Reset() { + *x = CertificateInstallSuccessEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_bridge_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CertificateInstallSuccessEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CertificateInstallSuccessEvent) ProtoMessage() {} + +func (x *CertificateInstallSuccessEvent) ProtoReflect() protoreflect.Message { + mi := &file_bridge_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CertificateInstallSuccessEvent.ProtoReflect.Descriptor instead. +func (*CertificateInstallSuccessEvent) Descriptor() ([]byte, []int) { + return file_bridge_proto_rawDescGZIP(), []int{23} +} + +type CertificateInstallCanceledEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CertificateInstallCanceledEvent) Reset() { + *x = CertificateInstallCanceledEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_bridge_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CertificateInstallCanceledEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CertificateInstallCanceledEvent) ProtoMessage() {} + +func (x *CertificateInstallCanceledEvent) ProtoReflect() protoreflect.Message { + mi := &file_bridge_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CertificateInstallCanceledEvent.ProtoReflect.Descriptor instead. +func (*CertificateInstallCanceledEvent) Descriptor() ([]byte, []int) { + return file_bridge_proto_rawDescGZIP(), []int{24} +} + +type CertificateInstallFailedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CertificateInstallFailedEvent) Reset() { + *x = CertificateInstallFailedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_bridge_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CertificateInstallFailedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CertificateInstallFailedEvent) ProtoMessage() {} + +func (x *CertificateInstallFailedEvent) ProtoReflect() protoreflect.Message { + mi := &file_bridge_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CertificateInstallFailedEvent.ProtoReflect.Descriptor instead. +func (*CertificateInstallFailedEvent) Descriptor() ([]byte, []int) { + return file_bridge_proto_rawDescGZIP(), []int{25} +} + //********************************************************** // Login related events //********************************************************** @@ -1910,7 +2066,7 @@ type LoginEvent struct { func (x *LoginEvent) Reset() { *x = LoginEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[23] + mi := &file_bridge_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1923,7 +2079,7 @@ func (x *LoginEvent) String() string { func (*LoginEvent) ProtoMessage() {} func (x *LoginEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[23] + mi := &file_bridge_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1936,7 +2092,7 @@ func (x *LoginEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginEvent.ProtoReflect.Descriptor instead. func (*LoginEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{23} + return file_bridge_proto_rawDescGZIP(), []int{26} } func (m *LoginEvent) GetEvent() isLoginEvent_Event { @@ -2027,7 +2183,7 @@ type LoginErrorEvent struct { func (x *LoginErrorEvent) Reset() { *x = LoginErrorEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[24] + mi := &file_bridge_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2040,7 +2196,7 @@ func (x *LoginErrorEvent) String() string { func (*LoginErrorEvent) ProtoMessage() {} func (x *LoginErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[24] + mi := &file_bridge_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2053,7 +2209,7 @@ func (x *LoginErrorEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginErrorEvent.ProtoReflect.Descriptor instead. func (*LoginErrorEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{24} + return file_bridge_proto_rawDescGZIP(), []int{27} } func (x *LoginErrorEvent) GetType() LoginErrorType { @@ -2081,7 +2237,7 @@ type LoginTfaRequestedEvent struct { func (x *LoginTfaRequestedEvent) Reset() { *x = LoginTfaRequestedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[25] + mi := &file_bridge_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2094,7 +2250,7 @@ func (x *LoginTfaRequestedEvent) String() string { func (*LoginTfaRequestedEvent) ProtoMessage() {} func (x *LoginTfaRequestedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[25] + mi := &file_bridge_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2107,7 +2263,7 @@ func (x *LoginTfaRequestedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginTfaRequestedEvent.ProtoReflect.Descriptor instead. func (*LoginTfaRequestedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{25} + return file_bridge_proto_rawDescGZIP(), []int{28} } func (x *LoginTfaRequestedEvent) GetUsername() string { @@ -2121,12 +2277,14 @@ type LoginTwoPasswordsRequestedEvent struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` } func (x *LoginTwoPasswordsRequestedEvent) Reset() { *x = LoginTwoPasswordsRequestedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[26] + mi := &file_bridge_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2139,7 +2297,7 @@ func (x *LoginTwoPasswordsRequestedEvent) String() string { func (*LoginTwoPasswordsRequestedEvent) ProtoMessage() {} func (x *LoginTwoPasswordsRequestedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[26] + mi := &file_bridge_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2152,7 +2310,14 @@ func (x *LoginTwoPasswordsRequestedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginTwoPasswordsRequestedEvent.ProtoReflect.Descriptor instead. func (*LoginTwoPasswordsRequestedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{26} + return file_bridge_proto_rawDescGZIP(), []int{29} +} + +func (x *LoginTwoPasswordsRequestedEvent) GetUsername() string { + if x != nil { + return x.Username + } + return "" } type LoginFinishedEvent struct { @@ -2167,7 +2332,7 @@ type LoginFinishedEvent struct { func (x *LoginFinishedEvent) Reset() { *x = LoginFinishedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[27] + mi := &file_bridge_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2180,7 +2345,7 @@ func (x *LoginFinishedEvent) String() string { func (*LoginFinishedEvent) ProtoMessage() {} func (x *LoginFinishedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[27] + mi := &file_bridge_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2193,7 +2358,7 @@ func (x *LoginFinishedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginFinishedEvent.ProtoReflect.Descriptor instead. func (*LoginFinishedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{27} + return file_bridge_proto_rawDescGZIP(), []int{30} } func (x *LoginFinishedEvent) GetUserID() string { @@ -2233,7 +2398,7 @@ type UpdateEvent struct { func (x *UpdateEvent) Reset() { *x = UpdateEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[28] + mi := &file_bridge_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2246,7 +2411,7 @@ func (x *UpdateEvent) String() string { func (*UpdateEvent) ProtoMessage() {} func (x *UpdateEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[28] + mi := &file_bridge_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2259,7 +2424,7 @@ func (x *UpdateEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateEvent.ProtoReflect.Descriptor instead. func (*UpdateEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{28} + return file_bridge_proto_rawDescGZIP(), []int{31} } func (m *UpdateEvent) GetEvent() isUpdateEvent_Event { @@ -2388,7 +2553,7 @@ type UpdateErrorEvent struct { func (x *UpdateErrorEvent) Reset() { *x = UpdateErrorEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[29] + mi := &file_bridge_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2401,7 +2566,7 @@ func (x *UpdateErrorEvent) String() string { func (*UpdateErrorEvent) ProtoMessage() {} func (x *UpdateErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[29] + mi := &file_bridge_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2414,7 +2579,7 @@ func (x *UpdateErrorEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateErrorEvent.ProtoReflect.Descriptor instead. func (*UpdateErrorEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{29} + return file_bridge_proto_rawDescGZIP(), []int{32} } func (x *UpdateErrorEvent) GetType() UpdateErrorType { @@ -2435,7 +2600,7 @@ type UpdateManualReadyEvent struct { func (x *UpdateManualReadyEvent) Reset() { *x = UpdateManualReadyEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[30] + mi := &file_bridge_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2448,7 +2613,7 @@ func (x *UpdateManualReadyEvent) String() string { func (*UpdateManualReadyEvent) ProtoMessage() {} func (x *UpdateManualReadyEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[30] + mi := &file_bridge_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2461,7 +2626,7 @@ func (x *UpdateManualReadyEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateManualReadyEvent.ProtoReflect.Descriptor instead. func (*UpdateManualReadyEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{30} + return file_bridge_proto_rawDescGZIP(), []int{33} } func (x *UpdateManualReadyEvent) GetVersion() string { @@ -2480,7 +2645,7 @@ type UpdateManualRestartNeededEvent struct { func (x *UpdateManualRestartNeededEvent) Reset() { *x = UpdateManualRestartNeededEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[31] + mi := &file_bridge_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2493,7 +2658,7 @@ func (x *UpdateManualRestartNeededEvent) String() string { func (*UpdateManualRestartNeededEvent) ProtoMessage() {} func (x *UpdateManualRestartNeededEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[31] + mi := &file_bridge_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2506,7 +2671,7 @@ func (x *UpdateManualRestartNeededEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateManualRestartNeededEvent.ProtoReflect.Descriptor instead. func (*UpdateManualRestartNeededEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{31} + return file_bridge_proto_rawDescGZIP(), []int{34} } type UpdateForceEvent struct { @@ -2520,7 +2685,7 @@ type UpdateForceEvent struct { func (x *UpdateForceEvent) Reset() { *x = UpdateForceEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[32] + mi := &file_bridge_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2533,7 +2698,7 @@ func (x *UpdateForceEvent) String() string { func (*UpdateForceEvent) ProtoMessage() {} func (x *UpdateForceEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[32] + mi := &file_bridge_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2546,7 +2711,7 @@ func (x *UpdateForceEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateForceEvent.ProtoReflect.Descriptor instead. func (*UpdateForceEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{32} + return file_bridge_proto_rawDescGZIP(), []int{35} } func (x *UpdateForceEvent) GetVersion() string { @@ -2565,7 +2730,7 @@ type UpdateSilentRestartNeeded struct { func (x *UpdateSilentRestartNeeded) Reset() { *x = UpdateSilentRestartNeeded{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[33] + mi := &file_bridge_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2578,7 +2743,7 @@ func (x *UpdateSilentRestartNeeded) String() string { func (*UpdateSilentRestartNeeded) ProtoMessage() {} func (x *UpdateSilentRestartNeeded) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[33] + mi := &file_bridge_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2591,7 +2756,7 @@ func (x *UpdateSilentRestartNeeded) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateSilentRestartNeeded.ProtoReflect.Descriptor instead. func (*UpdateSilentRestartNeeded) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{33} + return file_bridge_proto_rawDescGZIP(), []int{36} } type UpdateIsLatestVersion struct { @@ -2603,7 +2768,7 @@ type UpdateIsLatestVersion struct { func (x *UpdateIsLatestVersion) Reset() { *x = UpdateIsLatestVersion{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[34] + mi := &file_bridge_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2616,7 +2781,7 @@ func (x *UpdateIsLatestVersion) String() string { func (*UpdateIsLatestVersion) ProtoMessage() {} func (x *UpdateIsLatestVersion) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[34] + mi := &file_bridge_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2629,7 +2794,7 @@ func (x *UpdateIsLatestVersion) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateIsLatestVersion.ProtoReflect.Descriptor instead. func (*UpdateIsLatestVersion) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{34} + return file_bridge_proto_rawDescGZIP(), []int{37} } type UpdateCheckFinished struct { @@ -2641,7 +2806,7 @@ type UpdateCheckFinished struct { func (x *UpdateCheckFinished) Reset() { *x = UpdateCheckFinished{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[35] + mi := &file_bridge_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2654,7 +2819,7 @@ func (x *UpdateCheckFinished) String() string { func (*UpdateCheckFinished) ProtoMessage() {} func (x *UpdateCheckFinished) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[35] + mi := &file_bridge_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2667,7 +2832,7 @@ func (x *UpdateCheckFinished) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateCheckFinished.ProtoReflect.Descriptor instead. func (*UpdateCheckFinished) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{35} + return file_bridge_proto_rawDescGZIP(), []int{38} } type UpdateVersionChanged struct { @@ -2679,7 +2844,7 @@ type UpdateVersionChanged struct { func (x *UpdateVersionChanged) Reset() { *x = UpdateVersionChanged{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[36] + mi := &file_bridge_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2692,7 +2857,7 @@ func (x *UpdateVersionChanged) String() string { func (*UpdateVersionChanged) ProtoMessage() {} func (x *UpdateVersionChanged) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[36] + mi := &file_bridge_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2705,7 +2870,7 @@ func (x *UpdateVersionChanged) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateVersionChanged.ProtoReflect.Descriptor instead. func (*UpdateVersionChanged) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{36} + return file_bridge_proto_rawDescGZIP(), []int{39} } //********************************************************** @@ -2726,7 +2891,7 @@ type DiskCacheEvent struct { func (x *DiskCacheEvent) Reset() { *x = DiskCacheEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[37] + mi := &file_bridge_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2739,7 +2904,7 @@ func (x *DiskCacheEvent) String() string { func (*DiskCacheEvent) ProtoMessage() {} func (x *DiskCacheEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[37] + mi := &file_bridge_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2752,7 +2917,7 @@ func (x *DiskCacheEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DiskCacheEvent.ProtoReflect.Descriptor instead. func (*DiskCacheEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{37} + return file_bridge_proto_rawDescGZIP(), []int{40} } func (m *DiskCacheEvent) GetEvent() isDiskCacheEvent_Event { @@ -2816,7 +2981,7 @@ type DiskCacheErrorEvent struct { func (x *DiskCacheErrorEvent) Reset() { *x = DiskCacheErrorEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[38] + mi := &file_bridge_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2829,7 +2994,7 @@ func (x *DiskCacheErrorEvent) String() string { func (*DiskCacheErrorEvent) ProtoMessage() {} func (x *DiskCacheErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[38] + mi := &file_bridge_proto_msgTypes[41] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2842,7 +3007,7 @@ func (x *DiskCacheErrorEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DiskCacheErrorEvent.ProtoReflect.Descriptor instead. func (*DiskCacheErrorEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{38} + return file_bridge_proto_rawDescGZIP(), []int{41} } func (x *DiskCacheErrorEvent) GetType() DiskCacheErrorType { @@ -2863,7 +3028,7 @@ type DiskCachePathChangedEvent struct { func (x *DiskCachePathChangedEvent) Reset() { *x = DiskCachePathChangedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[39] + mi := &file_bridge_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2876,7 +3041,7 @@ func (x *DiskCachePathChangedEvent) String() string { func (*DiskCachePathChangedEvent) ProtoMessage() {} func (x *DiskCachePathChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[39] + mi := &file_bridge_proto_msgTypes[42] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2889,7 +3054,7 @@ func (x *DiskCachePathChangedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DiskCachePathChangedEvent.ProtoReflect.Descriptor instead. func (*DiskCachePathChangedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{39} + return file_bridge_proto_rawDescGZIP(), []int{42} } func (x *DiskCachePathChangedEvent) GetPath() string { @@ -2908,7 +3073,7 @@ type DiskCachePathChangeFinishedEvent struct { func (x *DiskCachePathChangeFinishedEvent) Reset() { *x = DiskCachePathChangeFinishedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[40] + mi := &file_bridge_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2921,7 +3086,7 @@ func (x *DiskCachePathChangeFinishedEvent) String() string { func (*DiskCachePathChangeFinishedEvent) ProtoMessage() {} func (x *DiskCachePathChangeFinishedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[40] + mi := &file_bridge_proto_msgTypes[43] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2934,7 +3099,7 @@ func (x *DiskCachePathChangeFinishedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DiskCachePathChangeFinishedEvent.ProtoReflect.Descriptor instead. func (*DiskCachePathChangeFinishedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{40} + return file_bridge_proto_rawDescGZIP(), []int{43} } //********************************************************** @@ -2955,7 +3120,7 @@ type MailServerSettingsEvent struct { func (x *MailServerSettingsEvent) Reset() { *x = MailServerSettingsEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[41] + mi := &file_bridge_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2968,7 +3133,7 @@ func (x *MailServerSettingsEvent) String() string { func (*MailServerSettingsEvent) ProtoMessage() {} func (x *MailServerSettingsEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[41] + mi := &file_bridge_proto_msgTypes[44] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2981,7 +3146,7 @@ func (x *MailServerSettingsEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use MailServerSettingsEvent.ProtoReflect.Descriptor instead. func (*MailServerSettingsEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{41} + return file_bridge_proto_rawDescGZIP(), []int{44} } func (m *MailServerSettingsEvent) GetEvent() isMailServerSettingsEvent_Event { @@ -3045,7 +3210,7 @@ type MailServerSettingsErrorEvent struct { func (x *MailServerSettingsErrorEvent) Reset() { *x = MailServerSettingsErrorEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[42] + mi := &file_bridge_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3058,7 +3223,7 @@ func (x *MailServerSettingsErrorEvent) String() string { func (*MailServerSettingsErrorEvent) ProtoMessage() {} func (x *MailServerSettingsErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[42] + mi := &file_bridge_proto_msgTypes[45] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3071,7 +3236,7 @@ func (x *MailServerSettingsErrorEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use MailServerSettingsErrorEvent.ProtoReflect.Descriptor instead. func (*MailServerSettingsErrorEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{42} + return file_bridge_proto_rawDescGZIP(), []int{45} } func (x *MailServerSettingsErrorEvent) GetType() MailServerSettingsErrorType { @@ -3092,7 +3257,7 @@ type MailServerSettingsChangedEvent struct { func (x *MailServerSettingsChangedEvent) Reset() { *x = MailServerSettingsChangedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[43] + mi := &file_bridge_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3105,7 +3270,7 @@ func (x *MailServerSettingsChangedEvent) String() string { func (*MailServerSettingsChangedEvent) ProtoMessage() {} func (x *MailServerSettingsChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[43] + mi := &file_bridge_proto_msgTypes[46] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3118,7 +3283,7 @@ func (x *MailServerSettingsChangedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use MailServerSettingsChangedEvent.ProtoReflect.Descriptor instead. func (*MailServerSettingsChangedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{43} + return file_bridge_proto_rawDescGZIP(), []int{46} } func (x *MailServerSettingsChangedEvent) GetSettings() *ImapSmtpSettings { @@ -3137,7 +3302,7 @@ type ChangeMailServerSettingsFinishedEvent struct { func (x *ChangeMailServerSettingsFinishedEvent) Reset() { *x = ChangeMailServerSettingsFinishedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[44] + mi := &file_bridge_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3150,7 +3315,7 @@ func (x *ChangeMailServerSettingsFinishedEvent) String() string { func (*ChangeMailServerSettingsFinishedEvent) ProtoMessage() {} func (x *ChangeMailServerSettingsFinishedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[44] + mi := &file_bridge_proto_msgTypes[47] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3163,7 +3328,7 @@ func (x *ChangeMailServerSettingsFinishedEvent) ProtoReflect() protoreflect.Mess // Deprecated: Use ChangeMailServerSettingsFinishedEvent.ProtoReflect.Descriptor instead. func (*ChangeMailServerSettingsFinishedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{44} + return file_bridge_proto_rawDescGZIP(), []int{47} } //********************************************************** @@ -3184,7 +3349,7 @@ type KeychainEvent struct { func (x *KeychainEvent) Reset() { *x = KeychainEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[45] + mi := &file_bridge_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3197,7 +3362,7 @@ func (x *KeychainEvent) String() string { func (*KeychainEvent) ProtoMessage() {} func (x *KeychainEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[45] + mi := &file_bridge_proto_msgTypes[48] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3210,7 +3375,7 @@ func (x *KeychainEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use KeychainEvent.ProtoReflect.Descriptor instead. func (*KeychainEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{45} + return file_bridge_proto_rawDescGZIP(), []int{48} } func (m *KeychainEvent) GetEvent() isKeychainEvent_Event { @@ -3272,7 +3437,7 @@ type ChangeKeychainFinishedEvent struct { func (x *ChangeKeychainFinishedEvent) Reset() { *x = ChangeKeychainFinishedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[46] + mi := &file_bridge_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3285,7 +3450,7 @@ func (x *ChangeKeychainFinishedEvent) String() string { func (*ChangeKeychainFinishedEvent) ProtoMessage() {} func (x *ChangeKeychainFinishedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[46] + mi := &file_bridge_proto_msgTypes[49] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3298,7 +3463,7 @@ func (x *ChangeKeychainFinishedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeKeychainFinishedEvent.ProtoReflect.Descriptor instead. func (*ChangeKeychainFinishedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{46} + return file_bridge_proto_rawDescGZIP(), []int{49} } type HasNoKeychainEvent struct { @@ -3310,7 +3475,7 @@ type HasNoKeychainEvent struct { func (x *HasNoKeychainEvent) Reset() { *x = HasNoKeychainEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[47] + mi := &file_bridge_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3323,7 +3488,7 @@ func (x *HasNoKeychainEvent) String() string { func (*HasNoKeychainEvent) ProtoMessage() {} func (x *HasNoKeychainEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[47] + mi := &file_bridge_proto_msgTypes[50] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3336,7 +3501,7 @@ func (x *HasNoKeychainEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use HasNoKeychainEvent.ProtoReflect.Descriptor instead. func (*HasNoKeychainEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{47} + return file_bridge_proto_rawDescGZIP(), []int{50} } type RebuildKeychainEvent struct { @@ -3348,7 +3513,7 @@ type RebuildKeychainEvent struct { func (x *RebuildKeychainEvent) Reset() { *x = RebuildKeychainEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[48] + mi := &file_bridge_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3361,7 +3526,7 @@ func (x *RebuildKeychainEvent) String() string { func (*RebuildKeychainEvent) ProtoMessage() {} func (x *RebuildKeychainEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[48] + mi := &file_bridge_proto_msgTypes[51] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3374,7 +3539,7 @@ func (x *RebuildKeychainEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use RebuildKeychainEvent.ProtoReflect.Descriptor instead. func (*RebuildKeychainEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{48} + return file_bridge_proto_rawDescGZIP(), []int{51} } //********************************************************** @@ -3396,7 +3561,7 @@ type MailEvent struct { func (x *MailEvent) Reset() { *x = MailEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[49] + mi := &file_bridge_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3409,7 +3574,7 @@ func (x *MailEvent) String() string { func (*MailEvent) ProtoMessage() {} func (x *MailEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[49] + mi := &file_bridge_proto_msgTypes[52] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3422,7 +3587,7 @@ func (x *MailEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use MailEvent.ProtoReflect.Descriptor instead. func (*MailEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{49} + return file_bridge_proto_rawDescGZIP(), []int{52} } func (m *MailEvent) GetEvent() isMailEvent_Event { @@ -3499,7 +3664,7 @@ type NoActiveKeyForRecipientEvent struct { func (x *NoActiveKeyForRecipientEvent) Reset() { *x = NoActiveKeyForRecipientEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[50] + mi := &file_bridge_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3512,7 +3677,7 @@ func (x *NoActiveKeyForRecipientEvent) String() string { func (*NoActiveKeyForRecipientEvent) ProtoMessage() {} func (x *NoActiveKeyForRecipientEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[50] + mi := &file_bridge_proto_msgTypes[53] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3525,7 +3690,7 @@ func (x *NoActiveKeyForRecipientEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use NoActiveKeyForRecipientEvent.ProtoReflect.Descriptor instead. func (*NoActiveKeyForRecipientEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{50} + return file_bridge_proto_rawDescGZIP(), []int{53} } func (x *NoActiveKeyForRecipientEvent) GetEmail() string { @@ -3546,7 +3711,7 @@ type AddressChangedEvent struct { func (x *AddressChangedEvent) Reset() { *x = AddressChangedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[51] + mi := &file_bridge_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3559,7 +3724,7 @@ func (x *AddressChangedEvent) String() string { func (*AddressChangedEvent) ProtoMessage() {} func (x *AddressChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[51] + mi := &file_bridge_proto_msgTypes[54] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3572,7 +3737,7 @@ func (x *AddressChangedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AddressChangedEvent.ProtoReflect.Descriptor instead. func (*AddressChangedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{51} + return file_bridge_proto_rawDescGZIP(), []int{54} } func (x *AddressChangedEvent) GetAddress() string { @@ -3593,7 +3758,7 @@ type AddressChangedLogoutEvent struct { func (x *AddressChangedLogoutEvent) Reset() { *x = AddressChangedLogoutEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[52] + mi := &file_bridge_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3606,7 +3771,7 @@ func (x *AddressChangedLogoutEvent) String() string { func (*AddressChangedLogoutEvent) ProtoMessage() {} func (x *AddressChangedLogoutEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[52] + mi := &file_bridge_proto_msgTypes[55] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3619,7 +3784,7 @@ func (x *AddressChangedLogoutEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AddressChangedLogoutEvent.ProtoReflect.Descriptor instead. func (*AddressChangedLogoutEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{52} + return file_bridge_proto_rawDescGZIP(), []int{55} } func (x *AddressChangedLogoutEvent) GetAddress() string { @@ -3638,7 +3803,7 @@ type ApiCertIssueEvent struct { func (x *ApiCertIssueEvent) Reset() { *x = ApiCertIssueEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[53] + mi := &file_bridge_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3651,7 +3816,7 @@ func (x *ApiCertIssueEvent) String() string { func (*ApiCertIssueEvent) ProtoMessage() {} func (x *ApiCertIssueEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[53] + mi := &file_bridge_proto_msgTypes[56] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3664,7 +3829,7 @@ func (x *ApiCertIssueEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ApiCertIssueEvent.ProtoReflect.Descriptor instead. func (*ApiCertIssueEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{53} + return file_bridge_proto_rawDescGZIP(), []int{56} } type UserEvent struct { @@ -3688,7 +3853,7 @@ type UserEvent struct { func (x *UserEvent) Reset() { *x = UserEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[54] + mi := &file_bridge_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3701,7 +3866,7 @@ func (x *UserEvent) String() string { func (*UserEvent) ProtoMessage() {} func (x *UserEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[54] + mi := &file_bridge_proto_msgTypes[57] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3714,7 +3879,7 @@ func (x *UserEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UserEvent.ProtoReflect.Descriptor instead. func (*UserEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{54} + return file_bridge_proto_rawDescGZIP(), []int{57} } func (m *UserEvent) GetEvent() isUserEvent_Event { @@ -3856,7 +4021,7 @@ type ToggleSplitModeFinishedEvent struct { func (x *ToggleSplitModeFinishedEvent) Reset() { *x = ToggleSplitModeFinishedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[55] + mi := &file_bridge_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3869,7 +4034,7 @@ func (x *ToggleSplitModeFinishedEvent) String() string { func (*ToggleSplitModeFinishedEvent) ProtoMessage() {} func (x *ToggleSplitModeFinishedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[55] + mi := &file_bridge_proto_msgTypes[58] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3882,7 +4047,7 @@ func (x *ToggleSplitModeFinishedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ToggleSplitModeFinishedEvent.ProtoReflect.Descriptor instead. func (*ToggleSplitModeFinishedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{55} + return file_bridge_proto_rawDescGZIP(), []int{58} } func (x *ToggleSplitModeFinishedEvent) GetUserID() string { @@ -3903,7 +4068,7 @@ type UserDisconnectedEvent struct { func (x *UserDisconnectedEvent) Reset() { *x = UserDisconnectedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[56] + mi := &file_bridge_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3916,7 +4081,7 @@ func (x *UserDisconnectedEvent) String() string { func (*UserDisconnectedEvent) ProtoMessage() {} func (x *UserDisconnectedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[56] + mi := &file_bridge_proto_msgTypes[59] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3929,7 +4094,7 @@ func (x *UserDisconnectedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UserDisconnectedEvent.ProtoReflect.Descriptor instead. func (*UserDisconnectedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{56} + return file_bridge_proto_rawDescGZIP(), []int{59} } func (x *UserDisconnectedEvent) GetUsername() string { @@ -3950,7 +4115,7 @@ type UserChangedEvent struct { func (x *UserChangedEvent) Reset() { *x = UserChangedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[57] + mi := &file_bridge_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3963,7 +4128,7 @@ func (x *UserChangedEvent) String() string { func (*UserChangedEvent) ProtoMessage() {} func (x *UserChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[57] + mi := &file_bridge_proto_msgTypes[60] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3976,7 +4141,7 @@ func (x *UserChangedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UserChangedEvent.ProtoReflect.Descriptor instead. func (*UserChangedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{57} + return file_bridge_proto_rawDescGZIP(), []int{60} } func (x *UserChangedEvent) GetUserID() string { @@ -3998,7 +4163,7 @@ type UserBadEvent struct { func (x *UserBadEvent) Reset() { *x = UserBadEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[58] + mi := &file_bridge_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4011,7 +4176,7 @@ func (x *UserBadEvent) String() string { func (*UserBadEvent) ProtoMessage() {} func (x *UserBadEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[58] + mi := &file_bridge_proto_msgTypes[61] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4024,7 +4189,7 @@ func (x *UserBadEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UserBadEvent.ProtoReflect.Descriptor instead. func (*UserBadEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{58} + return file_bridge_proto_rawDescGZIP(), []int{61} } func (x *UserBadEvent) GetUserID() string { @@ -4053,7 +4218,7 @@ type UsedBytesChangedEvent struct { func (x *UsedBytesChangedEvent) Reset() { *x = UsedBytesChangedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[59] + mi := &file_bridge_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4066,7 +4231,7 @@ func (x *UsedBytesChangedEvent) String() string { func (*UsedBytesChangedEvent) ProtoMessage() {} func (x *UsedBytesChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[59] + mi := &file_bridge_proto_msgTypes[62] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4079,7 +4244,7 @@ func (x *UsedBytesChangedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use UsedBytesChangedEvent.ProtoReflect.Descriptor instead. func (*UsedBytesChangedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{59} + return file_bridge_proto_rawDescGZIP(), []int{62} } func (x *UsedBytesChangedEvent) GetUserID() string { @@ -4107,7 +4272,7 @@ type ImapLoginFailedEvent struct { func (x *ImapLoginFailedEvent) Reset() { *x = ImapLoginFailedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[60] + mi := &file_bridge_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4120,7 +4285,7 @@ func (x *ImapLoginFailedEvent) String() string { func (*ImapLoginFailedEvent) ProtoMessage() {} func (x *ImapLoginFailedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[60] + mi := &file_bridge_proto_msgTypes[63] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4133,7 +4298,7 @@ func (x *ImapLoginFailedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ImapLoginFailedEvent.ProtoReflect.Descriptor instead. func (*ImapLoginFailedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{60} + return file_bridge_proto_rawDescGZIP(), []int{63} } func (x *ImapLoginFailedEvent) GetUsername() string { @@ -4154,7 +4319,7 @@ type SyncStartedEvent struct { func (x *SyncStartedEvent) Reset() { *x = SyncStartedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[61] + mi := &file_bridge_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4167,7 +4332,7 @@ func (x *SyncStartedEvent) String() string { func (*SyncStartedEvent) ProtoMessage() {} func (x *SyncStartedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[61] + mi := &file_bridge_proto_msgTypes[64] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4180,7 +4345,7 @@ func (x *SyncStartedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncStartedEvent.ProtoReflect.Descriptor instead. func (*SyncStartedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{61} + return file_bridge_proto_rawDescGZIP(), []int{64} } func (x *SyncStartedEvent) GetUserID() string { @@ -4201,7 +4366,7 @@ type SyncFinishedEvent struct { func (x *SyncFinishedEvent) Reset() { *x = SyncFinishedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[62] + mi := &file_bridge_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4214,7 +4379,7 @@ func (x *SyncFinishedEvent) String() string { func (*SyncFinishedEvent) ProtoMessage() {} func (x *SyncFinishedEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[62] + mi := &file_bridge_proto_msgTypes[65] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4227,7 +4392,7 @@ func (x *SyncFinishedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncFinishedEvent.ProtoReflect.Descriptor instead. func (*SyncFinishedEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{62} + return file_bridge_proto_rawDescGZIP(), []int{65} } func (x *SyncFinishedEvent) GetUserID() string { @@ -4251,7 +4416,7 @@ type SyncProgressEvent struct { func (x *SyncProgressEvent) Reset() { *x = SyncProgressEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[63] + mi := &file_bridge_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4264,7 +4429,7 @@ func (x *SyncProgressEvent) String() string { func (*SyncProgressEvent) ProtoMessage() {} func (x *SyncProgressEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[63] + mi := &file_bridge_proto_msgTypes[66] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4277,7 +4442,7 @@ func (x *SyncProgressEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncProgressEvent.ProtoReflect.Descriptor instead. func (*SyncProgressEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{63} + return file_bridge_proto_rawDescGZIP(), []int{66} } func (x *SyncProgressEvent) GetUserID() string { @@ -4319,7 +4484,7 @@ type GenericErrorEvent struct { func (x *GenericErrorEvent) Reset() { *x = GenericErrorEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[64] + mi := &file_bridge_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4332,7 +4497,7 @@ func (x *GenericErrorEvent) String() string { func (*GenericErrorEvent) ProtoMessage() {} func (x *GenericErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[64] + mi := &file_bridge_proto_msgTypes[67] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4345,7 +4510,7 @@ func (x *GenericErrorEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use GenericErrorEvent.ProtoReflect.Descriptor instead. func (*GenericErrorEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{64} + return file_bridge_proto_rawDescGZIP(), []int{67} } func (x *GenericErrorEvent) GetCode() ErrorCode { @@ -4477,7 +4642,7 @@ var file_bridge_proto_rawDesc = []byte{ 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x22, 0xeb, 0x04, 0x0a, 0x08, 0x41, 0x70, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x22, 0x9d, 0x07, 0x0a, 0x08, 0x41, 0x70, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x76, @@ -4515,630 +4680,666 @@ var file_bridge_proto_rawDesc = []byte{ 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, - 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x22, 0x33, 0x0a, 0x13, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x1e, 0x0a, 0x1c, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, - 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x65, 0x74, 0x46, - 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x18, 0x0a, 0x16, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x42, 0x75, 0x67, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, - 0x15, 0x0a, 0x13, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x68, 0x6f, 0x77, 0x4d, 0x61, - 0x69, 0x6e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x18, 0x0a, - 0x16, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, - 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xe3, 0x02, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x42, 0x0a, 0x0c, 0x74, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x66, 0x61, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x5b, 0x0a, 0x14, 0x74, 0x77, 0x6f, - 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x77, 0x6f, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x14, 0x74, 0x77, 0x6f, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, - 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x44, - 0x0a, 0x0f, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x64, 0x49, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x4c, 0x6f, 0x67, 0x67, - 0x65, 0x64, 0x49, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x55, 0x0a, - 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x28, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x22, 0x34, 0x0a, 0x16, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x66, 0x61, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x21, 0x0a, 0x1f, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x54, 0x77, 0x6f, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x50, 0x0a, - 0x12, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x77, - 0x61, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0c, 0x77, 0x61, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x22, - 0xb9, 0x04, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x2e, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, - 0x40, 0x0a, 0x0b, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, - 0x79, 0x12, 0x58, 0x0a, 0x13, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, - 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x13, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x05, 0x66, - 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, - 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x13, 0x73, - 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, - 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x48, 0x00, 0x52, 0x13, 0x73, 0x69, 0x6c, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, - 0x12, 0x47, 0x0a, 0x0f, 0x69, 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0f, 0x69, 0x73, 0x4c, 0x61, 0x74, 0x65, - 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0d, 0x63, 0x68, 0x65, - 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x63, - 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x44, 0x0a, 0x0e, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x48, 0x00, 0x52, 0x0e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x3d, 0x0a, 0x10, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x32, 0x0a, 0x16, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x20, - 0x0a, 0x1e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x22, 0x2c, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x1b, - 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x15, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x22, 0x16, 0x0a, 0x14, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x64, 0x22, 0xeb, 0x01, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, - 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x31, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, - 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x43, 0x0a, 0x0b, 0x70, 0x61, 0x74, - 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, - 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, - 0x00, 0x52, 0x0b, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x58, - 0x0a, 0x12, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, - 0x73, 0x68, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, 0x72, 0x70, - 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x22, 0x43, 0x0a, 0x13, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, - 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2f, 0x0a, 0x19, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, - 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x22, 0x0a, 0x20, 0x44, 0x69, 0x73, 0x6b, 0x43, - 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, - 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xbf, 0x02, 0x0a, 0x17, - 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, - 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x64, 0x0a, 0x19, 0x63, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x53, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, + 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x19, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x67, + 0x0a, 0x1a, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, + 0x74, 0x61, 0x6c, 0x6c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1a, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x43, + 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x61, 0x0a, 0x18, 0x63, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x46, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, + 0x61, 0x6c, 0x6c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x18, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, + 0x74, 0x61, 0x6c, 0x6c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, + 0x65, 0x6e, 0x74, 0x22, 0x33, 0x0a, 0x13, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x1e, 0x0a, 0x1c, 0x54, 0x6f, 0x67, 0x67, + 0x6c, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x65, + 0x74, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x18, + 0x0a, 0x16, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x42, 0x75, 0x67, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x68, 0x6f, 0x77, + 0x4d, 0x61, 0x69, 0x6e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0x18, 0x0a, 0x16, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x46, 0x61, 0x6c, 0x6c, + 0x62, 0x61, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x20, 0x0a, 0x1e, 0x43, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x53, + 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x21, 0x0a, 0x1f, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, + 0x6c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x1f, + 0x0a, 0x1d, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, + 0x74, 0x61, 0x6c, 0x6c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0xe3, 0x02, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2d, + 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x42, 0x0a, + 0x0c, 0x74, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x54, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, + 0x64, 0x12, 0x5b, 0x0a, 0x14, 0x74, 0x77, 0x6f, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x77, 0x6f, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x74, 0x77, 0x6f, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x36, + 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, 0x6e, + 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x08, 0x66, 0x69, + 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x44, 0x0a, 0x0f, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, + 0x79, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x64, 0x49, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, + 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x6c, 0x72, + 0x65, 0x61, 0x64, 0x79, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x64, 0x49, 0x6e, 0x42, 0x07, 0x0a, 0x05, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x55, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x34, 0x0a, 0x16, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x66, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0x3d, 0x0a, 0x1f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x77, 0x6f, 0x50, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x22, 0x50, 0x0a, 0x12, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, + 0x22, 0x0a, 0x0c, 0x77, 0x61, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x77, 0x61, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x4f, 0x75, 0x74, 0x22, 0xb9, 0x04, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x12, 0x64, 0x0a, 0x19, 0x6d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, + 0x72, 0x6f, 0x72, 0x12, 0x40, 0x0a, 0x0b, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, + 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x61, 0x64, + 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, + 0x52, 0x65, 0x61, 0x64, 0x79, 0x12, 0x58, 0x0a, 0x13, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, + 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, + 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x13, 0x6d, 0x61, 0x6e, 0x75, + 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x12, + 0x2e, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x63, + 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, + 0x53, 0x0a, 0x13, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x6c, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x48, 0x00, 0x52, + 0x13, 0x73, 0x69, 0x6c, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, + 0x65, 0x64, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x0f, 0x69, 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x73, 0x4c, 0x61, 0x74, + 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0f, 0x69, 0x73, + 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, + 0x0d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x48, + 0x00, 0x52, 0x0d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, + 0x12, 0x44, 0x0a, 0x0e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0x3d, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x15, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x32, + 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x52, 0x65, + 0x61, 0x64, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x22, 0x20, 0x0a, 0x1e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x75, + 0x61, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6f, + 0x72, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x22, 0x1b, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x6c, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x22, + 0x17, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x73, 0x4c, 0x61, 0x74, 0x65, 0x73, + 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x15, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x22, + 0x16, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x22, 0xeb, 0x01, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x6b, + 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x31, 0x0a, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x43, 0x0a, + 0x0b, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, + 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x12, 0x58, 0x0a, 0x12, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, + 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x70, 0x61, 0x74, 0x68, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x43, 0x0a, 0x13, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, + 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2f, 0x0a, 0x19, 0x44, 0x69, + 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x22, 0x0a, 0x20, 0x44, + 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0xbf, 0x02, 0x0a, 0x17, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3a, 0x0a, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x64, 0x0a, 0x19, 0x6d, 0x61, 0x69, 0x6c, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x19, 0x6d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x79, 0x0a, + 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, + 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x19, - 0x6d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x79, 0x0a, 0x20, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, - 0x73, 0x68, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x55, 0x0a, - 0x1c, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x35, 0x0a, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x22, 0x54, 0x0a, 0x1e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x27, 0x0a, 0x25, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, - 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x22, 0xff, 0x01, 0x0a, 0x0d, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x5b, 0x0a, 0x16, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, - 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, + 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x22, 0x55, 0x0a, 0x1c, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, + 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x54, 0x0a, 0x1e, 0x4d, 0x61, 0x69, 0x6c, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x27, + 0x0a, 0x25, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xff, 0x01, 0x0a, 0x0d, 0x4b, 0x65, 0x79, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x5b, 0x0a, 0x16, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x16, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, - 0x65, 0x64, 0x12, 0x40, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, - 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x48, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x68, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x0f, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, - 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x72, 0x65, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x07, 0x0a, 0x05, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x1d, 0x0a, 0x1b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, - 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x48, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x22, 0xd9, 0x02, 0x0a, 0x09, 0x4d, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x68, 0x0a, 0x1c, 0x6e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, - 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, - 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, - 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1c, 0x6e, 0x6f, - 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, - 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0e, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x0e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, - 0x55, 0x0a, 0x14, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x14, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x61, 0x70, 0x69, 0x43, 0x65, 0x72, - 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, - 0x72, 0x70, 0x63, 0x2e, 0x41, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, - 0x49, 0x73, 0x73, 0x75, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x34, - 0x0a, 0x1c, 0x4e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, - 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x14, - 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, - 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x2f, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x35, 0x0a, 0x19, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x68, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, + 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x16, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, + 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x40, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x4e, 0x6f, 0x4b, + 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x61, 0x73, 0x4e, 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x68, 0x61, 0x73, 0x4e, 0x6f, + 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x0f, 0x72, 0x65, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x0f, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x1d, 0x0a, 0x1b, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x48, 0x61, 0x73, 0x4e, + 0x6f, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x16, + 0x0a, 0x14, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xd9, 0x02, 0x0a, 0x09, 0x4d, 0x61, 0x69, 0x6c, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x68, 0x0a, 0x1c, 0x6e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x4e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, + 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x1c, 0x6e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, + 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x43, + 0x0a, 0x0e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x0e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x64, 0x12, 0x55, 0x0a, 0x14, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x13, 0x0a, 0x11, - 0x41, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x22, 0xb4, 0x05, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x5e, 0x0a, 0x17, 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, - 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, - 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, - 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, - 0x49, 0x0a, 0x10, 0x75, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x75, 0x73, 0x65, 0x72, 0x44, 0x69, - 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x0b, 0x75, 0x73, - 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x42, 0x61, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, - 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x53, 0x0a, 0x15, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x15, - 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, 0x14, 0x69, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, - 0x00, 0x52, 0x14, 0x69, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, - 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x44, 0x0a, 0x10, 0x73, 0x79, 0x6e, 0x63, 0x53, - 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x73, 0x79, 0x6e, - 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, - 0x11, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x50, 0x72, - 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, - 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, - 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, - 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x36, 0x0a, 0x1c, 0x54, 0x6f, 0x67, 0x67, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x61, 0x70, + 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, + 0x73, 0x73, 0x75, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x70, 0x69, + 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x22, 0x34, 0x0a, 0x1c, 0x4e, 0x6f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4b, 0x65, + 0x79, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x2f, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x35, 0x0a, 0x19, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, + 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x22, 0x13, 0x0a, 0x11, 0x41, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xb4, 0x05, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x5e, 0x0a, 0x17, 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, + 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, 0x74, 0x6f, 0x67, 0x67, + 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x10, 0x75, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x75, 0x73, + 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x3a, + 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x75, + 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x0c, 0x75, 0x73, + 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x53, 0x0a, 0x15, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, + 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x64, 0x42, + 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x15, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, 0x14, 0x69, 0x6d, 0x61, + 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, + 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x69, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x44, 0x0a, 0x10, 0x73, + 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, + 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x10, 0x73, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x47, 0x0a, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, + 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x11, 0x73, 0x79, + 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, + 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x36, 0x0a, 0x1c, + 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, + 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x44, 0x22, 0x33, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, + 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x10, 0x55, 0x73, 0x65, + 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x4a, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x22, 0x0a, + 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0x4d, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, + 0x22, 0x32, 0x0a, 0x14, 0x49, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, + 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x10, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, - 0x22, 0x33, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, - 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, - 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x10, 0x55, 0x73, 0x65, 0x72, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x44, 0x22, 0x4a, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x4d, 0x0a, - 0x15, 0x55, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1c, - 0x0a, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x32, 0x0a, 0x14, - 0x49, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, - 0x22, 0x2a, 0x0a, 0x10, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x2b, 0x0a, 0x11, - 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x87, 0x01, 0x0a, 0x11, 0x53, 0x79, - 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, - 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x4d, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x4d, - 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x4d, 0x73, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, - 0x67, 0x4d, 0x73, 0x22, 0x38, 0x0a, 0x11, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x2a, 0x71, 0x0a, - 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, - 0x5f, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, - 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x47, 0x5f, 0x57, 0x41, - 0x52, 0x4e, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x47, 0x5f, 0x49, 0x4e, 0x46, 0x4f, - 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, - 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x06, - 0x2a, 0x36, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, - 0x0a, 0x53, 0x49, 0x47, 0x4e, 0x45, 0x44, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x00, 0x12, 0x0a, 0x0a, - 0x06, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, - 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xa2, 0x01, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, - 0x53, 0x45, 0x52, 0x4e, 0x41, 0x4d, 0x45, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, - 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x52, 0x45, 0x45, - 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4e, 0x4e, 0x45, - 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, - 0x09, 0x54, 0x46, 0x41, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, - 0x54, 0x46, 0x41, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x04, 0x12, 0x17, 0x0a, 0x13, 0x54, - 0x57, 0x4f, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x05, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x57, 0x4f, 0x5f, 0x50, 0x41, 0x53, 0x53, - 0x57, 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x06, 0x2a, 0x5b, 0x0a, - 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x4d, 0x41, 0x4e, 0x55, 0x41, - 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x55, 0x50, 0x44, - 0x41, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x01, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x49, 0x4c, 0x45, - 0x4e, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, 0x6b, 0x0a, 0x12, 0x44, 0x69, - 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x20, 0x0a, 0x1c, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x5f, 0x55, - 0x4e, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, - 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x41, 0x4e, 0x54, 0x5f, 0x4d, 0x4f, 0x56, 0x45, 0x5f, - 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, - 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, 0xdd, 0x01, 0x0a, 0x1b, 0x4d, 0x61, 0x69, 0x6c, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x49, 0x4d, 0x41, 0x50, 0x5f, - 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, 0x50, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, 0x4f, 0x52, - 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x43, - 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x1a, 0x0a, - 0x16, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, - 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x49, 0x4d, 0x41, - 0x50, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, - 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, - 0x12, 0x25, 0x0a, 0x21, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, - 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x2a, 0x53, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x4c, 0x53, 0x5f, 0x43, - 0x45, 0x52, 0x54, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, - 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x4c, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x45, 0x58, - 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x32, 0xe2, 0x20, 0x0a, - 0x06, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x22, 0x2b, 0x0a, 0x11, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x87, 0x01, + 0x0a, 0x11, 0x53, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x70, + 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x6c, 0x61, 0x70, 0x73, + 0x65, 0x64, 0x4d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x6c, 0x61, 0x70, + 0x73, 0x65, 0x64, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, + 0x6e, 0x67, 0x4d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x61, + 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x4d, 0x73, 0x22, 0x38, 0x0a, 0x11, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x69, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x04, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, + 0x65, 0x2a, 0x71, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0d, 0x0a, + 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, + 0x4c, 0x4f, 0x47, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4c, + 0x4f, 0x47, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, + 0x47, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x47, 0x5f, + 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x44, 0x45, + 0x42, 0x55, 0x47, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x54, 0x52, 0x41, + 0x43, 0x45, 0x10, 0x06, 0x2a, 0x36, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x49, 0x47, 0x4e, 0x45, 0x44, 0x5f, 0x4f, 0x55, 0x54, 0x10, + 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, + 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xa2, 0x01, 0x0a, + 0x0e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x1b, 0x0a, 0x17, 0x55, 0x53, 0x45, 0x52, 0x4e, 0x41, 0x4d, 0x45, 0x5f, 0x50, 0x41, 0x53, 0x53, + 0x57, 0x4f, 0x52, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, + 0x46, 0x52, 0x45, 0x45, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x43, + 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, + 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x46, 0x41, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, + 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x46, 0x41, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x04, 0x12, + 0x17, 0x0a, 0x13, 0x54, 0x57, 0x4f, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x53, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x57, 0x4f, 0x5f, + 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, + 0x06, 0x2a, 0x5b, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x4d, + 0x41, 0x4e, 0x55, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x16, 0x0a, + 0x12, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, + 0x53, 0x49, 0x4c, 0x45, 0x4e, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, 0x6b, + 0x0a, 0x12, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x1c, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, + 0x48, 0x45, 0x5f, 0x55, 0x4e, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x41, 0x4e, 0x54, 0x5f, 0x4d, + 0x4f, 0x56, 0x45, 0x5f, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x46, + 0x55, 0x4c, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, 0xdd, 0x01, 0x0a, 0x1b, + 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x49, + 0x4d, 0x41, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, 0x50, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x4d, 0x54, 0x50, + 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, 0x50, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x50, 0x4f, + 0x52, 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, + 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x43, + 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x25, 0x0a, + 0x21, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x10, 0x04, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x43, 0x4f, 0x4e, + 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x48, 0x41, + 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x2a, 0x53, 0x0a, 0x09, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x4e, 0x4b, 0x4e, + 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x54, + 0x4c, 0x53, 0x5f, 0x43, 0x45, 0x52, 0x54, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x4c, 0x53, 0x5f, 0x4b, 0x45, + 0x59, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, + 0x32, 0xfc, 0x21, 0x0a, 0x06, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, + 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x08, 0x47, 0x75, 0x69, 0x52, 0x65, + 0x61, 0x64, 0x79, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x51, 0x75, 0x69, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x08, 0x47, 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, 0x12, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x36, 0x0a, 0x04, 0x51, 0x75, 0x69, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x39, 0x0a, 0x07, 0x52, + 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x4f, 0x6e, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x39, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x12, 0x43, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x4f, 0x6e, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x75, 0x70, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, - 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x49, 0x73, - 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x6e, 0x12, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, - 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, - 0x43, 0x0a, 0x0d, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x6e, - 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x49, 0x73, 0x42, 0x65, 0x74, - 0x61, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x0d, - 0x49, 0x73, 0x42, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x53, + 0x65, 0x74, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x6e, 0x12, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x0d, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x4f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, + 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x49, + 0x73, 0x42, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, + 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x12, 0x43, 0x0a, 0x0d, 0x49, 0x73, 0x42, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x49, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x6c, + 0x6c, 0x4d, 0x61, 0x69, 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, + 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x12, 0x46, 0x0a, 0x10, 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x69, 0x6c, 0x56, 0x69, 0x73, + 0x69, 0x62, 0x6c, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, + 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4c, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x49, + 0x73, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x13, 0x49, 0x73, 0x54, 0x65, 0x6c, 0x65, + 0x6d, 0x65, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x49, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x69, - 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x46, 0x0a, 0x10, - 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x69, 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, - 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4c, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x49, 0x73, 0x54, 0x65, 0x6c, - 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x12, 0x49, 0x0a, 0x13, 0x49, 0x73, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, - 0x79, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3c, 0x0a, - 0x04, 0x47, 0x6f, 0x4f, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x54, - 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x65, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x07, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x40, 0x0a, 0x08, - 0x4c, 0x6f, 0x67, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x43, - 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x12, 0x4c, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4e, 0x6f, - 0x74, 0x65, 0x73, 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x4e, 0x0a, 0x16, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x4c, - 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x47, 0x0a, 0x0f, 0x4c, 0x61, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x67, 0x65, - 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, - 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, - 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x47, 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, - 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x65, 0x12, 0x3c, 0x0a, 0x04, 0x47, 0x6f, 0x4f, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x4a, 0x0a, 0x12, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4d, 0x0a, 0x15, 0x45, 0x78, 0x70, 0x6f, - 0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, + 0x3e, 0x0a, 0x0c, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x65, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x45, 0x0a, 0x0d, 0x46, 0x6f, 0x72, 0x63, 0x65, - 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, - 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, - 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x36, - 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x32, 0x46, 0x41, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, - 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x32, - 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, - 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, - 0x41, 0x62, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0d, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4c, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x75, 0x74, - 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x12, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, + 0x3f, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x12, 0x49, 0x0a, 0x13, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, - 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x45, 0x0a, - 0x0d, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x48, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x44, 0x69, 0x73, 0x6b, 0x43, - 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, - 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x45, - 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x49, 0x73, 0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x0c, 0x49, 0x73, 0x44, 0x6f, 0x48, 0x45, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x44, 0x0a, 0x12, 0x4d, 0x61, 0x69, - 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, + 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x40, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x50, 0x61, 0x74, + 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4c, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, - 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, - 0x47, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, - 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x40, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, - 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x49, 0x73, - 0x50, 0x6f, 0x72, 0x74, 0x46, 0x72, 0x65, 0x65, 0x12, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x4e, 0x0a, 0x12, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, - 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x20, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, - 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4b, - 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x47, 0x0a, - 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, - 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x0a, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, - 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1a, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, - 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x12, 0x55, 0x0a, 0x18, 0x53, 0x65, 0x6e, 0x64, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x21, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, - 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4e, 0x0a, 0x16, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, + 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x4c, 0x69, 0x6e, 0x6b, 0x12, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, - 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x12, 0x51, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x55, 0x73, - 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x41, 0x70, 0x70, 0x6c, - 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, - 0x67, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x0f, 0x4c, 0x61, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x11, 0x41, 0x75, 0x74, 0x6f, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x1c, 0x2e, + 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4a, + 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x47, 0x0a, 0x0f, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x45, 0x6d, + 0x61, 0x69, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x3b, 0x0a, 0x09, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x12, 0x16, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x45, 0x0a, 0x0d, + 0x46, 0x6f, 0x72, 0x63, 0x65, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x12, 0x48, 0x0a, 0x10, 0x4b, 0x42, 0x41, 0x72, 0x74, 0x69, 0x63, 0x6c, 0x65, - 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, - 0x0e, 0x52, 0x75, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, - 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x41, - 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x69, 0x6e, 0x45, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x33, + 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x36, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x32, 0x46, 0x41, 0x12, + 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0f, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x32, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x12, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0a, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0b, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0d, 0x49, 0x6e, 0x73, 0x74, + 0x61, 0x6c, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x4d, 0x61, 0x69, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x6e, 0x2d, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x2f, 0x76, 0x33, 0x2f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4c, 0x0a, 0x16, 0x53, 0x65, 0x74, + 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x4f, 0x6e, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x13, 0x49, 0x73, 0x41, 0x75, 0x74, + 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x45, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, + 0x61, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x48, 0x0a, 0x10, 0x53, 0x65, 0x74, + 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x45, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x49, 0x73, 0x44, 0x6f, 0x48, 0x45, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x0c, 0x49, 0x73, + 0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x44, + 0x0a, 0x12, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x12, 0x47, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x69, 0x6c, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, + 0x74, 0x69, 0x6e, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x40, 0x0a, + 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x45, 0x0a, 0x0a, 0x49, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x72, 0x65, 0x65, 0x12, 0x1b, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, + 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4e, 0x0a, 0x12, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, + 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x20, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x76, 0x61, 0x69, + 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0x47, 0x0a, 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x47, + 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x47, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x1a, 0x0a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x12, + 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, + 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, + 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x55, 0x0a, 0x18, 0x53, 0x65, 0x6e, 0x64, 0x42, + 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x65, 0x65, 0x64, 0x62, + 0x61, 0x63, 0x6b, 0x12, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42, + 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, + 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0x42, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, + 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x51, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d, 0x61, 0x69, 0x6c, + 0x12, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x65, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x10, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, + 0x11, 0x41, 0x75, 0x74, 0x6f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x43, 0x6c, 0x69, 0x63, 0x6b, + 0x65, 0x64, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x48, 0x0a, 0x10, 0x4b, 0x42, 0x41, 0x72, + 0x74, 0x69, 0x63, 0x6c, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0x4f, 0x0a, 0x19, 0x49, 0x73, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x12, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x15, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x54, 0x4c, + 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4d, 0x0a, 0x15, + 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0e, 0x52, + 0x75, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f, + 0x53, 0x74, 0x6f, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, + 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x6e, 0x4d, 0x61, 0x69, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2d, + 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x2f, 0x76, 0x33, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -5154,7 +5355,7 @@ func file_bridge_proto_rawDescGZIP() []byte { } var file_bridge_proto_enumTypes = make([]protoimpl.EnumInfo, 7) -var file_bridge_proto_msgTypes = make([]protoimpl.MessageInfo, 65) +var file_bridge_proto_msgTypes = make([]protoimpl.MessageInfo, 68) var file_bridge_proto_goTypes = []interface{}{ (LogLevel)(0), // 0: grpc.LogLevel (UserState)(0), // 1: grpc.UserState @@ -5186,66 +5387,69 @@ var file_bridge_proto_goTypes = []interface{}{ (*ReportBugErrorEvent)(nil), // 27: grpc.ReportBugErrorEvent (*ShowMainWindowEvent)(nil), // 28: grpc.ShowMainWindowEvent (*ReportBugFallbackEvent)(nil), // 29: grpc.ReportBugFallbackEvent - (*LoginEvent)(nil), // 30: grpc.LoginEvent - (*LoginErrorEvent)(nil), // 31: grpc.LoginErrorEvent - (*LoginTfaRequestedEvent)(nil), // 32: grpc.LoginTfaRequestedEvent - (*LoginTwoPasswordsRequestedEvent)(nil), // 33: grpc.LoginTwoPasswordsRequestedEvent - (*LoginFinishedEvent)(nil), // 34: grpc.LoginFinishedEvent - (*UpdateEvent)(nil), // 35: grpc.UpdateEvent - (*UpdateErrorEvent)(nil), // 36: grpc.UpdateErrorEvent - (*UpdateManualReadyEvent)(nil), // 37: grpc.UpdateManualReadyEvent - (*UpdateManualRestartNeededEvent)(nil), // 38: grpc.UpdateManualRestartNeededEvent - (*UpdateForceEvent)(nil), // 39: grpc.UpdateForceEvent - (*UpdateSilentRestartNeeded)(nil), // 40: grpc.UpdateSilentRestartNeeded - (*UpdateIsLatestVersion)(nil), // 41: grpc.UpdateIsLatestVersion - (*UpdateCheckFinished)(nil), // 42: grpc.UpdateCheckFinished - (*UpdateVersionChanged)(nil), // 43: grpc.UpdateVersionChanged - (*DiskCacheEvent)(nil), // 44: grpc.DiskCacheEvent - (*DiskCacheErrorEvent)(nil), // 45: grpc.DiskCacheErrorEvent - (*DiskCachePathChangedEvent)(nil), // 46: grpc.DiskCachePathChangedEvent - (*DiskCachePathChangeFinishedEvent)(nil), // 47: grpc.DiskCachePathChangeFinishedEvent - (*MailServerSettingsEvent)(nil), // 48: grpc.MailServerSettingsEvent - (*MailServerSettingsErrorEvent)(nil), // 49: grpc.MailServerSettingsErrorEvent - (*MailServerSettingsChangedEvent)(nil), // 50: grpc.MailServerSettingsChangedEvent - (*ChangeMailServerSettingsFinishedEvent)(nil), // 51: grpc.ChangeMailServerSettingsFinishedEvent - (*KeychainEvent)(nil), // 52: grpc.KeychainEvent - (*ChangeKeychainFinishedEvent)(nil), // 53: grpc.ChangeKeychainFinishedEvent - (*HasNoKeychainEvent)(nil), // 54: grpc.HasNoKeychainEvent - (*RebuildKeychainEvent)(nil), // 55: grpc.RebuildKeychainEvent - (*MailEvent)(nil), // 56: grpc.MailEvent - (*NoActiveKeyForRecipientEvent)(nil), // 57: grpc.NoActiveKeyForRecipientEvent - (*AddressChangedEvent)(nil), // 58: grpc.AddressChangedEvent - (*AddressChangedLogoutEvent)(nil), // 59: grpc.AddressChangedLogoutEvent - (*ApiCertIssueEvent)(nil), // 60: grpc.ApiCertIssueEvent - (*UserEvent)(nil), // 61: grpc.UserEvent - (*ToggleSplitModeFinishedEvent)(nil), // 62: grpc.ToggleSplitModeFinishedEvent - (*UserDisconnectedEvent)(nil), // 63: grpc.UserDisconnectedEvent - (*UserChangedEvent)(nil), // 64: grpc.UserChangedEvent - (*UserBadEvent)(nil), // 65: grpc.UserBadEvent - (*UsedBytesChangedEvent)(nil), // 66: grpc.UsedBytesChangedEvent - (*ImapLoginFailedEvent)(nil), // 67: grpc.ImapLoginFailedEvent - (*SyncStartedEvent)(nil), // 68: grpc.SyncStartedEvent - (*SyncFinishedEvent)(nil), // 69: grpc.SyncFinishedEvent - (*SyncProgressEvent)(nil), // 70: grpc.SyncProgressEvent - (*GenericErrorEvent)(nil), // 71: grpc.GenericErrorEvent - (*wrapperspb.StringValue)(nil), // 72: google.protobuf.StringValue - (*emptypb.Empty)(nil), // 73: google.protobuf.Empty - (*wrapperspb.BoolValue)(nil), // 74: google.protobuf.BoolValue - (*wrapperspb.Int32Value)(nil), // 75: google.protobuf.Int32Value + (*CertificateInstallSuccessEvent)(nil), // 30: grpc.CertificateInstallSuccessEvent + (*CertificateInstallCanceledEvent)(nil), // 31: grpc.CertificateInstallCanceledEvent + (*CertificateInstallFailedEvent)(nil), // 32: grpc.CertificateInstallFailedEvent + (*LoginEvent)(nil), // 33: grpc.LoginEvent + (*LoginErrorEvent)(nil), // 34: grpc.LoginErrorEvent + (*LoginTfaRequestedEvent)(nil), // 35: grpc.LoginTfaRequestedEvent + (*LoginTwoPasswordsRequestedEvent)(nil), // 36: grpc.LoginTwoPasswordsRequestedEvent + (*LoginFinishedEvent)(nil), // 37: grpc.LoginFinishedEvent + (*UpdateEvent)(nil), // 38: grpc.UpdateEvent + (*UpdateErrorEvent)(nil), // 39: grpc.UpdateErrorEvent + (*UpdateManualReadyEvent)(nil), // 40: grpc.UpdateManualReadyEvent + (*UpdateManualRestartNeededEvent)(nil), // 41: grpc.UpdateManualRestartNeededEvent + (*UpdateForceEvent)(nil), // 42: grpc.UpdateForceEvent + (*UpdateSilentRestartNeeded)(nil), // 43: grpc.UpdateSilentRestartNeeded + (*UpdateIsLatestVersion)(nil), // 44: grpc.UpdateIsLatestVersion + (*UpdateCheckFinished)(nil), // 45: grpc.UpdateCheckFinished + (*UpdateVersionChanged)(nil), // 46: grpc.UpdateVersionChanged + (*DiskCacheEvent)(nil), // 47: grpc.DiskCacheEvent + (*DiskCacheErrorEvent)(nil), // 48: grpc.DiskCacheErrorEvent + (*DiskCachePathChangedEvent)(nil), // 49: grpc.DiskCachePathChangedEvent + (*DiskCachePathChangeFinishedEvent)(nil), // 50: grpc.DiskCachePathChangeFinishedEvent + (*MailServerSettingsEvent)(nil), // 51: grpc.MailServerSettingsEvent + (*MailServerSettingsErrorEvent)(nil), // 52: grpc.MailServerSettingsErrorEvent + (*MailServerSettingsChangedEvent)(nil), // 53: grpc.MailServerSettingsChangedEvent + (*ChangeMailServerSettingsFinishedEvent)(nil), // 54: grpc.ChangeMailServerSettingsFinishedEvent + (*KeychainEvent)(nil), // 55: grpc.KeychainEvent + (*ChangeKeychainFinishedEvent)(nil), // 56: grpc.ChangeKeychainFinishedEvent + (*HasNoKeychainEvent)(nil), // 57: grpc.HasNoKeychainEvent + (*RebuildKeychainEvent)(nil), // 58: grpc.RebuildKeychainEvent + (*MailEvent)(nil), // 59: grpc.MailEvent + (*NoActiveKeyForRecipientEvent)(nil), // 60: grpc.NoActiveKeyForRecipientEvent + (*AddressChangedEvent)(nil), // 61: grpc.AddressChangedEvent + (*AddressChangedLogoutEvent)(nil), // 62: grpc.AddressChangedLogoutEvent + (*ApiCertIssueEvent)(nil), // 63: grpc.ApiCertIssueEvent + (*UserEvent)(nil), // 64: grpc.UserEvent + (*ToggleSplitModeFinishedEvent)(nil), // 65: grpc.ToggleSplitModeFinishedEvent + (*UserDisconnectedEvent)(nil), // 66: grpc.UserDisconnectedEvent + (*UserChangedEvent)(nil), // 67: grpc.UserChangedEvent + (*UserBadEvent)(nil), // 68: grpc.UserBadEvent + (*UsedBytesChangedEvent)(nil), // 69: grpc.UsedBytesChangedEvent + (*ImapLoginFailedEvent)(nil), // 70: grpc.ImapLoginFailedEvent + (*SyncStartedEvent)(nil), // 71: grpc.SyncStartedEvent + (*SyncFinishedEvent)(nil), // 72: grpc.SyncFinishedEvent + (*SyncProgressEvent)(nil), // 73: grpc.SyncProgressEvent + (*GenericErrorEvent)(nil), // 74: grpc.GenericErrorEvent + (*wrapperspb.StringValue)(nil), // 75: google.protobuf.StringValue + (*emptypb.Empty)(nil), // 76: google.protobuf.Empty + (*wrapperspb.BoolValue)(nil), // 77: google.protobuf.BoolValue + (*wrapperspb.Int32Value)(nil), // 78: google.protobuf.Int32Value } var file_bridge_proto_depIdxs = []int32{ 0, // 0: grpc.AddLogEntryRequest.level:type_name -> grpc.LogLevel 1, // 1: grpc.User.state:type_name -> grpc.UserState 14, // 2: grpc.UserListResponse.users:type_name -> grpc.User 21, // 3: grpc.StreamEvent.app:type_name -> grpc.AppEvent - 30, // 4: grpc.StreamEvent.login:type_name -> grpc.LoginEvent - 35, // 5: grpc.StreamEvent.update:type_name -> grpc.UpdateEvent - 44, // 6: grpc.StreamEvent.cache:type_name -> grpc.DiskCacheEvent - 48, // 7: grpc.StreamEvent.mailServerSettings:type_name -> grpc.MailServerSettingsEvent - 52, // 8: grpc.StreamEvent.keychain:type_name -> grpc.KeychainEvent - 56, // 9: grpc.StreamEvent.mail:type_name -> grpc.MailEvent - 61, // 10: grpc.StreamEvent.user:type_name -> grpc.UserEvent - 71, // 11: grpc.StreamEvent.genericError:type_name -> grpc.GenericErrorEvent + 33, // 4: grpc.StreamEvent.login:type_name -> grpc.LoginEvent + 38, // 5: grpc.StreamEvent.update:type_name -> grpc.UpdateEvent + 47, // 6: grpc.StreamEvent.cache:type_name -> grpc.DiskCacheEvent + 51, // 7: grpc.StreamEvent.mailServerSettings:type_name -> grpc.MailServerSettingsEvent + 55, // 8: grpc.StreamEvent.keychain:type_name -> grpc.KeychainEvent + 59, // 9: grpc.StreamEvent.mail:type_name -> grpc.MailEvent + 64, // 10: grpc.StreamEvent.user:type_name -> grpc.UserEvent + 74, // 11: grpc.StreamEvent.genericError:type_name -> grpc.GenericErrorEvent 22, // 12: grpc.AppEvent.internetStatus:type_name -> grpc.InternetStatusEvent 23, // 13: grpc.AppEvent.toggleAutostartFinished:type_name -> grpc.ToggleAutostartFinishedEvent 24, // 14: grpc.AppEvent.resetFinished:type_name -> grpc.ResetFinishedEvent @@ -5254,172 +5458,179 @@ var file_bridge_proto_depIdxs = []int32{ 27, // 17: grpc.AppEvent.reportBugError:type_name -> grpc.ReportBugErrorEvent 28, // 18: grpc.AppEvent.showMainWindow:type_name -> grpc.ShowMainWindowEvent 29, // 19: grpc.AppEvent.reportBugFallback:type_name -> grpc.ReportBugFallbackEvent - 31, // 20: grpc.LoginEvent.error:type_name -> grpc.LoginErrorEvent - 32, // 21: grpc.LoginEvent.tfaRequested:type_name -> grpc.LoginTfaRequestedEvent - 33, // 22: grpc.LoginEvent.twoPasswordRequested:type_name -> grpc.LoginTwoPasswordsRequestedEvent - 34, // 23: grpc.LoginEvent.finished:type_name -> grpc.LoginFinishedEvent - 34, // 24: grpc.LoginEvent.alreadyLoggedIn:type_name -> grpc.LoginFinishedEvent - 2, // 25: grpc.LoginErrorEvent.type:type_name -> grpc.LoginErrorType - 36, // 26: grpc.UpdateEvent.error:type_name -> grpc.UpdateErrorEvent - 37, // 27: grpc.UpdateEvent.manualReady:type_name -> grpc.UpdateManualReadyEvent - 38, // 28: grpc.UpdateEvent.manualRestartNeeded:type_name -> grpc.UpdateManualRestartNeededEvent - 39, // 29: grpc.UpdateEvent.force:type_name -> grpc.UpdateForceEvent - 40, // 30: grpc.UpdateEvent.silentRestartNeeded:type_name -> grpc.UpdateSilentRestartNeeded - 41, // 31: grpc.UpdateEvent.isLatestVersion:type_name -> grpc.UpdateIsLatestVersion - 42, // 32: grpc.UpdateEvent.checkFinished:type_name -> grpc.UpdateCheckFinished - 43, // 33: grpc.UpdateEvent.versionChanged:type_name -> grpc.UpdateVersionChanged - 3, // 34: grpc.UpdateErrorEvent.type:type_name -> grpc.UpdateErrorType - 45, // 35: grpc.DiskCacheEvent.error:type_name -> grpc.DiskCacheErrorEvent - 46, // 36: grpc.DiskCacheEvent.pathChanged:type_name -> grpc.DiskCachePathChangedEvent - 47, // 37: grpc.DiskCacheEvent.pathChangeFinished:type_name -> grpc.DiskCachePathChangeFinishedEvent - 4, // 38: grpc.DiskCacheErrorEvent.type:type_name -> grpc.DiskCacheErrorType - 49, // 39: grpc.MailServerSettingsEvent.error:type_name -> grpc.MailServerSettingsErrorEvent - 50, // 40: grpc.MailServerSettingsEvent.mailServerSettingsChanged:type_name -> grpc.MailServerSettingsChangedEvent - 51, // 41: grpc.MailServerSettingsEvent.changeMailServerSettingsFinished:type_name -> grpc.ChangeMailServerSettingsFinishedEvent - 5, // 42: grpc.MailServerSettingsErrorEvent.type:type_name -> grpc.MailServerSettingsErrorType - 12, // 43: grpc.MailServerSettingsChangedEvent.settings:type_name -> grpc.ImapSmtpSettings - 53, // 44: grpc.KeychainEvent.changeKeychainFinished:type_name -> grpc.ChangeKeychainFinishedEvent - 54, // 45: grpc.KeychainEvent.hasNoKeychain:type_name -> grpc.HasNoKeychainEvent - 55, // 46: grpc.KeychainEvent.rebuildKeychain:type_name -> grpc.RebuildKeychainEvent - 57, // 47: grpc.MailEvent.noActiveKeyForRecipientEvent:type_name -> grpc.NoActiveKeyForRecipientEvent - 58, // 48: grpc.MailEvent.addressChanged:type_name -> grpc.AddressChangedEvent - 59, // 49: grpc.MailEvent.addressChangedLogout:type_name -> grpc.AddressChangedLogoutEvent - 60, // 50: grpc.MailEvent.apiCertIssue:type_name -> grpc.ApiCertIssueEvent - 62, // 51: grpc.UserEvent.toggleSplitModeFinished:type_name -> grpc.ToggleSplitModeFinishedEvent - 63, // 52: grpc.UserEvent.userDisconnected:type_name -> grpc.UserDisconnectedEvent - 64, // 53: grpc.UserEvent.userChanged:type_name -> grpc.UserChangedEvent - 65, // 54: grpc.UserEvent.userBadEvent:type_name -> grpc.UserBadEvent - 66, // 55: grpc.UserEvent.usedBytesChangedEvent:type_name -> grpc.UsedBytesChangedEvent - 67, // 56: grpc.UserEvent.imapLoginFailedEvent:type_name -> grpc.ImapLoginFailedEvent - 68, // 57: grpc.UserEvent.syncStartedEvent:type_name -> grpc.SyncStartedEvent - 69, // 58: grpc.UserEvent.syncFinishedEvent:type_name -> grpc.SyncFinishedEvent - 70, // 59: grpc.UserEvent.syncProgressEvent:type_name -> grpc.SyncProgressEvent - 6, // 60: grpc.GenericErrorEvent.code:type_name -> grpc.ErrorCode - 72, // 61: grpc.Bridge.CheckTokens:input_type -> google.protobuf.StringValue - 7, // 62: grpc.Bridge.AddLogEntry:input_type -> grpc.AddLogEntryRequest - 73, // 63: grpc.Bridge.GuiReady:input_type -> google.protobuf.Empty - 73, // 64: grpc.Bridge.Quit:input_type -> google.protobuf.Empty - 73, // 65: grpc.Bridge.Restart:input_type -> google.protobuf.Empty - 73, // 66: grpc.Bridge.ShowOnStartup:input_type -> google.protobuf.Empty - 74, // 67: grpc.Bridge.SetIsAutostartOn:input_type -> google.protobuf.BoolValue - 73, // 68: grpc.Bridge.IsAutostartOn:input_type -> google.protobuf.Empty - 74, // 69: grpc.Bridge.SetIsBetaEnabled:input_type -> google.protobuf.BoolValue - 73, // 70: grpc.Bridge.IsBetaEnabled:input_type -> google.protobuf.Empty - 74, // 71: grpc.Bridge.SetIsAllMailVisible:input_type -> google.protobuf.BoolValue - 73, // 72: grpc.Bridge.IsAllMailVisible:input_type -> google.protobuf.Empty - 74, // 73: grpc.Bridge.SetIsTelemetryDisabled:input_type -> google.protobuf.BoolValue - 73, // 74: grpc.Bridge.IsTelemetryDisabled:input_type -> google.protobuf.Empty - 73, // 75: grpc.Bridge.GoOs:input_type -> google.protobuf.Empty - 73, // 76: grpc.Bridge.TriggerReset:input_type -> google.protobuf.Empty - 73, // 77: grpc.Bridge.Version:input_type -> google.protobuf.Empty - 73, // 78: grpc.Bridge.LogsPath:input_type -> google.protobuf.Empty - 73, // 79: grpc.Bridge.LicensePath:input_type -> google.protobuf.Empty - 73, // 80: grpc.Bridge.ReleaseNotesPageLink:input_type -> google.protobuf.Empty - 73, // 81: grpc.Bridge.DependencyLicensesLink:input_type -> google.protobuf.Empty - 73, // 82: grpc.Bridge.LandingPageLink:input_type -> google.protobuf.Empty - 72, // 83: grpc.Bridge.SetColorSchemeName:input_type -> google.protobuf.StringValue - 73, // 84: grpc.Bridge.ColorSchemeName:input_type -> google.protobuf.Empty - 73, // 85: grpc.Bridge.CurrentEmailClient:input_type -> google.protobuf.Empty - 9, // 86: grpc.Bridge.ReportBug:input_type -> grpc.ReportBugRequest - 72, // 87: grpc.Bridge.ExportTLSCertificates:input_type -> google.protobuf.StringValue - 72, // 88: grpc.Bridge.ForceLauncher:input_type -> google.protobuf.StringValue - 72, // 89: grpc.Bridge.SetMainExecutable:input_type -> google.protobuf.StringValue - 10, // 90: grpc.Bridge.Login:input_type -> grpc.LoginRequest - 10, // 91: grpc.Bridge.Login2FA:input_type -> grpc.LoginRequest - 10, // 92: grpc.Bridge.Login2Passwords:input_type -> grpc.LoginRequest - 11, // 93: grpc.Bridge.LoginAbort:input_type -> grpc.LoginAbortRequest - 73, // 94: grpc.Bridge.CheckUpdate:input_type -> google.protobuf.Empty - 73, // 95: grpc.Bridge.InstallUpdate:input_type -> google.protobuf.Empty - 74, // 96: grpc.Bridge.SetIsAutomaticUpdateOn:input_type -> google.protobuf.BoolValue - 73, // 97: grpc.Bridge.IsAutomaticUpdateOn:input_type -> google.protobuf.Empty - 73, // 98: grpc.Bridge.DiskCachePath:input_type -> google.protobuf.Empty - 72, // 99: grpc.Bridge.SetDiskCachePath:input_type -> google.protobuf.StringValue - 74, // 100: grpc.Bridge.SetIsDoHEnabled:input_type -> google.protobuf.BoolValue - 73, // 101: grpc.Bridge.IsDoHEnabled:input_type -> google.protobuf.Empty - 73, // 102: grpc.Bridge.MailServerSettings:input_type -> google.protobuf.Empty - 12, // 103: grpc.Bridge.SetMailServerSettings:input_type -> grpc.ImapSmtpSettings - 73, // 104: grpc.Bridge.Hostname:input_type -> google.protobuf.Empty - 75, // 105: grpc.Bridge.IsPortFree:input_type -> google.protobuf.Int32Value - 73, // 106: grpc.Bridge.AvailableKeychains:input_type -> google.protobuf.Empty - 72, // 107: grpc.Bridge.SetCurrentKeychain:input_type -> google.protobuf.StringValue - 73, // 108: grpc.Bridge.CurrentKeychain:input_type -> google.protobuf.Empty - 73, // 109: grpc.Bridge.GetUserList:input_type -> google.protobuf.Empty - 72, // 110: grpc.Bridge.GetUser:input_type -> google.protobuf.StringValue - 15, // 111: grpc.Bridge.SetUserSplitMode:input_type -> grpc.UserSplitModeRequest - 16, // 112: grpc.Bridge.SendBadEventUserFeedback:input_type -> grpc.UserBadEventFeedbackRequest - 72, // 113: grpc.Bridge.LogoutUser:input_type -> google.protobuf.StringValue - 72, // 114: grpc.Bridge.RemoveUser:input_type -> google.protobuf.StringValue - 18, // 115: grpc.Bridge.ConfigureUserAppleMail:input_type -> grpc.ConfigureAppleMailRequest - 73, // 116: grpc.Bridge.ReportBugClicked:input_type -> google.protobuf.Empty - 72, // 117: grpc.Bridge.AutoconfigClicked:input_type -> google.protobuf.StringValue - 72, // 118: grpc.Bridge.KBArticleClicked:input_type -> google.protobuf.StringValue - 19, // 119: grpc.Bridge.RunEventStream:input_type -> grpc.EventStreamRequest - 73, // 120: grpc.Bridge.StopEventStream:input_type -> google.protobuf.Empty - 72, // 121: grpc.Bridge.CheckTokens:output_type -> google.protobuf.StringValue - 73, // 122: grpc.Bridge.AddLogEntry:output_type -> google.protobuf.Empty - 8, // 123: grpc.Bridge.GuiReady:output_type -> grpc.GuiReadyResponse - 73, // 124: grpc.Bridge.Quit:output_type -> google.protobuf.Empty - 73, // 125: grpc.Bridge.Restart:output_type -> google.protobuf.Empty - 74, // 126: grpc.Bridge.ShowOnStartup:output_type -> google.protobuf.BoolValue - 73, // 127: grpc.Bridge.SetIsAutostartOn:output_type -> google.protobuf.Empty - 74, // 128: grpc.Bridge.IsAutostartOn:output_type -> google.protobuf.BoolValue - 73, // 129: grpc.Bridge.SetIsBetaEnabled:output_type -> google.protobuf.Empty - 74, // 130: grpc.Bridge.IsBetaEnabled:output_type -> google.protobuf.BoolValue - 73, // 131: grpc.Bridge.SetIsAllMailVisible:output_type -> google.protobuf.Empty - 74, // 132: grpc.Bridge.IsAllMailVisible:output_type -> google.protobuf.BoolValue - 73, // 133: grpc.Bridge.SetIsTelemetryDisabled:output_type -> google.protobuf.Empty - 74, // 134: grpc.Bridge.IsTelemetryDisabled:output_type -> google.protobuf.BoolValue - 72, // 135: grpc.Bridge.GoOs:output_type -> google.protobuf.StringValue - 73, // 136: grpc.Bridge.TriggerReset:output_type -> google.protobuf.Empty - 72, // 137: grpc.Bridge.Version:output_type -> google.protobuf.StringValue - 72, // 138: grpc.Bridge.LogsPath:output_type -> google.protobuf.StringValue - 72, // 139: grpc.Bridge.LicensePath:output_type -> google.protobuf.StringValue - 72, // 140: grpc.Bridge.ReleaseNotesPageLink:output_type -> google.protobuf.StringValue - 72, // 141: grpc.Bridge.DependencyLicensesLink:output_type -> google.protobuf.StringValue - 72, // 142: grpc.Bridge.LandingPageLink:output_type -> google.protobuf.StringValue - 73, // 143: grpc.Bridge.SetColorSchemeName:output_type -> google.protobuf.Empty - 72, // 144: grpc.Bridge.ColorSchemeName:output_type -> google.protobuf.StringValue - 72, // 145: grpc.Bridge.CurrentEmailClient:output_type -> google.protobuf.StringValue - 73, // 146: grpc.Bridge.ReportBug:output_type -> google.protobuf.Empty - 73, // 147: grpc.Bridge.ExportTLSCertificates:output_type -> google.protobuf.Empty - 73, // 148: grpc.Bridge.ForceLauncher:output_type -> google.protobuf.Empty - 73, // 149: grpc.Bridge.SetMainExecutable:output_type -> google.protobuf.Empty - 73, // 150: grpc.Bridge.Login:output_type -> google.protobuf.Empty - 73, // 151: grpc.Bridge.Login2FA:output_type -> google.protobuf.Empty - 73, // 152: grpc.Bridge.Login2Passwords:output_type -> google.protobuf.Empty - 73, // 153: grpc.Bridge.LoginAbort:output_type -> google.protobuf.Empty - 73, // 154: grpc.Bridge.CheckUpdate:output_type -> google.protobuf.Empty - 73, // 155: grpc.Bridge.InstallUpdate:output_type -> google.protobuf.Empty - 73, // 156: grpc.Bridge.SetIsAutomaticUpdateOn:output_type -> google.protobuf.Empty - 74, // 157: grpc.Bridge.IsAutomaticUpdateOn:output_type -> google.protobuf.BoolValue - 72, // 158: grpc.Bridge.DiskCachePath:output_type -> google.protobuf.StringValue - 73, // 159: grpc.Bridge.SetDiskCachePath:output_type -> google.protobuf.Empty - 73, // 160: grpc.Bridge.SetIsDoHEnabled:output_type -> google.protobuf.Empty - 74, // 161: grpc.Bridge.IsDoHEnabled:output_type -> google.protobuf.BoolValue - 12, // 162: grpc.Bridge.MailServerSettings:output_type -> grpc.ImapSmtpSettings - 73, // 163: grpc.Bridge.SetMailServerSettings:output_type -> google.protobuf.Empty - 72, // 164: grpc.Bridge.Hostname:output_type -> google.protobuf.StringValue - 74, // 165: grpc.Bridge.IsPortFree:output_type -> google.protobuf.BoolValue - 13, // 166: grpc.Bridge.AvailableKeychains:output_type -> grpc.AvailableKeychainsResponse - 73, // 167: grpc.Bridge.SetCurrentKeychain:output_type -> google.protobuf.Empty - 72, // 168: grpc.Bridge.CurrentKeychain:output_type -> google.protobuf.StringValue - 17, // 169: grpc.Bridge.GetUserList:output_type -> grpc.UserListResponse - 14, // 170: grpc.Bridge.GetUser:output_type -> grpc.User - 73, // 171: grpc.Bridge.SetUserSplitMode:output_type -> google.protobuf.Empty - 73, // 172: grpc.Bridge.SendBadEventUserFeedback:output_type -> google.protobuf.Empty - 73, // 173: grpc.Bridge.LogoutUser:output_type -> google.protobuf.Empty - 73, // 174: grpc.Bridge.RemoveUser:output_type -> google.protobuf.Empty - 73, // 175: grpc.Bridge.ConfigureUserAppleMail:output_type -> google.protobuf.Empty - 73, // 176: grpc.Bridge.ReportBugClicked:output_type -> google.protobuf.Empty - 73, // 177: grpc.Bridge.AutoconfigClicked:output_type -> google.protobuf.Empty - 73, // 178: grpc.Bridge.KBArticleClicked:output_type -> google.protobuf.Empty - 20, // 179: grpc.Bridge.RunEventStream:output_type -> grpc.StreamEvent - 73, // 180: grpc.Bridge.StopEventStream:output_type -> google.protobuf.Empty - 121, // [121:181] is the sub-list for method output_type - 61, // [61:121] is the sub-list for method input_type - 61, // [61:61] is the sub-list for extension type_name - 61, // [61:61] is the sub-list for extension extendee - 0, // [0:61] is the sub-list for field type_name + 30, // 20: grpc.AppEvent.certificateInstallSuccess:type_name -> grpc.CertificateInstallSuccessEvent + 31, // 21: grpc.AppEvent.certificateInstallCanceled:type_name -> grpc.CertificateInstallCanceledEvent + 32, // 22: grpc.AppEvent.certificateInstallFailed:type_name -> grpc.CertificateInstallFailedEvent + 34, // 23: grpc.LoginEvent.error:type_name -> grpc.LoginErrorEvent + 35, // 24: grpc.LoginEvent.tfaRequested:type_name -> grpc.LoginTfaRequestedEvent + 36, // 25: grpc.LoginEvent.twoPasswordRequested:type_name -> grpc.LoginTwoPasswordsRequestedEvent + 37, // 26: grpc.LoginEvent.finished:type_name -> grpc.LoginFinishedEvent + 37, // 27: grpc.LoginEvent.alreadyLoggedIn:type_name -> grpc.LoginFinishedEvent + 2, // 28: grpc.LoginErrorEvent.type:type_name -> grpc.LoginErrorType + 39, // 29: grpc.UpdateEvent.error:type_name -> grpc.UpdateErrorEvent + 40, // 30: grpc.UpdateEvent.manualReady:type_name -> grpc.UpdateManualReadyEvent + 41, // 31: grpc.UpdateEvent.manualRestartNeeded:type_name -> grpc.UpdateManualRestartNeededEvent + 42, // 32: grpc.UpdateEvent.force:type_name -> grpc.UpdateForceEvent + 43, // 33: grpc.UpdateEvent.silentRestartNeeded:type_name -> grpc.UpdateSilentRestartNeeded + 44, // 34: grpc.UpdateEvent.isLatestVersion:type_name -> grpc.UpdateIsLatestVersion + 45, // 35: grpc.UpdateEvent.checkFinished:type_name -> grpc.UpdateCheckFinished + 46, // 36: grpc.UpdateEvent.versionChanged:type_name -> grpc.UpdateVersionChanged + 3, // 37: grpc.UpdateErrorEvent.type:type_name -> grpc.UpdateErrorType + 48, // 38: grpc.DiskCacheEvent.error:type_name -> grpc.DiskCacheErrorEvent + 49, // 39: grpc.DiskCacheEvent.pathChanged:type_name -> grpc.DiskCachePathChangedEvent + 50, // 40: grpc.DiskCacheEvent.pathChangeFinished:type_name -> grpc.DiskCachePathChangeFinishedEvent + 4, // 41: grpc.DiskCacheErrorEvent.type:type_name -> grpc.DiskCacheErrorType + 52, // 42: grpc.MailServerSettingsEvent.error:type_name -> grpc.MailServerSettingsErrorEvent + 53, // 43: grpc.MailServerSettingsEvent.mailServerSettingsChanged:type_name -> grpc.MailServerSettingsChangedEvent + 54, // 44: grpc.MailServerSettingsEvent.changeMailServerSettingsFinished:type_name -> grpc.ChangeMailServerSettingsFinishedEvent + 5, // 45: grpc.MailServerSettingsErrorEvent.type:type_name -> grpc.MailServerSettingsErrorType + 12, // 46: grpc.MailServerSettingsChangedEvent.settings:type_name -> grpc.ImapSmtpSettings + 56, // 47: grpc.KeychainEvent.changeKeychainFinished:type_name -> grpc.ChangeKeychainFinishedEvent + 57, // 48: grpc.KeychainEvent.hasNoKeychain:type_name -> grpc.HasNoKeychainEvent + 58, // 49: grpc.KeychainEvent.rebuildKeychain:type_name -> grpc.RebuildKeychainEvent + 60, // 50: grpc.MailEvent.noActiveKeyForRecipientEvent:type_name -> grpc.NoActiveKeyForRecipientEvent + 61, // 51: grpc.MailEvent.addressChanged:type_name -> grpc.AddressChangedEvent + 62, // 52: grpc.MailEvent.addressChangedLogout:type_name -> grpc.AddressChangedLogoutEvent + 63, // 53: grpc.MailEvent.apiCertIssue:type_name -> grpc.ApiCertIssueEvent + 65, // 54: grpc.UserEvent.toggleSplitModeFinished:type_name -> grpc.ToggleSplitModeFinishedEvent + 66, // 55: grpc.UserEvent.userDisconnected:type_name -> grpc.UserDisconnectedEvent + 67, // 56: grpc.UserEvent.userChanged:type_name -> grpc.UserChangedEvent + 68, // 57: grpc.UserEvent.userBadEvent:type_name -> grpc.UserBadEvent + 69, // 58: grpc.UserEvent.usedBytesChangedEvent:type_name -> grpc.UsedBytesChangedEvent + 70, // 59: grpc.UserEvent.imapLoginFailedEvent:type_name -> grpc.ImapLoginFailedEvent + 71, // 60: grpc.UserEvent.syncStartedEvent:type_name -> grpc.SyncStartedEvent + 72, // 61: grpc.UserEvent.syncFinishedEvent:type_name -> grpc.SyncFinishedEvent + 73, // 62: grpc.UserEvent.syncProgressEvent:type_name -> grpc.SyncProgressEvent + 6, // 63: grpc.GenericErrorEvent.code:type_name -> grpc.ErrorCode + 75, // 64: grpc.Bridge.CheckTokens:input_type -> google.protobuf.StringValue + 7, // 65: grpc.Bridge.AddLogEntry:input_type -> grpc.AddLogEntryRequest + 76, // 66: grpc.Bridge.GuiReady:input_type -> google.protobuf.Empty + 76, // 67: grpc.Bridge.Quit:input_type -> google.protobuf.Empty + 76, // 68: grpc.Bridge.Restart:input_type -> google.protobuf.Empty + 76, // 69: grpc.Bridge.ShowOnStartup:input_type -> google.protobuf.Empty + 77, // 70: grpc.Bridge.SetIsAutostartOn:input_type -> google.protobuf.BoolValue + 76, // 71: grpc.Bridge.IsAutostartOn:input_type -> google.protobuf.Empty + 77, // 72: grpc.Bridge.SetIsBetaEnabled:input_type -> google.protobuf.BoolValue + 76, // 73: grpc.Bridge.IsBetaEnabled:input_type -> google.protobuf.Empty + 77, // 74: grpc.Bridge.SetIsAllMailVisible:input_type -> google.protobuf.BoolValue + 76, // 75: grpc.Bridge.IsAllMailVisible:input_type -> google.protobuf.Empty + 77, // 76: grpc.Bridge.SetIsTelemetryDisabled:input_type -> google.protobuf.BoolValue + 76, // 77: grpc.Bridge.IsTelemetryDisabled:input_type -> google.protobuf.Empty + 76, // 78: grpc.Bridge.GoOs:input_type -> google.protobuf.Empty + 76, // 79: grpc.Bridge.TriggerReset:input_type -> google.protobuf.Empty + 76, // 80: grpc.Bridge.Version:input_type -> google.protobuf.Empty + 76, // 81: grpc.Bridge.LogsPath:input_type -> google.protobuf.Empty + 76, // 82: grpc.Bridge.LicensePath:input_type -> google.protobuf.Empty + 76, // 83: grpc.Bridge.ReleaseNotesPageLink:input_type -> google.protobuf.Empty + 76, // 84: grpc.Bridge.DependencyLicensesLink:input_type -> google.protobuf.Empty + 76, // 85: grpc.Bridge.LandingPageLink:input_type -> google.protobuf.Empty + 75, // 86: grpc.Bridge.SetColorSchemeName:input_type -> google.protobuf.StringValue + 76, // 87: grpc.Bridge.ColorSchemeName:input_type -> google.protobuf.Empty + 76, // 88: grpc.Bridge.CurrentEmailClient:input_type -> google.protobuf.Empty + 9, // 89: grpc.Bridge.ReportBug:input_type -> grpc.ReportBugRequest + 75, // 90: grpc.Bridge.ForceLauncher:input_type -> google.protobuf.StringValue + 75, // 91: grpc.Bridge.SetMainExecutable:input_type -> google.protobuf.StringValue + 10, // 92: grpc.Bridge.Login:input_type -> grpc.LoginRequest + 10, // 93: grpc.Bridge.Login2FA:input_type -> grpc.LoginRequest + 10, // 94: grpc.Bridge.Login2Passwords:input_type -> grpc.LoginRequest + 11, // 95: grpc.Bridge.LoginAbort:input_type -> grpc.LoginAbortRequest + 76, // 96: grpc.Bridge.CheckUpdate:input_type -> google.protobuf.Empty + 76, // 97: grpc.Bridge.InstallUpdate:input_type -> google.protobuf.Empty + 77, // 98: grpc.Bridge.SetIsAutomaticUpdateOn:input_type -> google.protobuf.BoolValue + 76, // 99: grpc.Bridge.IsAutomaticUpdateOn:input_type -> google.protobuf.Empty + 76, // 100: grpc.Bridge.DiskCachePath:input_type -> google.protobuf.Empty + 75, // 101: grpc.Bridge.SetDiskCachePath:input_type -> google.protobuf.StringValue + 77, // 102: grpc.Bridge.SetIsDoHEnabled:input_type -> google.protobuf.BoolValue + 76, // 103: grpc.Bridge.IsDoHEnabled:input_type -> google.protobuf.Empty + 76, // 104: grpc.Bridge.MailServerSettings:input_type -> google.protobuf.Empty + 12, // 105: grpc.Bridge.SetMailServerSettings:input_type -> grpc.ImapSmtpSettings + 76, // 106: grpc.Bridge.Hostname:input_type -> google.protobuf.Empty + 78, // 107: grpc.Bridge.IsPortFree:input_type -> google.protobuf.Int32Value + 76, // 108: grpc.Bridge.AvailableKeychains:input_type -> google.protobuf.Empty + 75, // 109: grpc.Bridge.SetCurrentKeychain:input_type -> google.protobuf.StringValue + 76, // 110: grpc.Bridge.CurrentKeychain:input_type -> google.protobuf.Empty + 76, // 111: grpc.Bridge.GetUserList:input_type -> google.protobuf.Empty + 75, // 112: grpc.Bridge.GetUser:input_type -> google.protobuf.StringValue + 15, // 113: grpc.Bridge.SetUserSplitMode:input_type -> grpc.UserSplitModeRequest + 16, // 114: grpc.Bridge.SendBadEventUserFeedback:input_type -> grpc.UserBadEventFeedbackRequest + 75, // 115: grpc.Bridge.LogoutUser:input_type -> google.protobuf.StringValue + 75, // 116: grpc.Bridge.RemoveUser:input_type -> google.protobuf.StringValue + 18, // 117: grpc.Bridge.ConfigureUserAppleMail:input_type -> grpc.ConfigureAppleMailRequest + 76, // 118: grpc.Bridge.ReportBugClicked:input_type -> google.protobuf.Empty + 75, // 119: grpc.Bridge.AutoconfigClicked:input_type -> google.protobuf.StringValue + 75, // 120: grpc.Bridge.KBArticleClicked:input_type -> google.protobuf.StringValue + 76, // 121: grpc.Bridge.IsTLSCertificateInstalled:input_type -> google.protobuf.Empty + 76, // 122: grpc.Bridge.InstallTLSCertificate:input_type -> google.protobuf.Empty + 75, // 123: grpc.Bridge.ExportTLSCertificates:input_type -> google.protobuf.StringValue + 19, // 124: grpc.Bridge.RunEventStream:input_type -> grpc.EventStreamRequest + 76, // 125: grpc.Bridge.StopEventStream:input_type -> google.protobuf.Empty + 75, // 126: grpc.Bridge.CheckTokens:output_type -> google.protobuf.StringValue + 76, // 127: grpc.Bridge.AddLogEntry:output_type -> google.protobuf.Empty + 8, // 128: grpc.Bridge.GuiReady:output_type -> grpc.GuiReadyResponse + 76, // 129: grpc.Bridge.Quit:output_type -> google.protobuf.Empty + 76, // 130: grpc.Bridge.Restart:output_type -> google.protobuf.Empty + 77, // 131: grpc.Bridge.ShowOnStartup:output_type -> google.protobuf.BoolValue + 76, // 132: grpc.Bridge.SetIsAutostartOn:output_type -> google.protobuf.Empty + 77, // 133: grpc.Bridge.IsAutostartOn:output_type -> google.protobuf.BoolValue + 76, // 134: grpc.Bridge.SetIsBetaEnabled:output_type -> google.protobuf.Empty + 77, // 135: grpc.Bridge.IsBetaEnabled:output_type -> google.protobuf.BoolValue + 76, // 136: grpc.Bridge.SetIsAllMailVisible:output_type -> google.protobuf.Empty + 77, // 137: grpc.Bridge.IsAllMailVisible:output_type -> google.protobuf.BoolValue + 76, // 138: grpc.Bridge.SetIsTelemetryDisabled:output_type -> google.protobuf.Empty + 77, // 139: grpc.Bridge.IsTelemetryDisabled:output_type -> google.protobuf.BoolValue + 75, // 140: grpc.Bridge.GoOs:output_type -> google.protobuf.StringValue + 76, // 141: grpc.Bridge.TriggerReset:output_type -> google.protobuf.Empty + 75, // 142: grpc.Bridge.Version:output_type -> google.protobuf.StringValue + 75, // 143: grpc.Bridge.LogsPath:output_type -> google.protobuf.StringValue + 75, // 144: grpc.Bridge.LicensePath:output_type -> google.protobuf.StringValue + 75, // 145: grpc.Bridge.ReleaseNotesPageLink:output_type -> google.protobuf.StringValue + 75, // 146: grpc.Bridge.DependencyLicensesLink:output_type -> google.protobuf.StringValue + 75, // 147: grpc.Bridge.LandingPageLink:output_type -> google.protobuf.StringValue + 76, // 148: grpc.Bridge.SetColorSchemeName:output_type -> google.protobuf.Empty + 75, // 149: grpc.Bridge.ColorSchemeName:output_type -> google.protobuf.StringValue + 75, // 150: grpc.Bridge.CurrentEmailClient:output_type -> google.protobuf.StringValue + 76, // 151: grpc.Bridge.ReportBug:output_type -> google.protobuf.Empty + 76, // 152: grpc.Bridge.ForceLauncher:output_type -> google.protobuf.Empty + 76, // 153: grpc.Bridge.SetMainExecutable:output_type -> google.protobuf.Empty + 76, // 154: grpc.Bridge.Login:output_type -> google.protobuf.Empty + 76, // 155: grpc.Bridge.Login2FA:output_type -> google.protobuf.Empty + 76, // 156: grpc.Bridge.Login2Passwords:output_type -> google.protobuf.Empty + 76, // 157: grpc.Bridge.LoginAbort:output_type -> google.protobuf.Empty + 76, // 158: grpc.Bridge.CheckUpdate:output_type -> google.protobuf.Empty + 76, // 159: grpc.Bridge.InstallUpdate:output_type -> google.protobuf.Empty + 76, // 160: grpc.Bridge.SetIsAutomaticUpdateOn:output_type -> google.protobuf.Empty + 77, // 161: grpc.Bridge.IsAutomaticUpdateOn:output_type -> google.protobuf.BoolValue + 75, // 162: grpc.Bridge.DiskCachePath:output_type -> google.protobuf.StringValue + 76, // 163: grpc.Bridge.SetDiskCachePath:output_type -> google.protobuf.Empty + 76, // 164: grpc.Bridge.SetIsDoHEnabled:output_type -> google.protobuf.Empty + 77, // 165: grpc.Bridge.IsDoHEnabled:output_type -> google.protobuf.BoolValue + 12, // 166: grpc.Bridge.MailServerSettings:output_type -> grpc.ImapSmtpSettings + 76, // 167: grpc.Bridge.SetMailServerSettings:output_type -> google.protobuf.Empty + 75, // 168: grpc.Bridge.Hostname:output_type -> google.protobuf.StringValue + 77, // 169: grpc.Bridge.IsPortFree:output_type -> google.protobuf.BoolValue + 13, // 170: grpc.Bridge.AvailableKeychains:output_type -> grpc.AvailableKeychainsResponse + 76, // 171: grpc.Bridge.SetCurrentKeychain:output_type -> google.protobuf.Empty + 75, // 172: grpc.Bridge.CurrentKeychain:output_type -> google.protobuf.StringValue + 17, // 173: grpc.Bridge.GetUserList:output_type -> grpc.UserListResponse + 14, // 174: grpc.Bridge.GetUser:output_type -> grpc.User + 76, // 175: grpc.Bridge.SetUserSplitMode:output_type -> google.protobuf.Empty + 76, // 176: grpc.Bridge.SendBadEventUserFeedback:output_type -> google.protobuf.Empty + 76, // 177: grpc.Bridge.LogoutUser:output_type -> google.protobuf.Empty + 76, // 178: grpc.Bridge.RemoveUser:output_type -> google.protobuf.Empty + 76, // 179: grpc.Bridge.ConfigureUserAppleMail:output_type -> google.protobuf.Empty + 76, // 180: grpc.Bridge.ReportBugClicked:output_type -> google.protobuf.Empty + 76, // 181: grpc.Bridge.AutoconfigClicked:output_type -> google.protobuf.Empty + 76, // 182: grpc.Bridge.KBArticleClicked:output_type -> google.protobuf.Empty + 77, // 183: grpc.Bridge.IsTLSCertificateInstalled:output_type -> google.protobuf.BoolValue + 76, // 184: grpc.Bridge.InstallTLSCertificate:output_type -> google.protobuf.Empty + 76, // 185: grpc.Bridge.ExportTLSCertificates:output_type -> google.protobuf.Empty + 20, // 186: grpc.Bridge.RunEventStream:output_type -> grpc.StreamEvent + 76, // 187: grpc.Bridge.StopEventStream:output_type -> google.protobuf.Empty + 126, // [126:188] is the sub-list for method output_type + 64, // [64:126] is the sub-list for method input_type + 64, // [64:64] is the sub-list for extension type_name + 64, // [64:64] is the sub-list for extension extendee + 0, // [0:64] is the sub-list for field type_name } func init() { file_bridge_proto_init() } @@ -5705,7 +5916,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginEvent); i { + switch v := v.(*CertificateInstallSuccessEvent); i { case 0: return &v.state case 1: @@ -5717,7 +5928,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginErrorEvent); i { + switch v := v.(*CertificateInstallCanceledEvent); i { case 0: return &v.state case 1: @@ -5729,7 +5940,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginTfaRequestedEvent); i { + switch v := v.(*CertificateInstallFailedEvent); i { case 0: return &v.state case 1: @@ -5741,7 +5952,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginTwoPasswordsRequestedEvent); i { + switch v := v.(*LoginEvent); i { case 0: return &v.state case 1: @@ -5753,7 +5964,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoginFinishedEvent); i { + switch v := v.(*LoginErrorEvent); i { case 0: return &v.state case 1: @@ -5765,7 +5976,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateEvent); i { + switch v := v.(*LoginTfaRequestedEvent); i { case 0: return &v.state case 1: @@ -5777,7 +5988,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateErrorEvent); i { + switch v := v.(*LoginTwoPasswordsRequestedEvent); i { case 0: return &v.state case 1: @@ -5789,7 +6000,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateManualReadyEvent); i { + switch v := v.(*LoginFinishedEvent); i { case 0: return &v.state case 1: @@ -5801,7 +6012,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateManualRestartNeededEvent); i { + switch v := v.(*UpdateEvent); i { case 0: return &v.state case 1: @@ -5813,7 +6024,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateForceEvent); i { + switch v := v.(*UpdateErrorEvent); i { case 0: return &v.state case 1: @@ -5825,7 +6036,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateSilentRestartNeeded); i { + switch v := v.(*UpdateManualReadyEvent); i { case 0: return &v.state case 1: @@ -5837,7 +6048,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateIsLatestVersion); i { + switch v := v.(*UpdateManualRestartNeededEvent); i { case 0: return &v.state case 1: @@ -5849,7 +6060,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateCheckFinished); i { + switch v := v.(*UpdateForceEvent); i { case 0: return &v.state case 1: @@ -5861,7 +6072,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateVersionChanged); i { + switch v := v.(*UpdateSilentRestartNeeded); i { case 0: return &v.state case 1: @@ -5873,7 +6084,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiskCacheEvent); i { + switch v := v.(*UpdateIsLatestVersion); i { case 0: return &v.state case 1: @@ -5885,7 +6096,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiskCacheErrorEvent); i { + switch v := v.(*UpdateCheckFinished); i { case 0: return &v.state case 1: @@ -5897,7 +6108,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiskCachePathChangedEvent); i { + switch v := v.(*UpdateVersionChanged); i { case 0: return &v.state case 1: @@ -5909,7 +6120,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiskCachePathChangeFinishedEvent); i { + switch v := v.(*DiskCacheEvent); i { case 0: return &v.state case 1: @@ -5921,7 +6132,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MailServerSettingsEvent); i { + switch v := v.(*DiskCacheErrorEvent); i { case 0: return &v.state case 1: @@ -5933,7 +6144,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MailServerSettingsErrorEvent); i { + switch v := v.(*DiskCachePathChangedEvent); i { case 0: return &v.state case 1: @@ -5945,7 +6156,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MailServerSettingsChangedEvent); i { + switch v := v.(*DiskCachePathChangeFinishedEvent); i { case 0: return &v.state case 1: @@ -5957,7 +6168,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChangeMailServerSettingsFinishedEvent); i { + switch v := v.(*MailServerSettingsEvent); i { case 0: return &v.state case 1: @@ -5969,7 +6180,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KeychainEvent); i { + switch v := v.(*MailServerSettingsErrorEvent); i { case 0: return &v.state case 1: @@ -5981,7 +6192,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChangeKeychainFinishedEvent); i { + switch v := v.(*MailServerSettingsChangedEvent); i { case 0: return &v.state case 1: @@ -5993,7 +6204,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HasNoKeychainEvent); i { + switch v := v.(*ChangeMailServerSettingsFinishedEvent); i { case 0: return &v.state case 1: @@ -6005,7 +6216,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RebuildKeychainEvent); i { + switch v := v.(*KeychainEvent); i { case 0: return &v.state case 1: @@ -6017,7 +6228,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MailEvent); i { + switch v := v.(*ChangeKeychainFinishedEvent); i { case 0: return &v.state case 1: @@ -6029,7 +6240,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NoActiveKeyForRecipientEvent); i { + switch v := v.(*HasNoKeychainEvent); i { case 0: return &v.state case 1: @@ -6041,7 +6252,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddressChangedEvent); i { + switch v := v.(*RebuildKeychainEvent); i { case 0: return &v.state case 1: @@ -6053,7 +6264,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddressChangedLogoutEvent); i { + switch v := v.(*MailEvent); i { case 0: return &v.state case 1: @@ -6065,7 +6276,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ApiCertIssueEvent); i { + switch v := v.(*NoActiveKeyForRecipientEvent); i { case 0: return &v.state case 1: @@ -6077,7 +6288,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UserEvent); i { + switch v := v.(*AddressChangedEvent); i { case 0: return &v.state case 1: @@ -6089,7 +6300,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ToggleSplitModeFinishedEvent); i { + switch v := v.(*AddressChangedLogoutEvent); i { case 0: return &v.state case 1: @@ -6101,7 +6312,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UserDisconnectedEvent); i { + switch v := v.(*ApiCertIssueEvent); i { case 0: return &v.state case 1: @@ -6113,7 +6324,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UserChangedEvent); i { + switch v := v.(*UserEvent); i { case 0: return &v.state case 1: @@ -6125,7 +6336,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UserBadEvent); i { + switch v := v.(*ToggleSplitModeFinishedEvent); i { case 0: return &v.state case 1: @@ -6137,7 +6348,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UsedBytesChangedEvent); i { + switch v := v.(*UserDisconnectedEvent); i { case 0: return &v.state case 1: @@ -6149,7 +6360,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ImapLoginFailedEvent); i { + switch v := v.(*UserChangedEvent); i { case 0: return &v.state case 1: @@ -6161,7 +6372,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SyncStartedEvent); i { + switch v := v.(*UserBadEvent); i { case 0: return &v.state case 1: @@ -6173,7 +6384,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SyncFinishedEvent); i { + switch v := v.(*UsedBytesChangedEvent); i { case 0: return &v.state case 1: @@ -6185,7 +6396,7 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SyncProgressEvent); i { + switch v := v.(*ImapLoginFailedEvent); i { case 0: return &v.state case 1: @@ -6197,6 +6408,42 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncStartedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bridge_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncFinishedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bridge_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncProgressEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bridge_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GenericErrorEvent); i { case 0: return &v.state @@ -6229,15 +6476,18 @@ func file_bridge_proto_init() { (*AppEvent_ReportBugError)(nil), (*AppEvent_ShowMainWindow)(nil), (*AppEvent_ReportBugFallback)(nil), + (*AppEvent_CertificateInstallSuccess)(nil), + (*AppEvent_CertificateInstallCanceled)(nil), + (*AppEvent_CertificateInstallFailed)(nil), } - file_bridge_proto_msgTypes[23].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[26].OneofWrappers = []interface{}{ (*LoginEvent_Error)(nil), (*LoginEvent_TfaRequested)(nil), (*LoginEvent_TwoPasswordRequested)(nil), (*LoginEvent_Finished)(nil), (*LoginEvent_AlreadyLoggedIn)(nil), } - file_bridge_proto_msgTypes[28].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[31].OneofWrappers = []interface{}{ (*UpdateEvent_Error)(nil), (*UpdateEvent_ManualReady)(nil), (*UpdateEvent_ManualRestartNeeded)(nil), @@ -6247,28 +6497,28 @@ func file_bridge_proto_init() { (*UpdateEvent_CheckFinished)(nil), (*UpdateEvent_VersionChanged)(nil), } - file_bridge_proto_msgTypes[37].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[40].OneofWrappers = []interface{}{ (*DiskCacheEvent_Error)(nil), (*DiskCacheEvent_PathChanged)(nil), (*DiskCacheEvent_PathChangeFinished)(nil), } - file_bridge_proto_msgTypes[41].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[44].OneofWrappers = []interface{}{ (*MailServerSettingsEvent_Error)(nil), (*MailServerSettingsEvent_MailServerSettingsChanged)(nil), (*MailServerSettingsEvent_ChangeMailServerSettingsFinished)(nil), } - file_bridge_proto_msgTypes[45].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[48].OneofWrappers = []interface{}{ (*KeychainEvent_ChangeKeychainFinished)(nil), (*KeychainEvent_HasNoKeychain)(nil), (*KeychainEvent_RebuildKeychain)(nil), } - file_bridge_proto_msgTypes[49].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[52].OneofWrappers = []interface{}{ (*MailEvent_NoActiveKeyForRecipientEvent)(nil), (*MailEvent_AddressChanged)(nil), (*MailEvent_AddressChangedLogout)(nil), (*MailEvent_ApiCertIssue)(nil), } - file_bridge_proto_msgTypes[54].OneofWrappers = []interface{}{ + file_bridge_proto_msgTypes[57].OneofWrappers = []interface{}{ (*UserEvent_ToggleSplitModeFinished)(nil), (*UserEvent_UserDisconnected)(nil), (*UserEvent_UserChanged)(nil), @@ -6285,7 +6535,7 @@ func file_bridge_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_bridge_proto_rawDesc, NumEnums: 7, - NumMessages: 65, + NumMessages: 68, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/frontend/grpc/bridge.proto b/internal/frontend/grpc/bridge.proto index 6781865e..4199de5b 100644 --- a/internal/frontend/grpc/bridge.proto +++ b/internal/frontend/grpc/bridge.proto @@ -56,7 +56,6 @@ service Bridge { rpc ColorSchemeName(google.protobuf.Empty) returns (google.protobuf.StringValue); // TODO Color scheme should probably entirely be managed by the client. rpc CurrentEmailClient(google.protobuf.Empty) returns (google.protobuf.StringValue); rpc ReportBug(ReportBugRequest) returns (google.protobuf.Empty); - rpc ExportTLSCertificates(google.protobuf.StringValue) returns (google.protobuf.Empty); rpc ForceLauncher(google.protobuf.StringValue) returns (google.protobuf.Empty); rpc SetMainExecutable(google.protobuf.StringValue) returns (google.protobuf.Empty); @@ -103,6 +102,11 @@ service Bridge { rpc AutoconfigClicked(google.protobuf.StringValue) returns (google.protobuf.Empty); rpc KBArticleClicked(google.protobuf.StringValue) returns (google.protobuf.Empty); + // TLS certificate related calls + rpc IsTLSCertificateInstalled(google.protobuf.Empty) returns (google.protobuf.BoolValue); + rpc InstallTLSCertificate(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc ExportTLSCertificates(google.protobuf.StringValue) returns (google.protobuf.Empty); + // Server -> Client event stream rpc RunEventStream(EventStreamRequest) returns (stream StreamEvent); // Keep streaming until StopEventStream is called. rpc StopEventStream(google.protobuf.Empty) returns (google.protobuf.Empty); @@ -262,6 +266,9 @@ message AppEvent { ReportBugErrorEvent reportBugError = 6; ShowMainWindowEvent showMainWindow = 7; ReportBugFallbackEvent reportBugFallback = 8; + CertificateInstallSuccessEvent certificateInstallSuccess = 9; + CertificateInstallCanceledEvent certificateInstallCanceled = 10; + CertificateInstallFailedEvent certificateInstallFailed = 11; } } @@ -276,6 +283,9 @@ message ReportBugSuccessEvent {} message ReportBugErrorEvent {} message ShowMainWindowEvent {} message ReportBugFallbackEvent {} +message CertificateInstallSuccessEvent {} +message CertificateInstallCanceledEvent {} +message CertificateInstallFailedEvent {} //********************************************************** // Login related events @@ -309,7 +319,9 @@ message LoginTfaRequestedEvent { string username = 1; } -message LoginTwoPasswordsRequestedEvent {} +message LoginTwoPasswordsRequestedEvent { + string username = 1; +} message LoginFinishedEvent { string userID = 1; diff --git a/internal/frontend/grpc/bridge_grpc.pb.go b/internal/frontend/grpc/bridge_grpc.pb.go index 4767c41a..73ef1590 100644 --- a/internal/frontend/grpc/bridge_grpc.pb.go +++ b/internal/frontend/grpc/bridge_grpc.pb.go @@ -51,7 +51,6 @@ type BridgeClient interface { ColorSchemeName(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) CurrentEmailClient(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) ReportBug(ctx context.Context, in *ReportBugRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) ForceLauncher(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) SetMainExecutable(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) // login @@ -90,6 +89,10 @@ type BridgeClient interface { ReportBugClicked(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) AutoconfigClicked(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) KBArticleClicked(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) + // TLS certificate related calls + IsTLSCertificateInstalled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) + InstallTLSCertificate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) // Server -> Client event stream RunEventStream(ctx context.Context, in *EventStreamRequest, opts ...grpc.CallOption) (Bridge_RunEventStreamClient, error) StopEventStream(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) @@ -337,15 +340,6 @@ func (c *bridgeClient) ReportBug(ctx context.Context, in *ReportBugRequest, opts return out, nil } -func (c *bridgeClient) ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) { - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, "/grpc.Bridge/ExportTLSCertificates", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *bridgeClient) ForceLauncher(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) { out := new(emptypb.Empty) err := c.cc.Invoke(ctx, "/grpc.Bridge/ForceLauncher", in, out, opts...) @@ -625,6 +619,33 @@ func (c *bridgeClient) KBArticleClicked(ctx context.Context, in *wrapperspb.Stri return out, nil } +func (c *bridgeClient) IsTLSCertificateInstalled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) { + out := new(wrapperspb.BoolValue) + err := c.cc.Invoke(ctx, "/grpc.Bridge/IsTLSCertificateInstalled", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *bridgeClient) InstallTLSCertificate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/grpc.Bridge/InstallTLSCertificate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *bridgeClient) ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/grpc.Bridge/ExportTLSCertificates", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *bridgeClient) RunEventStream(ctx context.Context, in *EventStreamRequest, opts ...grpc.CallOption) (Bridge_RunEventStreamClient, error) { stream, err := c.cc.NewStream(ctx, &Bridge_ServiceDesc.Streams[0], "/grpc.Bridge/RunEventStream", opts...) if err != nil { @@ -697,7 +718,6 @@ type BridgeServer interface { ColorSchemeName(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) CurrentEmailClient(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) ReportBug(context.Context, *ReportBugRequest) (*emptypb.Empty, error) - ExportTLSCertificates(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) ForceLauncher(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) SetMainExecutable(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) // login @@ -736,6 +756,10 @@ type BridgeServer interface { ReportBugClicked(context.Context, *emptypb.Empty) (*emptypb.Empty, error) AutoconfigClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) KBArticleClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) + // TLS certificate related calls + IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) + InstallTLSCertificate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + ExportTLSCertificates(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) // Server -> Client event stream RunEventStream(*EventStreamRequest, Bridge_RunEventStreamServer) error StopEventStream(context.Context, *emptypb.Empty) (*emptypb.Empty, error) @@ -824,9 +848,6 @@ func (UnimplementedBridgeServer) CurrentEmailClient(context.Context, *emptypb.Em func (UnimplementedBridgeServer) ReportBug(context.Context, *ReportBugRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method ReportBug not implemented") } -func (UnimplementedBridgeServer) ExportTLSCertificates(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method ExportTLSCertificates not implemented") -} func (UnimplementedBridgeServer) ForceLauncher(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method ForceLauncher not implemented") } @@ -920,6 +941,15 @@ func (UnimplementedBridgeServer) AutoconfigClicked(context.Context, *wrapperspb. func (UnimplementedBridgeServer) KBArticleClicked(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method KBArticleClicked not implemented") } +func (UnimplementedBridgeServer) IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) { + return nil, status.Errorf(codes.Unimplemented, "method IsTLSCertificateInstalled not implemented") +} +func (UnimplementedBridgeServer) InstallTLSCertificate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method InstallTLSCertificate not implemented") +} +func (UnimplementedBridgeServer) ExportTLSCertificates(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExportTLSCertificates not implemented") +} func (UnimplementedBridgeServer) RunEventStream(*EventStreamRequest, Bridge_RunEventStreamServer) error { return status.Errorf(codes.Unimplemented, "method RunEventStream not implemented") } @@ -1407,24 +1437,6 @@ func _Bridge_ReportBug_Handler(srv interface{}, ctx context.Context, dec func(in return interceptor(ctx, in, info, handler) } -func _Bridge_ExportTLSCertificates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(wrapperspb.StringValue) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(BridgeServer).ExportTLSCertificates(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/grpc.Bridge/ExportTLSCertificates", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BridgeServer).ExportTLSCertificates(ctx, req.(*wrapperspb.StringValue)) - } - return interceptor(ctx, in, info, handler) -} - func _Bridge_ForceLauncher_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(wrapperspb.StringValue) if err := dec(in); err != nil { @@ -1983,6 +1995,60 @@ func _Bridge_KBArticleClicked_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _Bridge_IsTLSCertificateInstalled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BridgeServer).IsTLSCertificateInstalled(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.Bridge/IsTLSCertificateInstalled", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BridgeServer).IsTLSCertificateInstalled(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Bridge_InstallTLSCertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BridgeServer).InstallTLSCertificate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.Bridge/InstallTLSCertificate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BridgeServer).InstallTLSCertificate(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Bridge_ExportTLSCertificates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(wrapperspb.StringValue) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BridgeServer).ExportTLSCertificates(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.Bridge/ExportTLSCertificates", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BridgeServer).ExportTLSCertificates(ctx, req.(*wrapperspb.StringValue)) + } + return interceptor(ctx, in, info, handler) +} + func _Bridge_RunEventStream_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(EventStreamRequest) if err := stream.RecvMsg(m); err != nil { @@ -2133,10 +2199,6 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{ MethodName: "ReportBug", Handler: _Bridge_ReportBug_Handler, }, - { - MethodName: "ExportTLSCertificates", - Handler: _Bridge_ExportTLSCertificates_Handler, - }, { MethodName: "ForceLauncher", Handler: _Bridge_ForceLauncher_Handler, @@ -2261,6 +2323,18 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{ MethodName: "KBArticleClicked", Handler: _Bridge_KBArticleClicked_Handler, }, + { + MethodName: "IsTLSCertificateInstalled", + Handler: _Bridge_IsTLSCertificateInstalled_Handler, + }, + { + MethodName: "InstallTLSCertificate", + Handler: _Bridge_InstallTLSCertificate_Handler, + }, + { + MethodName: "ExportTLSCertificates", + Handler: _Bridge_ExportTLSCertificates_Handler, + }, { MethodName: "StopEventStream", Handler: _Bridge_StopEventStream_Handler, diff --git a/internal/frontend/grpc/event_factory.go b/internal/frontend/grpc/event_factory.go index 66e79ebe..4bdb7899 100644 --- a/internal/frontend/grpc/event_factory.go +++ b/internal/frontend/grpc/event_factory.go @@ -45,6 +45,18 @@ func NewReportBugFallbackEvent() *StreamEvent { return appEvent(&AppEvent{Event: &AppEvent_ReportBugFallback{ReportBugFallback: &ReportBugFallbackEvent{}}}) } +func NewCertInstallSuccessEvent() *StreamEvent { + return appEvent(&AppEvent{Event: &AppEvent_CertificateInstallSuccess{CertificateInstallSuccess: &CertificateInstallSuccessEvent{}}}) +} + +func NewCertInstallCanceledEvent() *StreamEvent { + return appEvent(&AppEvent{Event: &AppEvent_CertificateInstallCanceled{CertificateInstallCanceled: &CertificateInstallCanceledEvent{}}}) +} + +func NewCertInstallFailedEvent() *StreamEvent { + return appEvent(&AppEvent{Event: &AppEvent_CertificateInstallFailed{CertificateInstallFailed: &CertificateInstallFailedEvent{}}}) +} + func NewShowMainWindowEvent() *StreamEvent { return appEvent(&AppEvent{Event: &AppEvent_ShowMainWindow{ShowMainWindow: &ShowMainWindowEvent{}}}) } @@ -57,8 +69,8 @@ func NewLoginTfaRequestedEvent(username string) *StreamEvent { return loginEvent(&LoginEvent{Event: &LoginEvent_TfaRequested{TfaRequested: &LoginTfaRequestedEvent{Username: username}}}) } -func NewLoginTwoPasswordsRequestedEvent() *StreamEvent { - return loginEvent(&LoginEvent{Event: &LoginEvent_TwoPasswordRequested{}}) +func NewLoginTwoPasswordsRequestedEvent(username string) *StreamEvent { + return loginEvent(&LoginEvent{Event: &LoginEvent_TwoPasswordRequested{TwoPasswordRequested: &LoginTwoPasswordsRequestedEvent{Username: username}}}) } func NewLoginFinishedEvent(userID string, wasSignedOut bool) *StreamEvent { diff --git a/internal/frontend/grpc/service_cert.go b/internal/frontend/grpc/service_cert.go new file mode 100644 index 00000000..399f3dc7 --- /dev/null +++ b/internal/frontend/grpc/service_cert.go @@ -0,0 +1,79 @@ +// Copyright (c) 2023 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + +package grpc + +import ( + "context" + "errors" + "os" + "path/filepath" + + "github.com/ProtonMail/gluon/async" + "github.com/ProtonMail/proton-bridge/v3/internal/certs" + "google.golang.org/protobuf/types/known/emptypb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +func (s *Service) IsTLSCertificateInstalled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) { + s.log.Info("IsTLSCertificateInstalled") + + cert, _ := s.bridge.GetBridgeTLSCert() + + return &wrapperspb.BoolValue{Value: certs.NewInstaller().IsCertInstalled(cert)}, nil +} + +func (s *Service) InstallTLSCertificate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + s.log.Info("InstallTLSCertificate") + + go func() { + defer async.HandlePanic(s.panicHandler) + cert, _ := s.bridge.GetBridgeTLSCert() + + err := certs.NewInstaller().InstallCert(cert) + switch { + case err == nil: + _ = s.SendEvent(NewCertInstallSuccessEvent()) + case errors.Is(err, certs.ErrUserCanceledCertificateInstall): + _ = s.SendEvent(NewCertInstallCanceledEvent()) + default: + _ = s.SendEvent(NewCertInstallFailedEvent()) + } + }() + + return &emptypb.Empty{}, nil +} + +func (s *Service) ExportTLSCertificates(_ context.Context, folderPath *wrapperspb.StringValue) (*emptypb.Empty, error) { + s.log.WithField("folderPath", folderPath).Info("ExportTLSCertificates") + + go func() { + defer async.HandlePanic(s.panicHandler) + + cert, key := s.bridge.GetBridgeTLSCert() + + if err := os.WriteFile(filepath.Join(folderPath.Value, "cert.pem"), cert, 0o600); err != nil { + _ = s.SendEvent(NewGenericErrorEvent(ErrorCode_TLS_CERT_EXPORT_ERROR)) + } + + if err := os.WriteFile(filepath.Join(folderPath.Value, "key.pem"), key, 0o600); err != nil { + _ = s.SendEvent(NewGenericErrorEvent(ErrorCode_TLS_KEY_EXPORT_ERROR)) + } + }() + + return &emptypb.Empty{}, nil +} diff --git a/internal/frontend/grpc/service_methods.go b/internal/frontend/grpc/service_methods.go index ae1d76f7..009856c6 100644 --- a/internal/frontend/grpc/service_methods.go +++ b/internal/frontend/grpc/service_methods.go @@ -21,8 +21,6 @@ import ( "context" "encoding/base64" "errors" - "os" - "path/filepath" "runtime" "github.com/Masterminds/semver/v3" @@ -364,26 +362,6 @@ func (s *Service) ReportBug(_ context.Context, report *ReportBugRequest) (*empty return &emptypb.Empty{}, nil } -func (s *Service) ExportTLSCertificates(_ context.Context, folderPath *wrapperspb.StringValue) (*emptypb.Empty, error) { - s.log.WithField("folderPath", folderPath).Info("ExportTLSCertificates") - - go func() { - defer async.HandlePanic(s.panicHandler) - - cert, key := s.bridge.GetBridgeTLSCert() - - if err := os.WriteFile(filepath.Join(folderPath.Value, "cert.pem"), cert, 0o600); err != nil { - _ = s.SendEvent(NewGenericErrorEvent(ErrorCode_TLS_CERT_EXPORT_ERROR)) - } - - if err := os.WriteFile(filepath.Join(folderPath.Value, "key.pem"), key, 0o600); err != nil { - _ = s.SendEvent(NewGenericErrorEvent(ErrorCode_TLS_KEY_EXPORT_ERROR)) - } - }() - - return &emptypb.Empty{}, nil -} - func (s *Service) ForceLauncher(_ context.Context, launcher *wrapperspb.StringValue) (*emptypb.Empty, error) { s.log.WithField("launcher", launcher.Value).Debug("ForceLauncher") @@ -446,7 +424,7 @@ func (s *Service) Login(_ context.Context, login *LoginRequest) (*emptypb.Empty, _ = s.SendEvent(NewLoginTfaRequestedEvent(login.Username)) case auth.PasswordMode == proton.TwoPasswordMode: - _ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent()) + _ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent(login.Username)) default: s.finishLogin() @@ -491,7 +469,7 @@ func (s *Service) Login2FA(_ context.Context, login *LoginRequest) (*emptypb.Emp } if s.auth.PasswordMode == proton.TwoPasswordMode { - _ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent()) + _ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent(login.Username)) return } diff --git a/internal/frontend/grpc/service_stream.go b/internal/frontend/grpc/service_stream.go index a7491dae..c87b34c5 100644 --- a/internal/frontend/grpc/service_stream.go +++ b/internal/frontend/grpc/service_stream.go @@ -126,7 +126,7 @@ func (s *Service) StartEventTest() error { // login NewLoginError(LoginErrorType_FREE_USER, "error"), NewLoginTfaRequestedEvent(dummyAddress), - NewLoginTwoPasswordsRequestedEvent(), + NewLoginTwoPasswordsRequestedEvent(dummyAddress), NewLoginFinishedEvent("userID", false), NewLoginAlreadyLoggedInEvent("userID"), diff --git a/internal/services/imapservice/connector.go b/internal/services/imapservice/connector.go index c21fe5ca..6e9dc031 100644 --- a/internal/services/imapservice/connector.go +++ b/internal/services/imapservice/connector.go @@ -189,7 +189,7 @@ func (s *Connector) GetMessageLiteral(ctx context.Context, id imap.MessageID) ([ var literal []byte err = s.identityState.WithAddrKR(msg.AddressID, func(_, addrKR *crypto.KeyRing) error { - l, buildErr := message.BuildRFC822(addrKR, msg.Message, msg.AttData, defaultMessageJobOpts()) + l, buildErr := message.DecryptAndBuildRFC822(addrKR, msg.Message, msg.AttData, defaultMessageJobOpts()) if buildErr != nil { return buildErr } @@ -279,7 +279,7 @@ func (s *Connector) CreateMessage(ctx context.Context, _ connector.IMAPStateWrit if err := s.identityState.WithAddrKR(full.AddressID, func(_, addrKR *crypto.KeyRing) error { var err error - if literal, err = message.BuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil { + if literal, err = message.DecryptAndBuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil { return err } @@ -702,7 +702,7 @@ func (s *Connector) importMessage( return fmt.Errorf("failed to fetch message: %w", err) } - if literal, err = message.BuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil { + if literal, err = message.DecryptAndBuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil { return fmt.Errorf("failed to build message: %w", err) } diff --git a/internal/services/imapservice/sync_build.go b/internal/services/imapservice/sync_build.go index 1af8e407..fe7348cb 100644 --- a/internal/services/imapservice/sync_build.go +++ b/internal/services/imapservice/sync_build.go @@ -57,7 +57,7 @@ func buildRFC822(apiLabels map[string]proton.Label, full proton.FullMessage, add buffer.Grow(full.Size) - if buildErr := message.BuildRFC822Into(addrKR, full.Message, full.AttData, defaultMessageJobOpts(), buffer); buildErr != nil { + if buildErr := message.DecryptAndBuildRFC822Into(addrKR, full.Message, full.AttData, defaultMessageJobOpts(), buffer); buildErr != nil { update = newMessageCreatedFailedUpdate(apiLabels, full.MessageMetadata, buildErr) err = buildErr } else if created, parseErr := newMessageCreatedUpdate(apiLabels, full.MessageMetadata, buffer.Bytes()); parseErr != nil { diff --git a/internal/services/imapservice/sync_message_builder.go b/internal/services/imapservice/sync_message_builder.go index 45adbb61..a0a006e2 100644 --- a/internal/services/imapservice/sync_message_builder.go +++ b/internal/services/imapservice/sync_message_builder.go @@ -46,7 +46,7 @@ func (s SyncMessageBuilder) BuildMessage( ) (syncservice.BuildResult, error) { buffer.Grow(full.Size) - if err := message.BuildRFC822Into(addrKR, full.Message, full.AttData, defaultMessageJobOpts(), buffer); err != nil { + if err := message.DecryptAndBuildRFC822Into(addrKR, full.Message, full.AttData, defaultMessageJobOpts(), buffer); err != nil { return syncservice.BuildResult{}, err } diff --git a/internal/services/smtp/smtp.go b/internal/services/smtp/smtp.go index 58730228..b26fd143 100644 --- a/internal/services/smtp/smtp.go +++ b/internal/services/smtp/smtp.go @@ -535,7 +535,7 @@ func getContactSettings( return proton.ContactSettings{}, fmt.Errorf("failed to get contact: %w", err) } - return contact.GetSettings(userKR, recipient) + return contact.GetSettings(userKR, recipient, proton.CardTypeSigned) } func getMessageSender(parser *parser.Parser) (string, bool) { diff --git a/internal/services/syncservice/stage_download_test.go b/internal/services/syncservice/stage_download_test.go index ed640444..29f91638 100644 --- a/internal/services/syncservice/stage_download_test.go +++ b/internal/services/syncservice/stage_download_test.go @@ -340,10 +340,9 @@ func TestDownloadStage_JobAbortsOnAttachmentDownloadError(t *testing.T) { MessageMetadata: proton.MessageMetadata{ ID: "msg", }, - Header: "", - ParsedHeaders: nil, - Body: "", - MIMEType: "", + Header: "", + Body: "", + MIMEType: "", Attachments: []proton.Attachment{{ ID: "attach", }}, @@ -387,11 +386,10 @@ func buildDownloadStageData(tj *tjob, numMessages int, with422 bool) ([]string, ID: msgID, Size: len([]byte(msgID)), }, - Header: "", - ParsedHeaders: nil, - Body: msgID, - MIMEType: "", - Attachments: nil, + Header: "", + Body: msgID, + MIMEType: "", + Attachments: nil, }, AttData: nil, } diff --git a/internal/vault/certs.go b/internal/vault/certs.go index ee48e203..1434eef6 100644 --- a/internal/vault/certs.go +++ b/internal/vault/certs.go @@ -66,16 +66,6 @@ func (vault *Vault) SetBridgeTLSCertKey(cert, key []byte) error { }) } -func (vault *Vault) GetCertsInstalled() bool { - return vault.getSafe().Certs.Installed -} - -func (vault *Vault) SetCertsInstalled(installed bool) error { - return vault.modSafe(func(data *Data) { - data.Certs.Installed = installed - }) -} - func readPEMCert(certPEMPath, keyPEMPath string) ([]byte, []byte, error) { certPEM, err := os.ReadFile(filepath.Clean(certPEMPath)) if err != nil { diff --git a/internal/vault/certs_test.go b/internal/vault/certs_test.go index 0a3d7fde..8f5b6187 100644 --- a/internal/vault/certs_test.go +++ b/internal/vault/certs_test.go @@ -31,13 +31,4 @@ func TestVault_TLSCerts(t *testing.T) { cert, key := s.GetBridgeTLSCert() require.NotEmpty(t, cert) require.NotEmpty(t, key) - - // Check the certificates are not installed. - require.False(t, s.GetCertsInstalled()) - - // Install the certificates. - require.NoError(t, s.SetCertsInstalled(true)) - - // Check the certificates are installed. - require.True(t, s.GetCertsInstalled()) } diff --git a/internal/vault/types_certs.go b/internal/vault/types_certs.go index 195a43a1..190cb975 100644 --- a/internal/vault/types_certs.go +++ b/internal/vault/types_certs.go @@ -20,8 +20,7 @@ package vault import "github.com/ProtonMail/proton-bridge/v3/internal/certs" type Certs struct { - Bridge Cert - Installed bool + Bridge Cert // If non-empty, the path to the PEM-encoded certificate file. CustomCertPath string diff --git a/pkg/message/build.go b/pkg/message/build.go index baf916ee..87fd73c2 100644 --- a/pkg/message/build.go +++ b/pkg/message/build.go @@ -19,8 +19,6 @@ package message import ( "bytes" - "encoding/base64" - "io" "mime" "net/mail" "strings" @@ -47,48 +45,36 @@ var ( // InternalIDDomain is used as a placeholder for reference/message ID headers to improve compatibility with various clients. const InternalIDDomain = `protonmail.internalid` -func BuildRFC822(kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions) ([]byte, error) { - buf := new(bytes.Buffer) - if err := BuildRFC822Into(kr, msg, attData, opts, buf); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func BuildRFC822Into(kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions, buf *bytes.Buffer) error { +func BuildRFC822Into(kr *crypto.KeyRing, decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer) error { switch { - case len(msg.Attachments) > 0: - return buildMultipartRFC822(kr, msg, attData, opts, buf) + case len(decrypted.Msg.Attachments) > 0: + return buildMultipartRFC822(decrypted, opts, buf) - case msg.MIMEType == "multipart/mixed": - return buildPGPRFC822(kr, msg, opts, buf) + case decrypted.Msg.MIMEType == "multipart/mixed": + return buildPGPRFC822(kr, decrypted, opts, buf) default: - return buildSimpleRFC822(kr, msg, opts, buf) + return buildSimpleRFC822(decrypted, opts, buf) } } -func buildSimpleRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions, buf *bytes.Buffer) error { - var decrypted bytes.Buffer - decrypted.Grow(len(msg.Body)) - - if err := msg.DecryptInto(kr, &decrypted); err != nil { +func buildSimpleRFC822(decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer) error { + if decrypted.BodyErr != nil { if !opts.IgnoreDecryptionErrors { - return errors.Wrap(ErrDecryptionFailed, err.Error()) + return decrypted.BodyErr } - return buildMultipartRFC822(kr, msg, nil, opts, buf) + return buildMultipartRFC822(decrypted, opts, buf) } - hdr := getTextPartHeader(getMessageHeader(msg, opts), decrypted.Bytes(), msg.MIMEType) + hdr := getTextPartHeader(getMessageHeader(decrypted.Msg, opts), decrypted.Body.Bytes(), decrypted.Msg.MIMEType) w, err := message.CreateWriter(buf, hdr) if err != nil { return err } - if _, err := w.Write(decrypted.Bytes()); err != nil { + if _, err := w.Write(decrypted.Body.Bytes()); err != nil { return err } @@ -96,15 +82,13 @@ func buildSimpleRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions, } func buildMultipartRFC822( - kr *crypto.KeyRing, - msg proton.Message, - attData [][]byte, + decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer, ) error { - boundary := newBoundary(msg.ID) + boundary := newBoundary(decrypted.Msg.ID) - hdr := getMessageHeader(msg, opts) + hdr := getMessageHeader(decrypted.Msg, opts) hdr.SetContentType("multipart/mixed", map[string]string{"boundary": boundary.gen()}) @@ -115,31 +99,31 @@ func buildMultipartRFC822( var ( inlineAtts []proton.Attachment - inlineData [][]byte + inlineData []DecryptedAttachment attachAtts []proton.Attachment - attachData [][]byte + attachData []DecryptedAttachment ) - for index, att := range msg.Attachments { + for index, att := range decrypted.Msg.Attachments { if att.Disposition == proton.InlineDisposition { inlineAtts = append(inlineAtts, att) - inlineData = append(inlineData, attData[index]) + inlineData = append(inlineData, decrypted.Attachments[index]) } else { attachAtts = append(attachAtts, att) - attachData = append(attachData, attData[index]) + attachData = append(attachData, decrypted.Attachments[index]) } } if len(inlineAtts) > 0 { - if err := writeRelatedParts(w, kr, boundary, msg, inlineAtts, inlineData, opts); err != nil { + if err := writeRelatedParts(w, boundary, decrypted, inlineAtts, inlineData, opts); err != nil { return err } - } else if err := writeTextPart(w, kr, msg, opts); err != nil { + } else if err := writeTextPart(w, decrypted, opts); err != nil { return err } for i, att := range attachAtts { - if err := writeAttachmentPart(w, kr, att, attachData[i], opts); err != nil { + if err := writeAttachmentPart(w, att, attachData[i], opts); err != nil { return err } } @@ -149,89 +133,53 @@ func buildMultipartRFC822( func writeTextPart( w *message.Writer, - kr *crypto.KeyRing, - msg proton.Message, + decrypted *DecryptedMessage, opts JobOptions, ) error { - var decrypted bytes.Buffer - decrypted.Grow(len(msg.Body)) - - if err := msg.DecryptInto(kr, &decrypted); err != nil { + if decrypted.BodyErr != nil { if !opts.IgnoreDecryptionErrors { - return errors.Wrap(ErrDecryptionFailed, err.Error()) + return decrypted.BodyErr } - return writeCustomTextPart(w, msg, err) + return writeCustomTextPart(w, decrypted, decrypted.BodyErr) } - return writePart(w, getTextPartHeader(message.Header{}, decrypted.Bytes(), msg.MIMEType), decrypted.Bytes()) + return writePart(w, getTextPartHeader(message.Header{}, decrypted.Body.Bytes(), decrypted.Msg.MIMEType), decrypted.Body.Bytes()) } func writeAttachmentPart( w *message.Writer, - kr *crypto.KeyRing, att proton.Attachment, - attData []byte, + decryptedAttachment DecryptedAttachment, opts JobOptions, ) error { - kps, err := base64.StdEncoding.DecodeString(att.KeyPackets) - if err != nil { - return err - } - - // Use io.Multi - attachmentReader := io.MultiReader(bytes.NewReader(kps), bytes.NewReader(attData)) - - stream, err := kr.DecryptStream(attachmentReader, nil, crypto.GetUnixTime()) - if err != nil { + if decryptedAttachment.Err != nil { if !opts.IgnoreDecryptionErrors { - return errors.Wrap(ErrDecryptionFailed, err.Error()) + return decryptedAttachment.Err } log. WithField("attID", att.ID). - WithError(err). - Warn("Attachment decryption failed - construct") + WithError(decryptedAttachment.Err). + Warn("Attachment decryption failed") var pgpMessageBuffer bytes.Buffer - pgpMessageBuffer.Grow(len(kps) + len(attData)) - pgpMessageBuffer.Write(kps) - pgpMessageBuffer.Write(attData) + pgpMessageBuffer.Grow(len(decryptedAttachment.Packet) + len(decryptedAttachment.Encrypted)) + pgpMessageBuffer.Write(decryptedAttachment.Packet) + pgpMessageBuffer.Write(decryptedAttachment.Encrypted) - return writeCustomAttachmentPart(w, att, &crypto.PGPMessage{Data: pgpMessageBuffer.Bytes()}, err) + return writeCustomAttachmentPart(w, att, &crypto.PGPMessage{Data: pgpMessageBuffer.Bytes()}, decryptedAttachment.Err) } - var decryptBuffer bytes.Buffer - decryptBuffer.Grow(len(kps) + len(attData)) - - if _, err := decryptBuffer.ReadFrom(stream); err != nil { - if !opts.IgnoreDecryptionErrors { - return errors.Wrap(ErrDecryptionFailed, err.Error()) - } - - log. - WithField("attID", att.ID). - WithError(err). - Warn("Attachment decryption failed - stream") - - var pgpMessageBuffer bytes.Buffer - pgpMessageBuffer.Grow(len(kps) + len(attData)) - pgpMessageBuffer.Write(kps) - pgpMessageBuffer.Write(attData) - - return writeCustomAttachmentPart(w, att, &crypto.PGPMessage{Data: pgpMessageBuffer.Bytes()}, err) - } - - return writePart(w, getAttachmentPartHeader(att), decryptBuffer.Bytes()) + return writePart(w, getAttachmentPartHeader(att), decryptedAttachment.Data.Bytes()) } func writeRelatedParts( w *message.Writer, - kr *crypto.KeyRing, boundary *boundary, - msg proton.Message, + decrypted *DecryptedMessage, atts []proton.Attachment, - attData [][]byte, + attData []DecryptedAttachment, opts JobOptions, ) error { hdr := message.Header{} @@ -239,12 +187,12 @@ func writeRelatedParts( hdr.SetContentType("multipart/related", map[string]string{"boundary": boundary.gen()}) return createPart(w, hdr, func(rel *message.Writer) error { - if err := writeTextPart(rel, kr, msg, opts); err != nil { + if err := writeTextPart(rel, decrypted, opts); err != nil { return err } for i, att := range atts { - if err := writeAttachmentPart(rel, kr, att, attData[i], opts); err != nil { + if err := writeAttachmentPart(rel, att, attData[i], opts); err != nil { return err } } @@ -253,37 +201,34 @@ func writeRelatedParts( }) } -func buildPGPRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions, buf *bytes.Buffer) error { - var decrypted bytes.Buffer - decrypted.Grow(len(msg.Body)) - - if err := msg.DecryptInto(kr, &decrypted); err != nil { +func buildPGPRFC822(kr *crypto.KeyRing, decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer) error { + if decrypted.BodyErr != nil { if !opts.IgnoreDecryptionErrors { - return errors.Wrap(ErrDecryptionFailed, err.Error()) + return decrypted.BodyErr } - return buildPGPMIMEFallbackRFC822(msg, opts, buf) + return buildPGPMIMEFallbackRFC822(decrypted, opts, buf) } - hdr := getMessageHeader(msg, opts) + hdr := getMessageHeader(decrypted.Msg, opts) - sigs, err := proton.ExtractSignatures(kr, msg.Body) + sigs, err := proton.ExtractSignatures(kr, decrypted.Msg.Body) if err != nil { - log.WithError(err).WithField("id", msg.ID).Warn("Extract signature failed") + log.WithError(err).WithField("id", decrypted.Msg.ID).Warn("Extract signature failed") } if len(sigs) > 0 { - return writeMultipartSignedRFC822(hdr, decrypted.Bytes(), sigs[0], buf) + return writeMultipartSignedRFC822(hdr, decrypted.Body.Bytes(), sigs[0], buf) } - return writeMultipartEncryptedRFC822(hdr, decrypted.Bytes(), buf) + return writeMultipartEncryptedRFC822(hdr, decrypted.Body.Bytes(), buf) } -func buildPGPMIMEFallbackRFC822(msg proton.Message, opts JobOptions, buf *bytes.Buffer) error { - hdr := getMessageHeader(msg, opts) +func buildPGPMIMEFallbackRFC822(decrypted *DecryptedMessage, opts JobOptions, buf *bytes.Buffer) error { + hdr := getMessageHeader(decrypted.Msg, opts) hdr.SetContentType("multipart/encrypted", map[string]string{ - "boundary": newBoundary(msg.ID).gen(), + "boundary": newBoundary(decrypted.Msg.ID).gen(), "protocol": "application/pgp-encrypted", }) @@ -307,7 +252,7 @@ func buildPGPMIMEFallbackRFC822(msg proton.Message, opts JobOptions, buf *bytes. dataHdr.SetContentDisposition("inline", map[string]string{"filename": "encrypted.asc"}) dataHdr.Set("Content-Description", "OpenPGP encrypted message") - if err := writePart(w, dataHdr, []byte(msg.Body)); err != nil { + if err := writePart(w, dataHdr, []byte(decrypted.Msg.Body)); err != nil { return err } @@ -558,8 +503,8 @@ func getAttachmentPartHeader(att proton.Attachment) message.Header { func toMessageHeader(hdr proton.Headers) message.Header { var res message.Header - for key, val := range hdr { - for _, val := range val { + for _, key := range hdr.Order { + for _, val := range hdr.Values[key] { // Using AddRaw instead of Add to save key-value pair as byte buffer within Header. // This buffer is used latter on in message writer to construct message and avoid crash // when key length is more than 76 characters long. diff --git a/pkg/message/build_custom.go b/pkg/message/build_custom.go index 175991da..dbbb7fc4 100644 --- a/pkg/message/build_custom.go +++ b/pkg/message/build_custom.go @@ -30,10 +30,10 @@ import ( // writeCustomTextPart writes an armored-PGP text part for a message body that couldn't be decrypted. func writeCustomTextPart( w *message.Writer, - msg proton.Message, + decrypted *DecryptedMessage, decError error, ) error { - enc, err := crypto.NewPGPMessageFromArmored(msg.Body) + enc, err := crypto.NewPGPMessageFromArmored(decrypted.Msg.Body) if err != nil { return err } @@ -48,7 +48,7 @@ func writeCustomTextPart( var hdr message.Header - hdr.SetContentType(string(msg.MIMEType), nil) + hdr.SetContentType(string(decrypted.Msg.MIMEType), nil) part, err := w.CreatePart(hdr) if err != nil { diff --git a/pkg/message/build_framework_test.go b/pkg/message/build_framework_test.go index 516e224e..9f232945 100644 --- a/pkg/message/build_framework_test.go +++ b/pkg/message/build_framework_test.go @@ -64,12 +64,20 @@ func newTestMessageWithHeaders( func newRawTestMessageWithHeaders(messageID, addressID, mimeType, body string, date time.Time, headers map[string][]string) proton.Message { msgHeaders := proton.Headers{ - "Content-Type": {mimeType}, - "Date": {date.In(time.UTC).Format(time.RFC1123Z)}, + Values: map[string][]string{ + "Content-Type": {mimeType}, + "Date": {date.In(time.UTC).Format(time.RFC1123Z)}, + }, + Order: []string{"Content-Type", "Date"}, } for k, v := range headers { - msgHeaders[k] = v + _, ok := msgHeaders.Values[k] + if !ok { + msgHeaders.Order = append(msgHeaders.Order, k) + } + + msgHeaders.Values[k] = v } return proton.Message{ @@ -98,9 +106,12 @@ func addTestAttachment( Name: name, MIMEType: rfc822.MIMEType(mimeType), Headers: proton.Headers{ - "Content-Type": {mimeType}, - "Content-Disposition": {disposition}, - "Content-Transfer-Encoding": {"base64"}, + Values: map[string][]string{ + "Content-Type": {mimeType}, + "Content-Disposition": {disposition}, + "Content-Transfer-Encoding": {"base64"}, + }, + Order: []string{"Content-Type", "Content-Disposition", "Content-Transfer-Encoding"}, }, Disposition: proton.Disposition(disposition), KeyPackets: base64.StdEncoding.EncodeToString(enc.GetBinaryKeyPacket()), diff --git a/pkg/message/build_test.go b/pkg/message/build_test.go index 58c2ee9b..580d495c 100644 --- a/pkg/message/build_test.go +++ b/pkg/message/build_test.go @@ -39,7 +39,7 @@ func TestBuildPlainMessage(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Now()) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -54,9 +54,10 @@ func TestBuildPlainMessageWithLongKey(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Now()) - msg.ParsedHeaders["ReallyVeryVeryVeryVeryVeryLongLongLongLongLongLongLongKeyThatWillHaveNotSoLongValue"] = []string{"value"} + msg.ParsedHeaders.Values["ReallyVeryVeryVeryVeryVeryLongLongLongLongLongLongLongKeyThatWillHaveNotSoLongValue"] = []string{"value"} + msg.ParsedHeaders.Order = append(msg.ParsedHeaders.Order, "ReallyVeryVeryVeryVeryVeryLongLongLongLongLongLongLongKeyThatWillHaveNotSoLongValue") - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -73,7 +74,7 @@ func TestBuildHTMLMessage(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Now()) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -91,7 +92,7 @@ func TestBuildPlainEncryptedMessage(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -116,7 +117,7 @@ func TestBuildPlainEncryptedMessageMissingHeader(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Now()) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -133,7 +134,7 @@ func TestBuildPlainEncryptedMessageInvalidHeader(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Now()) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -158,7 +159,7 @@ func TestBuildPlainSignedEncryptedMessageMissingHeader(t *testing.T) { msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -195,7 +196,7 @@ func TestBuildPlainSignedEncryptedMessageInvalidHeader(t *testing.T) { msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -224,7 +225,7 @@ func TestBuildPlainEncryptedLatin2Message(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -246,7 +247,7 @@ func TestBuildHTMLEncryptedMessage(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -280,7 +281,7 @@ func TestBuildPlainSignedMessage(t *testing.T) { msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -318,7 +319,7 @@ func TestBuildPlainSignedBase64Message(t *testing.T) { msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -349,7 +350,7 @@ func TestBuildSignedPlainEncryptedMessage(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -386,7 +387,7 @@ func TestBuildSignedHTMLEncryptedMessage(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -425,7 +426,7 @@ func TestBuildSignedPlainEncryptedMessageWithPubKey(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -471,7 +472,7 @@ func TestBuildSignedHTMLEncryptedMessageWithPubKey(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -518,7 +519,7 @@ func TestBuildSignedMultipartAlternativeEncryptedMessageWithPubKey(t *testing.T) kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -575,7 +576,7 @@ func TestBuildSignedEmbeddedMessageRFC822EncryptedMessageWithPubKey(t *testing.T kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -625,7 +626,7 @@ func TestBuildHTMLMessageWithAttachment(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Now()) att := addTestAttachment(t, kr, &msg, "attachID", "file.png", "image/png", "attachment", "attachment") - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) require.NoError(t, err) section(t, res, 1). @@ -649,7 +650,7 @@ func TestBuildHTMLMessageWithRFC822Attachment(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Now()) att := addTestAttachment(t, kr, &msg, "attachID", "file.eml", "message/rfc822", "attachment", "... message/rfc822 ...") - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) require.NoError(t, err) section(t, res, 1). @@ -673,7 +674,7 @@ func TestBuildHTMLMessageWithInlineAttachment(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Now()) inl := addTestAttachment(t, kr, &msg, "inlineID", "file.png", "image/png", "inline", "inline") - res, err := BuildRFC822(kr, msg, [][]byte{inl}, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{inl}, JobOptions{}) require.NoError(t, err) section(t, res, 1). @@ -703,7 +704,7 @@ func TestBuildHTMLMessageWithComplexAttachments(t *testing.T) { att0 := addTestAttachment(t, kr, &msg, "attachID0", "attach0.png", "image/png", "attachment", "attach0") att1 := addTestAttachment(t, kr, &msg, "attachID1", "attach1.png", "image/png", "attachment", "attach1") - res, err := BuildRFC822(kr, msg, [][]byte{ + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{ inl0, inl1, att0, @@ -756,7 +757,7 @@ func TestBuildAttachmentWithExoticFilename(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Now()) att := addTestAttachment(t, kr, &msg, "attachID", `I řeally šhould leařn czech.png`, "image/png", "attachment", "attachment") - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) require.NoError(t, err) // The "name" and "filename" params should actually be RFC2047-encoded because they aren't 7-bit clean. @@ -778,7 +779,7 @@ func TestBuildAttachmentWithLongFilename(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Now()) att := addTestAttachment(t, kr, &msg, "attachID", veryLongName, "image/png", "attachment", "attachment") - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) require.NoError(t, err) // NOTE: hasMaxLineLength is too high! Long filenames should be linewrapped using multipart filenames. @@ -798,7 +799,7 @@ func TestBuildMessageDate(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res).expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)) @@ -814,7 +815,7 @@ func TestBuildMessageWithInvalidDate(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/html", "body", time.Unix(-1, 0)) // Build the message as usual; the date will be before 1970. - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -822,7 +823,7 @@ func TestBuildMessageWithInvalidDate(t *testing.T) { expectHeader(`X-Original-Date`, isMissing()) // Build the message with date sanitization enabled; the date will be RFC822's birthdate. - resFix, err := BuildRFC822(kr, msg, nil, JobOptions{SanitizeDate: true}) + resFix, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{SanitizeDate: true}) require.NoError(t, err) section(t, resFix). @@ -849,7 +850,7 @@ func TestBuildMessageWithExistingOriginalDate(t *testing.T) { }) // Build the message as usual; the date will be before 1970. - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -857,7 +858,7 @@ func TestBuildMessageWithExistingOriginalDate(t *testing.T) { expectHeader(`X-Original-Date`, is("Sun, 15 Jan 2023 04:23:03 +0100 (W. Europe Standard Time)")) // Build the message with date sanitization enabled; the date will be RFC822's birthdate. - resFix, err := BuildRFC822(kr, msg, nil, JobOptions{SanitizeDate: true}) + resFix, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{SanitizeDate: true}) require.NoError(t, err) section(t, resFix). @@ -872,7 +873,7 @@ func TestBuildMessageInternalID(t *testing.T) { kr := utils.MakeKeyRing(t) msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Now()) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res).expectHeader(`Message-Id`, is(``)) @@ -888,7 +889,7 @@ func TestBuildMessageExternalID(t *testing.T) { // Set the message's external ID; this should be used preferentially to set the Message-Id header field. msg.ExternalID = "externalID" - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res).expectHeader(`Message-Id`, is(``)) @@ -903,7 +904,7 @@ func TestBuild8BitBody(t *testing.T) { // Set an 8-bit body; the charset should be set to UTF-8. msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "I řeally šhould leařn czech", time.Now()) - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res).expectContentTypeParam(`charset`, is(`utf-8`)) @@ -919,7 +920,7 @@ func TestBuild8BitSubject(t *testing.T) { // Set an 8-bit subject; it should be RFC2047-encoded. msg.Subject = `I řeally šhould leařn czech` - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -940,7 +941,7 @@ func TestBuild8BitSender(t *testing.T) { Address: `mail@example.com`, } - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -961,7 +962,7 @@ func TestBuild8BitRecipients(t *testing.T) { {Name: `leařn czech`, Address: `mail2@example.com`}, } - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res). @@ -977,14 +978,15 @@ func TestBuildIncludeMessageIDReference(t *testing.T) { msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Now()) // Add references. - msg.ParsedHeaders["References"] = []string{""} + msg.ParsedHeaders.Values["References"] = []string{""} + msg.ParsedHeaders.Order = append(msg.ParsedHeaders.Order, "References") - res, err := BuildRFC822(kr, msg, nil, JobOptions{}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.NoError(t, err) section(t, res).expectHeader(`References`, is(``)) - resRef, err := BuildRFC822(kr, msg, nil, JobOptions{AddMessageIDReference: true}) + resRef, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{AddMessageIDReference: true}) require.NoError(t, err) section(t, resRef).expectHeader(`References`, is(` `)) @@ -999,10 +1001,10 @@ func TestBuildMessageIsDeterministic(t *testing.T) { inl := addTestAttachment(t, kr, &msg, "inlineID", "file.png", "image/png", "inline", "inline") att := addTestAttachment(t, kr, &msg, "attachID", "attach.png", "image/png", "attachment", "attachment") - res1, err := BuildRFC822(kr, msg, [][]byte{inl, att}, JobOptions{}) + res1, err := DecryptAndBuildRFC822(kr, msg, [][]byte{inl, att}, JobOptions{}) require.NoError(t, err) - res2, err := BuildRFC822(kr, msg, [][]byte{inl, att}, JobOptions{}) + res2, err := DecryptAndBuildRFC822(kr, msg, [][]byte{inl, att}, JobOptions{}) require.NoError(t, err) assert.Equal(t, res1, res2) @@ -1017,7 +1019,7 @@ func TestBuildUndecryptableMessage(t *testing.T) { // Use a different keyring for encrypting the message; it won't be decryptable. msg := newTestMessage(t, utils.MakeKeyRing(t), "messageID", "addressID", "text/plain", "body", time.Now()) - _, err := BuildRFC822(kr, msg, nil, JobOptions{}) + _, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{}) require.ErrorIs(t, err, ErrDecryptionFailed) } @@ -1031,7 +1033,7 @@ func TestBuildUndecryptableAttachment(t *testing.T) { // Use a different keyring for encrypting the attachment; it won't be decryptable. att := addTestAttachment(t, utils.MakeKeyRing(t), &msg, "attachID", "file.png", "image/png", "attachment", "attachment") - _, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) + _, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{}) require.ErrorIs(t, err, ErrDecryptionFailed) } @@ -1046,7 +1048,7 @@ func TestBuildCustomMessagePlain(t *testing.T) { msg := newTestMessage(t, foreignKR, "messageID", "addressID", "text/plain", "body", time.Now()) // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, nil, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1070,7 +1072,7 @@ func TestBuildCustomMessageHTML(t *testing.T) { msg := newTestMessage(t, foreignKR, "messageID", "addressID", "text/html", "body", time.Now()) // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, nil, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1098,7 +1100,7 @@ func TestBuildCustomMessageEncrypted(t *testing.T) { msg.Subject = "this is a subject to make sure we preserve subject" // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, nil, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, nil, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1132,7 +1134,7 @@ func TestBuildCustomMessagePlainWithAttachment(t *testing.T) { att := addTestAttachment(t, foreignKR, &msg, "attachID", "file.png", "image/png", "attachment", "attachment") // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1165,7 +1167,7 @@ func TestBuildCustomMessageHTMLWithAttachment(t *testing.T) { att := addTestAttachment(t, foreignKR, &msg, "attachID", "file.png", "image/png", "attachment", "attachment") // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1200,7 +1202,7 @@ func TestBuildCustomMessageOnlyBodyIsUndecryptable(t *testing.T) { att := addTestAttachment(t, kr, &msg, "attachID", "file.png", "image/png", "attachment", "attachment") // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1233,7 +1235,7 @@ func TestBuildCustomMessageOnlyAttachmentIsUndecryptable(t *testing.T) { att := addTestAttachment(t, foreignKR, &msg, "attachID", "file.png", "image/png", "attachment", "attachment") // Tell the job to ignore decryption errors; a custom message will be returned instead of an error. - res, err := BuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{att}, JobOptions{IgnoreDecryptionErrors: true}) require.NoError(t, err) section(t, res). @@ -1271,7 +1273,7 @@ func TestBuildComplexMIMEType(t *testing.T) { att0 := addTestAttachment(t, kr, &msg, "attachID0", "attach0.png", "image/png", "attachment", "attach0") att1 := addTestAttachment(t, kr, &msg, "attachID1", "Cat_August_2010-4.jpeg", "image/jpeg; name=Cat_August_2010-4.jpeg; x-unix-mode=0644", "attachment", "attach1") - res, err := BuildRFC822(kr, msg, [][]byte{ + res, err := DecryptAndBuildRFC822(kr, msg, [][]byte{ att0, att1, }, JobOptions{}) diff --git a/pkg/message/decrypt.go b/pkg/message/decrypt.go new file mode 100644 index 00000000..ee6a7dd2 --- /dev/null +++ b/pkg/message/decrypt.go @@ -0,0 +1,88 @@ +// Copyright (c) 2023 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + +package message + +import ( + "bytes" + "encoding/base64" + "io" + + "github.com/ProtonMail/go-proton-api" + "github.com/ProtonMail/gopenpgp/v2/crypto" + "github.com/pkg/errors" +) + +type DecryptedAttachment struct { + Packet []byte + Encrypted []byte + Data bytes.Buffer + Err error +} + +type DecryptedMessage struct { + Msg proton.Message + Body bytes.Buffer + BodyErr error + Attachments []DecryptedAttachment +} + +var ErrInvalidAttachmentPacket = errors.New("invalid attachment packet") + +func DecryptMessage(kr *crypto.KeyRing, msg proton.Message, attData [][]byte) DecryptedMessage { + result := DecryptedMessage{ + Msg: msg, + } + + result.Body.Grow(len(msg.Body)) + + if err := msg.DecryptInto(kr, &result.Body); err != nil { + result.BodyErr = errors.Wrap(ErrDecryptionFailed, err.Error()) + } + + result.Attachments = make([]DecryptedAttachment, len(msg.Attachments)) + + for i, attachment := range msg.Attachments { + result.Attachments[i].Encrypted = attData[i] + + kps, err := base64.StdEncoding.DecodeString(attachment.KeyPackets) + if err != nil { + result.Attachments[i].Err = errors.Wrap(ErrInvalidAttachmentPacket, err.Error()) + continue + } + + result.Attachments[i].Packet = kps + + // Use io.Multi + attachmentReader := io.MultiReader(bytes.NewReader(kps), bytes.NewReader(attData[i])) + + stream, err := kr.DecryptStream(attachmentReader, nil, crypto.GetUnixTime()) + if err != nil { + result.Attachments[i].Err = errors.Wrap(ErrDecryptionFailed, err.Error()) + continue + } + + result.Attachments[i].Data.Grow(len(kps) + len(attData)) + + if _, err := result.Attachments[i].Data.ReadFrom(stream); err != nil { + result.Attachments[i].Err = errors.Wrap(ErrDecryptionFailed, err.Error()) + continue + } + } + + return result +} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/bridgeqml.qmlproject b/pkg/message/decrypt_and_build.go similarity index 52% rename from internal/frontend/bridge-gui/bridge-gui/qml/bridgeqml.qmlproject rename to pkg/message/decrypt_and_build.go index 15f0db8e..9c35678c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/bridgeqml.qmlproject +++ b/pkg/message/decrypt_and_build.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Proton AG +// Copyright (c) 2023 Proton AG // // This file is part of Proton Mail Bridge. // @@ -15,23 +15,26 @@ // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . -import QmlProject 1.1 +package message -Project { - mainFile: "./MainWindow.qml" +import ( + "bytes" - /* Include .qml, .js, and image files from current directory and subdirectories */ - QmlFiles { - directory: "./" - } - JavaScriptFiles { - directory: "./" - } - ImageFiles { - directory: "./" - } - /* List of plugin directories passed to QML runtime */ - importPaths: [ - "./" - ] + "github.com/ProtonMail/go-proton-api" + "github.com/ProtonMail/gopenpgp/v2/crypto" +) + +func DecryptAndBuildRFC822(kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions) ([]byte, error) { + buf := new(bytes.Buffer) + if err := DecryptAndBuildRFC822Into(kr, msg, attData, opts, buf); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func DecryptAndBuildRFC822Into(kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions, buf *bytes.Buffer) error { + decrypted := DecryptMessage(kr, msg, attData) + + return BuildRFC822Into(kr, &decrypted, opts, buf) } diff --git a/tests/contact_test.go b/tests/contact_test.go new file mode 100644 index 00000000..aeab60ae --- /dev/null +++ b/tests/contact_test.go @@ -0,0 +1,448 @@ +// Copyright (c) 2023 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + +package tests + +import ( + "context" + "errors" + "os" + + "github.com/ProtonMail/gluon/rfc822" + "github.com/ProtonMail/go-proton-api" + "github.com/ProtonMail/gopenpgp/v2/crypto" + "github.com/cucumber/godog" + "github.com/emersion/go-vcard" +) + +func (s *scenario) userHasContacts(user string, contacts *godog.Table) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contactList, err := unmarshalTable[Contact](contacts) + if err != nil { + return err + } + for _, contact := range contactList { + var settings = proton.ContactSettings{} + format, err := stringToMimeType(contact.Format) + if err != nil { + settings.MIMEType = nil + } else { + settings.SetMimeType(format) + } + scheme, err := stringToEncryptionScheme(contact.Scheme) + if err != nil { + settings.Scheme = nil + } else { + settings.SetScheme(scheme) + } + sign, err := stringToBool(contact.Sign) + if err != nil { + settings.Sign = nil + } else { + settings.SetSign(sign) + } + encrypt, err := stringToBool(contact.Encrypt) + if err != nil { + settings.Encrypt = nil + } else { + settings.SetEncrypt(encrypt) + } + if err := createContact(ctx, c, contact.Email, contact.Name, addrKR, &settings); err != nil { + return err + } + } + return nil + }) + }) +} + +func (s *scenario) userHasContactWithName(user, contact, name string) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + return createContact(ctx, c, contact, name, addrKR, nil) + }) + }) +} + +func (s *scenario) contactOfUserHasNoMessageFormat(email, user string) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.MIMEType = nil + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasMessageFormat(email, user, format string) error { + value, err := stringToMimeType(format) + if err != nil { + return err + } + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.SetMimeType(value) + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasNoEncryptionScheme(email, user string) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.Scheme = nil + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasEncryptionScheme(email, user, scheme string) error { + value := proton.PGPInlineScheme + switch { + case scheme == "inline": + value = proton.PGPInlineScheme + case scheme == "MIME": + value = proton.PGPMIMEScheme + default: + return errors.New("parameter should either be 'inline' or 'MIME'") + } + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.SetScheme(value) + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasNoSignature(email, user string) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.Sign = nil + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasSignature(email, user, enabled string) error { + value := true + switch { + case enabled == "enabled": + value = true + case enabled == "disabled": + value = false + default: + return errors.New("parameter should either be 'enabled' or 'disabled'") + } + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.SetSign(value) + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasNoEncryption(email, user string) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.Encrypt = nil + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasEncryption(email, user, enabled string) error { + value := true + switch { + case enabled == "enabled": + value = true + case enabled == "disabled": + value = false + default: + return errors.New("parameter should either be 'enabled' or 'disabled'") + } + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + settings.SetEncrypt(value) + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func (s *scenario) contactOfUserHasPubKey(email, user string, pubKey *godog.DocString) error { + return s.addContactKey(email, user, pubKey.Content) +} + +func (s *scenario) contactOfUserHasPubKeyFromFile(email, user, file string) error { + body, err := os.ReadFile(file) + if err != nil { + return err + } + return s.addContactKey(email, user, string(body)) +} + +func getContact(ctx context.Context, c *proton.Client, email string) (proton.Contact, error) { + contacts, err := c.GetAllContactEmails(ctx, email) + if err != nil { + return proton.Contact{}, err + } + if len(contacts) == 0 { + return proton.Contact{}, errors.New("No contact found with email " + email) + } + return c.GetContact(ctx, contacts[0].ContactID) +} + +func createContact(ctx context.Context, c *proton.Client, contact, name string, addrKR *crypto.KeyRing, settings *proton.ContactSettings) error { + card, err := proton.NewCard(addrKR, proton.CardTypeSigned) + if err != nil { + return err + } + if err := card.Set(addrKR, vcard.FieldUID, &vcard.Field{Value: "proton-legacy-139892c2-f691-4118-8c29-061196013e04", Group: "test"}); err != nil { + return err + } + + if err := card.Set(addrKR, vcard.FieldFormattedName, &vcard.Field{Value: name, Group: "test"}); err != nil { + return err + } + if err := card.Set(addrKR, vcard.FieldEmail, &vcard.Field{Value: contact, Group: "test"}); err != nil { + return err + } + res, err := c.CreateContacts(ctx, proton.CreateContactsReq{Contacts: []proton.ContactCards{{Cards: []*proton.Card{card}}}, Overwrite: 1}) + if err != nil { + return err + } + if res[0].Response.APIError.Code != proton.SuccessCode { + return errors.New("APIError " + res[0].Response.APIError.Message + " while creating contact") + } + + if settings != nil { + ctact, err := getContact(ctx, c, contact) + if err != nil { + return err + } + for _, card := range ctact.Cards { + settings, err := ctact.GetSettings(addrKR, contact, card.Type) + if err != nil { + return err + } + + err = ctact.SetSettings(addrKR, contact, card.Type, settings) + if err != nil { + return err + } + } + } + + return nil +} + +func (s *scenario) addContactKey(email, user string, pubKey string) error { + return s.t.withClient(context.Background(), user, func(ctx context.Context, c *proton.Client) error { + addrID := s.t.getUserByName(user).getAddrID(s.t.getUserByName(user).getEmails()[0]) + return s.t.withAddrKR(ctx, c, user, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { + contact, err := getContact(ctx, c, email) + if err != nil { + return err + } + for _, card := range contact.Cards { + settings, err := contact.GetSettings(addrKR, email, card.Type) + if err != nil { + return err + } + key, err := crypto.NewKeyFromArmored(pubKey) + if err != nil { + return err + } + settings.AddKey(key) + + err = contact.SetSettings(addrKR, email, card.Type, settings) + if err != nil { + return err + } + } + _, err = c.UpdateContact(ctx, contact.ContactMetadata.ID, proton.UpdateContactReq{Cards: contact.Cards}) + return err + }) + }) +} + +func stringToMimeType(value string) (rfc822.MIMEType, error) { + switch { + case value == "plain": + return rfc822.TextPlain, nil + case value == "HTML": + return rfc822.TextHTML, nil + } + return rfc822.TextPlain, errors.New("parameter should either be 'plain' or 'HTML'") +} + +func stringToEncryptionScheme(value string) (proton.EncryptionScheme, error) { + switch { + case value == "inline": + return proton.PGPInlineScheme, nil + case value == "MIME": + return proton.PGPMIMEScheme, nil + } + return proton.PGPInlineScheme, errors.New("parameter should either be 'inline' or 'MIME'") +} + +func stringToBool(value string) (bool, error) { + switch { + case value == "enabled": + return true, nil + case value == "disabled": + return false, nil + } + return false, errors.New("parameter should either be 'enabled' or 'disabled'") +} diff --git a/tests/features/imap/message/import.feature b/tests/features/imap/message/import.feature index f264287b..377cbaa7 100644 --- a/tests/features/imap/message/import.feature +++ b/tests/features/imap/message/import.feature @@ -21,9 +21,19 @@ Feature: IMAP import messages Hello """ Then it succeeds - And IMAP client "1" eventually sees the following messages in "INBOX": - | from | to | subject | body | - | bridgetest@pm.test | bridgetest@example.com | Basic text/plain message | Hello | + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Bridge Test ", + "date": "01 Jan 80 00:00 +0000", + "to": "Internal Bridge ", + "subject": "Basic text/plain message", + "content": { + "content-type": "text/plain", + "body-is": "Hello" + } + } + """ Scenario: Import message with double charset in content type When IMAP client "1" appends the following message to "INBOX": @@ -39,9 +49,22 @@ Feature: IMAP import messages Hello """ Then it succeeds - And IMAP client "1" eventually sees the following messages in "INBOX": - | from | to | subject | body | - | bridgetest@pm.test | bridgetest@example.com | Message with double charset in content type | Hello | + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Bridge Test ", + "date": "01 Jan 80 00:00 +0000", + "to": "Internal Bridge ", + "subject": "Message with double charset in content type", + "content": { + "content-type": "text/plain", + "content-type-charset": "utf-8", + "content-disposition": "", + "transfer-encoding": "quoted-printable", + "body-is": "Hello" + } + } + """ Scenario: Import message with attachment name encoded by RFC 2047 without quoting @@ -69,31 +92,87 @@ Feature: IMAP import messages """ Then it succeeds +# And IMAP client "1" eventually sees the following message in "INBOX" with this structure: +# """ +# { +# "from": "Bridge Test ", +# "date": "01 Jan 80 00:00 +0000", +# "to": "Internal Bridge ", +# "subject": "Message with attachment name encoded by RFC 2047 without quoting", +# "body-contains": "Hello", +# "content": { +# "content-type": "multipart/mixed; boundary=\"boundary\"", +# "sections":[ +# { +# "content-type": "text/plain", +# "body-is": "Hello" +# }, +# { +# "content-type": "application/pdf", +# "content-type-name": "=?US-ASCII?Q?filename?=", +# "content-disposition": "attachment", +# "content-disposition-filename": "=?US-ASCII?Q?filename?=", +# "body-is": "somebytes" +# } +# ] +# } +# } +# """ # The message is imported as UTF-8 and the content type is determined at build time. Scenario: Import message as latin1 without content type When IMAP client "1" appends "plain/text_plain_unknown_latin1.eml" to "INBOX" Then it succeeds - And IMAP client "1" eventually sees the following messages in "INBOX": - | from | to | body | - | sender@pm.me | receiver@pm.me | ééééééé | + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Sender ", + "date": "01 Jan 80 00:00 +0000", + "to": "Receiver ", + "content": { + "content-type": "text/plain", + "body-is": "ééééééé" + } + } + """ # The message is imported and the body is converted to UTF-8. Scenario: Import message as latin1 with content type When IMAP client "1" appends "plain/text_plain_latin1.eml" to "INBOX" Then it succeeds - And IMAP client "1" eventually sees the following messages in "INBOX": - | from | to | body | - | sender@pm.me | receiver@pm.me | ééééééé | + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Sender ", + "date": "01 Jan 80 00:00 +0000", + "to": "Receiver ", + "content": { + "content-type": "text/plain", + "content-type-charset": "utf-8", + "body-is": "ééééééé" + } + } + """ + # The message is imported anad the body is wrongly converted (body is corrupted). Scenario: Import message as latin1 with wrong content type When IMAP client "1" appends "plain/text_plain_wrong_latin1.eml" to "INBOX" Then it succeeds - And IMAP client "1" eventually sees the following messages in "INBOX": - | from | to | - | sender@pm.me | receiver@pm.me | + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Sender ", + "date": "01 Jan 80 00:00 +0000", + "to": "Receiver ", + "content": { + "content-type": "text/plain", + "content-type-charset": "utf-8", + "body-is": "" + } + } + """ Scenario: Import received message to Sent When IMAP client "1" appends the following message to "Sent": @@ -107,9 +186,19 @@ Feature: IMAP import messages Hello """ Then it succeeds - And IMAP client "1" eventually sees the following messages in "Sent": - | from | to | subject | body | - | foo@example.com | bridgetest@pm.test | Hello | Hello | + And IMAP client "1" eventually sees the following message in "Sent" with this structure: + """ + { + "from": "Foo ", + "date": "01 Jan 80 00:00 +0000", + "to": "Bridge Test ", + "subject": "Hello", + "content": { + "content-type": "text/plain", + "body-is": "Hello" + } + } + """ And IMAP client "1" eventually sees 0 messages in "Inbox" Scenario: Import non-received message to Inbox @@ -123,11 +212,22 @@ Feature: IMAP import messages Hello """ Then it succeeds - And IMAP client "1" eventually sees the following messages in "INBOX": - | from | to | subject | body | - | foo@example.com | bridgetest@pm.test | Hello | Hello | + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Foo ", + "date": "01 Jan 80 00:00 +0000", + "to": "Bridge Test ", + "subject": "Hello", + "content": { + "content-type": "text/plain", + "body-is": "Hello" + } + } + """ And IMAP client "1" eventually sees 0 messages in "Sent" + Scenario: Import non-received message to Sent When IMAP client "1" appends the following message to "Sent": """ @@ -139,10 +239,20 @@ Feature: IMAP import messages Hello """ Then it succeeds - And IMAP client "1" eventually sees the following messages in "Sent": - | from | to | subject | body | - | foo@example.com | bridgetest@pm.test | Hello | Hello | And IMAP client "1" eventually sees 0 messages in "Inbox" + And IMAP client "1" eventually sees the following message in "Sent" with this structure: + """ + { + "from": "Foo ", + "date": "01 Jan 80 00:00 +0000", + "to": "Bridge Test ", + "subject": "Hello", + "content": { + "content-type": "text/plain", + "body-is": "Hello" + } + } + """ Scenario Outline: Import message without sender to When IMAP client "1" appends the following message to "": @@ -155,16 +265,53 @@ Feature: IMAP import messages Nope. """ Then it succeeds - And IMAP client "1" eventually sees the following messages in "": - | to | subject | body | - | lionel@richie.com | RE: Hello, is it me you looking for? | Nope. | - + And IMAP client "1" eventually sees the following message in "" with this structure: + """ + { + "from": "Somebody@somewhere.org", + "date": "01 Jan 80 00:00 +0000", + "to": "Lionel Richie ", + "subject": "RE: Hello, is it me you looking for?", + "content": { + "content-type": "text/plain", + "content-type-charset":"utf-8", + "transfer-encoding":"quoted-printable", + "body-is": "Nope." + } + } + """ Examples: | mailbox | - | Drafts | | Archive | | Sent | + Scenario: Import message without sender to Drafts + When IMAP client "1" appends the following message to "Drafts": + """ + From: Somebody@somewhere.org + Date: 01 Jan 1980 00:00:00 +0000 + To: Lionel Richie + Subject: RE: Hello, is it me you looking for? + + Nope. + """ + Then it succeeds + And IMAP client "1" eventually sees the following message in "Drafts" with this structure: + """ + { + "date": "01 Jan 01 00:00 +0000", + "to": "Lionel Richie ", + "subject": "RE: Hello, is it me you looking for?", + "content": { + "content-type": "text/plain", + "content-type-charset":"utf-8", + "transfer-encoding":"quoted-printable", + "body-is": "Nope." + } + } + """ + + Scenario: Import embedded message When IMAP client "1" appends the following message to "INBOX": """ @@ -175,11 +322,21 @@ Feature: IMAP import messages Content-Type: multipart/mixed; boundary="boundary" Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000 + --boundary + This is a multi-part message in MIME format. + --boundary Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit + Hello + + --boundary + Content-Type: text/html; charset=utf-8 + Content-Transfer-Encoding: 7bit + +

HELLO

--boundary Content-Type: message/rfc822; name="embedded.eml" @@ -198,3 +355,41 @@ Feature: IMAP import messages """ Then it succeeds + And IMAP client "1" eventually sees the following message in "INBOX" with this structure: + """ + { + "from": "Foo ", + "date": "01 Jan 80 00:00 +0000", + "to": "Bridge Test ", + "subject": "Embedded message", + "body-contains": "Hello", + "content": { + "content-type": "multipart/mixed", + "sections":[ + { + "body-is": "This is a multi-part message in MIME format." + }, + { + "content-type": "text/plain", + "content-type-charset": "utf-8", + "transfer-encoding": "7bit", + "body-is": "Hello" + }, + { + "content-type": "text/html", + "content-type-charset": "utf-8", + "transfer-encoding": "7bit", + "body-contains": "HELLO" + }, + { + "content-type": "message/rfc822", + "content-type-name": "embedded.eml", + "transfer-encoding": "7bit", + "content-disposition": "attachment", + "content-disposition-filename": "embedded.eml", + "body-is": "From: Bar \nTo: Bridge Test \nSubject: (No Subject)\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\n\nhello" + } + ] + } + } + """ diff --git a/tests/features/user/contact.feature b/tests/features/user/contact.feature new file mode 100644 index 00000000..08ce8643 --- /dev/null +++ b/tests/features/user/contact.feature @@ -0,0 +1,65 @@ +Feature: user's contact + Background: + Given there exists an account with username "[user:user]" and password "password" + And user "[user:user]" has contact "SuperTester@proton.me" with name "Super TESTER" + And user "[user:user]" has contacts: + | name | email | format | scheme | signature | encryption | + | Tester One | tester1@proton.me | plain | MIME | enabled | enabled | + | Tester Two | tester2@proton.me | HTML | inline | disabled | disabled | + Then it succeeds + When bridge starts + And the user logs in with username "[user:user]" and password "password" + Then it succeeds + + + Scenario: Playing with contact settings + When the contact "SuperTester@proton.me" of user "[user:user]" has message format "plain" + When the contact "SuperTester@proton.me" of user "[user:user]" has message format "HTML" + When the contact "SuperTester@proton.me" of user "[user:user]" has encryption scheme "inline" + When the contact "SuperTester@proton.me" of user "[user:user]" has encryption scheme "MIME" + When the contact "SuperTester@proton.me" of user "[user:user]" has no signature + When the contact "SuperTester@proton.me" of user "[user:user]" has no encryption + When the contact "SuperTester@proton.me" of user "[user:user]" has signature "enabled" + When the contact "SuperTester@proton.me" of user "[user:user]" has encryption "enabled" + When the contact "SuperTester@proton.me" of user "[user:user]" has signature "disabled" + When the contact "SuperTester@proton.me" of user "[user:user]" has encryption "disabled" + When the contact "SuperTester@proton.me" of user "[user:user]" has public key from file "testdata/keys/pubkey.asc" + When the contact "SuperTester@proton.me" of user "[user:user]" has public key: + """ + -----BEGIN PGP PUBLIC KEY BLOCK----- + + xsDNBGCwvxYBDACtFOvVIma53f1RLCaE3LtaIaY+sVHHdwsB8g13Kl0x5sK53AchIVR+6RE0JHG1 + pbwQX4Hm05w6cjemDo652Cjn946zXQ65GYMYiG9Uw+HVldk3TsmKHdvI3zZNQkihnGSMP65BG5Mi + 6M3Yq/5FAEP3cOCUKJKkSd6KEx6x3+mbjoPnb4fV0OlfNZa1+FDVlE1gkH3GKQIdcutF5nMDvxry + RHM20vnR1YPrY587Uz6JTnarxCeENn442W/aiG5O2FXgt5QKW66TtTzESry/y6JEpg9EiLKG0Ki4 + k6Z2kkP+YS5xvmqSohVqusmBnOk+wppIhrWaxGJ08Rv5HgzGS3gS29XmzxlBDE+FCrOVSOjAQ94g + UtHZMIPL91A2JMc3RbOXpqVPNyJ+dRzQZ1obyXoaaoiLCQlBtVSbCKUOLVY+bmpyqUdSx45k31Hf + FSUj8KrkjsCw6QFpVEfa5LxKfLHfulZdjL3FquxiYjrLHsYmdlIY2lqtaQocINk6VTa+YkkAEQEA + Ac0cQlFBIDxwbS5icmlkZ2UucWFAZ21haWwuY29tPsLBDwQTAQgAORYhBMTS4mxV82UN59X4Y1MP + t/KzWl0zBQJgsL8WBQkFo5qAAhsDBQsJCAcCBhUICQoLAgUWAgMBAAAKCRBTD7fys1pdMw0dC/9w + Ud0I1lp/AHztlIrPYgwwJcSK7eSxuHXelX6mFImjlieKcWjdBL4Gj8MyOxPgjRDW7JecRZA/7tMI + 37+izWH2CukevGrbpdyuzX0AR7I7DpX4tDVFNTxi7vYDk+Q+lVJ5dL4lYww3t7cuzhqUvj4oSJaS + 9cNeFc66owij7juQoQQ7DmOsLMUw9qlMsDvZNvu83x7hIyGLBCY1gY1VtCeb3QT7uCG8LrQrWkI9 + RLgzZioegHxMtvUgzQRw8U9mS8lJ4J2LaI3Z4DliyKSEebplVMfl53dSl1wfV5huZKifoo9NAusw + lrRw+3Ae+VZ0Obnz14qmyCwevHv6QlkXtntSY1wyprOvzWiu8PE9rHoTmwLI8wMkbiLdFVXCZbon + /1Hg0n1K0fv1A8cIc5JSeCe3y8YMm7b5oEie/cnArqDjZ8VB/vm5H9zvHxfJCI5FwlEVBlosSpib + Tm/1fSpqDgAmH7IDe3wCY8899kmfbBqJzr+5xaCGt+0mgC8jpJIEIKHOwM0EYLC/FwEMAKtvqck9 + 78vAr1ttKpOAEQcKf1X04QLy2AvzHGNcud+XC1u0bHLm3OQsYyLaP3DVAvain6vrVVGiswdsexUI + yIEpBTo+9Rco7MtwwESfxG10p2bbd8q74EaJZkt/ifL6oxEYgp8tCgAB6tqGoXCmkG0nKszrrTTz + Lo/3bHjzfxF01oGDNlQVGVwW+8d5tjV5vowxeSjmdIZXJPNep4Lah/xFisWb71VwdzVEaOi6k7rQ + J5k+Dp1wrCqW1H5RZZt6dGweU4LbuTYBWtnw/2YKz+hBOYGDzil9hqTG9fRXu31d4xOZxuZkv61R + 3DWrxuECKUHgJvFaao0KSnBDa/T/RMJ9Y/KQ0bx0zXOTtoDOhOhpMA8JUTMfWb3Uul50ikxLI5EJ + xnBroy2bLLaRW6ijMgpdnZRAtmhssHipOisxXoxiWMoRfJBR01DhbmSQPTjpsjqM2Z24hPcKN+sf + 9kCKTmaJ2hbOfurriPmM0GHdgewbf5cemKgqVaPfhvyBXhnRjwARAQABwsD8BBgBCAAmFiEExNLi + bFXzZQ3n1fhjUw+38rNaXTMFAmCwvxcFCQWjmoACGwwACgkQUw+38rNaXTNTSgwAqomSuzK80Goi + eOqJ6e0LLiKJTGzMtrtugK9HYzFn1rT7n9W2lZuf4X8Ayo9i32Q4Of1V17EXOyYWHOK/prTDd9DV + sRa+fzLVzC6jln3AKeRi9k/DIs7GDs0poQZyttTVLilK8uDkEWM7mWAyjyBTtWyiKTlfFb7W+M3R + 1lTKXQsn/wBkboJNZj+VTNo5NZ6vIx4PJRFW2lsDKbYJ+Vh5vZUdTwHXr5gLadtWzrVgBVMiLyEr + fgCzdyfMRy+g4uoYxt9JuFvisU/DDVNeAZ8hSgLdI4w65wjeXtT0syzpL9+pJQX0McugEpbIEiOt + e55OL1C0hjvHnsLHPkRuUOtQKru/gNl0bLqZ7mYqPNhJbh/58k+N4eoeTvCjMy65anWuiWjPbm16 + GH/3erZiijKDGYn8UqldiOK9dTC6DbvyJdxuYFliV7cSWIBtiOeGrajxzkuUHMW+d1d4l2gPqs2+ + eT1x4J+7ydQgCvyyI4W01xcFlAL70VRTlYKIbMXJBZ6L + =9sH1 + -----END PGP PUBLIC KEY BLOCK----- + """ + diff --git a/tests/imap_test.go b/tests/imap_test.go index b11e7813..8138a9c9 100644 --- a/tests/imap_test.go +++ b/tests/imap_test.go @@ -19,6 +19,8 @@ package tests import ( "bytes" + "encoding/base64" + "encoding/json" "fmt" "io" "os" @@ -117,8 +119,8 @@ func (s *scenario) imapClientCannotAuthenticateWithIncorrectUsername(clientID st func (s *scenario) imapClientCannotAuthenticateWithIncorrectPassword(clientID string) error { userID, client := s.t.getIMAPClient(clientID) - - if err := client.Login(s.t.getUserByID(userID).getEmails()[0], s.t.getUserByID(userID).getBridgePass()+"bad"); err == nil { + badPass := base64.StdEncoding.EncodeToString([]byte("bad_password")) + if err := client.Login(s.t.getUserByID(userID).getEmails()[0], badPass); err == nil { return fmt.Errorf("expected error, got nil") } @@ -342,6 +344,26 @@ func (s *scenario) imapClientEventuallySeesTheFollowingMessagesInMailbox(clientI }) } +func (s *scenario) imapClientSeesMessageInMailboxWithStructure(clientID, mailbox string, message *godog.DocString) error { + return eventually(func() error { + _, client := s.t.getIMAPClient(clientID) + + var msgStruct MessageStruct + if err := json.Unmarshal([]byte(message.Content), &msgStruct); err != nil { + return err + } + + fetch, err := clientFetch(client, mailbox) + if err != nil { + return err + } + + haveMessages := xslices.Map(fetch, newMessageStructFromIMAP) + + return matchStructure(haveMessages, msgStruct) + }) +} + func (s *scenario) imapClientSeesMessagesInMailbox(clientID string, count int, mailbox string) error { _, client := s.t.getIMAPClient(clientID) diff --git a/tests/smtp_test.go b/tests/smtp_test.go index c5d48f66..4a986837 100644 --- a/tests/smtp_test.go +++ b/tests/smtp_test.go @@ -18,6 +18,7 @@ package tests import ( + "encoding/base64" "fmt" "net/smtp" "os" @@ -84,8 +85,8 @@ func (s *scenario) smtpClientCannotAuthenticateWithIncorrectUsername(clientID st func (s *scenario) smtpClientCannotAuthenticateWithIncorrectPassword(clientID string) error { userID, client := s.t.getSMTPClient(clientID) - - if err := client.Auth(smtp.PlainAuth("", s.t.getUserByID(userID).getEmails()[0], s.t.getUserByID(userID).getBridgePass()+"bad", constants.Host)); err == nil { + badPass := base64.StdEncoding.EncodeToString([]byte("bad_password")) + if err := client.Auth(smtp.PlainAuth("", s.t.getUserByID(userID).getEmails()[0], badPass, constants.Host)); err == nil { return fmt.Errorf("expected error, got nil") } diff --git a/tests/steps_test.go b/tests/steps_test.go index afe452b8..5df03455 100644 --- a/tests/steps_test.go +++ b/tests/steps_test.go @@ -143,6 +143,7 @@ func (s *scenario) steps(ctx *godog.ScenarioContext) { ctx.Step(`^IMAP client "([^"]*)" moves the message with subject "([^"]*)" from "([^"]*)" to "([^"]*)"$`, s.imapClientMovesTheMessageWithSubjectFromTo) ctx.Step(`^IMAP client "([^"]*)" moves all messages from "([^"]*)" to "([^"]*)"$`, s.imapClientMovesAllMessagesFromTo) ctx.Step(`^IMAP client "([^"]*)" eventually sees the following messages in "([^"]*)":$`, s.imapClientEventuallySeesTheFollowingMessagesInMailbox) + ctx.Step(`^IMAP client "([^"]*)" eventually sees the following message in "([^"]*)" with this structure:$`, s.imapClientSeesMessageInMailboxWithStructure) ctx.Step(`^IMAP client "([^"]*)" eventually sees (\d+) messages in "([^"]*)"$`, s.imapClientEventuallySeesMessagesInMailbox) ctx.Step(`^IMAP client "([^"]*)" marks message (\d+) as deleted$`, s.imapClientMarksMessageAsDeleted) ctx.Step(`^IMAP client "([^"]*)" marks the message with subject "([^"]*)" as deleted$`, s.imapClientMarksTheMessageWithSubjectAsDeleted) @@ -194,4 +195,18 @@ func (s *scenario) steps(ctx *godog.ScenarioContext) { ctx.Step(`^config status event "([^"]*)" is eventually send (\d+) time`, s.configStatusEventIsEventuallySendXTime) ctx.Step(`^config status event "([^"]*)" is not send more than (\d+) time`, s.configStatusEventIsNotSendMoreThanXTime) ctx.Step(`^force config status progress to be sent for user"([^"]*)"$`, s.forceConfigStatusProgressToBeSentForUser) + + // ==== CONTACT ==== + ctx.Step(`^user "([^"]*)" has contact "([^"]*)" with name "([^"]*)"$`, s.userHasContactWithName) + ctx.Step(`^user "([^"]*)" has contacts:$`, s.userHasContacts) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has no message format$`, s.contactOfUserHasNoMessageFormat) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has message format "([^"]*)"$`, s.contactOfUserHasMessageFormat) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has no encryption scheme$`, s.contactOfUserHasNoEncryptionScheme) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has encryption scheme "([^"]*)"$`, s.contactOfUserHasEncryptionScheme) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has no signature$`, s.contactOfUserHasNoSignature) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has signature "([^"]*)"$`, s.contactOfUserHasSignature) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has no encryption$`, s.contactOfUserHasNoEncryption) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has encryption "([^"]*)"$`, s.contactOfUserHasEncryption) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has public key:$`, s.contactOfUserHasPubKey) + ctx.Step(`^the contact "([^"]*)" of user "([^"]*)" has public key from file "([^"]*)"$`, s.contactOfUserHasPubKeyFromFile) } diff --git a/tests/testdata/keys/pubkey.asc b/tests/testdata/keys/pubkey.asc new file mode 100644 index 00000000..a66987af --- /dev/null +++ b/tests/testdata/keys/pubkey.asc @@ -0,0 +1,35 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBGCwvxYBDACtFOvVIma53f1RLCaE3LtaIaY+sVHHdwsB8g13Kl0x5sK53AchIVR+6RE0JHG1 +pbwQX4Hm05w6cjemDo652Cjn946zXQ65GYMYiG9Uw+HVldk3TsmKHdvI3zZNQkihnGSMP65BG5Mi +6M3Yq/5FAEP3cOCUKJKkSd6KEx6x3+mbjoPnb4fV0OlfNZa1+FDVlE1gkH3GKQIdcutF5nMDvxry +RHM20vnR1YPrY587Uz6JTnarxCeENn442W/aiG5O2FXgt5QKW66TtTzESry/y6JEpg9EiLKG0Ki4 +k6Z2kkP+YS5xvmqSohVqusmBnOk+wppIhrWaxGJ08Rv5HgzGS3gS29XmzxlBDE+FCrOVSOjAQ94g +UtHZMIPL91A2JMc3RbOXpqVPNyJ+dRzQZ1obyXoaaoiLCQlBtVSbCKUOLVY+bmpyqUdSx45k31Hf +FSUj8KrkjsCw6QFpVEfa5LxKfLHfulZdjL3FquxiYjrLHsYmdlIY2lqtaQocINk6VTa+YkkAEQEA +Ac0cQlFBIDxwbS5icmlkZ2UucWFAZ21haWwuY29tPsLBDwQTAQgAORYhBMTS4mxV82UN59X4Y1MP +t/KzWl0zBQJgsL8WBQkFo5qAAhsDBQsJCAcCBhUICQoLAgUWAgMBAAAKCRBTD7fys1pdMw0dC/9w +Ud0I1lp/AHztlIrPYgwwJcSK7eSxuHXelX6mFImjlieKcWjdBL4Gj8MyOxPgjRDW7JecRZA/7tMI +37+izWH2CukevGrbpdyuzX0AR7I7DpX4tDVFNTxi7vYDk+Q+lVJ5dL4lYww3t7cuzhqUvj4oSJaS +9cNeFc66owij7juQoQQ7DmOsLMUw9qlMsDvZNvu83x7hIyGLBCY1gY1VtCeb3QT7uCG8LrQrWkI9 +RLgzZioegHxMtvUgzQRw8U9mS8lJ4J2LaI3Z4DliyKSEebplVMfl53dSl1wfV5huZKifoo9NAusw +lrRw+3Ae+VZ0Obnz14qmyCwevHv6QlkXtntSY1wyprOvzWiu8PE9rHoTmwLI8wMkbiLdFVXCZbon +/1Hg0n1K0fv1A8cIc5JSeCe3y8YMm7b5oEie/cnArqDjZ8VB/vm5H9zvHxfJCI5FwlEVBlosSpib +Tm/1fSpqDgAmH7IDe3wCY8899kmfbBqJzr+5xaCGt+0mgC8jpJIEIKHOwM0EYLC/FwEMAKtvqck9 +78vAr1ttKpOAEQcKf1X04QLy2AvzHGNcud+XC1u0bHLm3OQsYyLaP3DVAvain6vrVVGiswdsexUI +yIEpBTo+9Rco7MtwwESfxG10p2bbd8q74EaJZkt/ifL6oxEYgp8tCgAB6tqGoXCmkG0nKszrrTTz +Lo/3bHjzfxF01oGDNlQVGVwW+8d5tjV5vowxeSjmdIZXJPNep4Lah/xFisWb71VwdzVEaOi6k7rQ +J5k+Dp1wrCqW1H5RZZt6dGweU4LbuTYBWtnw/2YKz+hBOYGDzil9hqTG9fRXu31d4xOZxuZkv61R +3DWrxuECKUHgJvFaao0KSnBDa/T/RMJ9Y/KQ0bx0zXOTtoDOhOhpMA8JUTMfWb3Uul50ikxLI5EJ +xnBroy2bLLaRW6ijMgpdnZRAtmhssHipOisxXoxiWMoRfJBR01DhbmSQPTjpsjqM2Z24hPcKN+sf +9kCKTmaJ2hbOfurriPmM0GHdgewbf5cemKgqVaPfhvyBXhnRjwARAQABwsD8BBgBCAAmFiEExNLi +bFXzZQ3n1fhjUw+38rNaXTMFAmCwvxcFCQWjmoACGwwACgkQUw+38rNaXTNTSgwAqomSuzK80Goi +eOqJ6e0LLiKJTGzMtrtugK9HYzFn1rT7n9W2lZuf4X8Ayo9i32Q4Of1V17EXOyYWHOK/prTDd9DV +sRa+fzLVzC6jln3AKeRi9k/DIs7GDs0poQZyttTVLilK8uDkEWM7mWAyjyBTtWyiKTlfFb7W+M3R +1lTKXQsn/wBkboJNZj+VTNo5NZ6vIx4PJRFW2lsDKbYJ+Vh5vZUdTwHXr5gLadtWzrVgBVMiLyEr +fgCzdyfMRy+g4uoYxt9JuFvisU/DDVNeAZ8hSgLdI4w65wjeXtT0syzpL9+pJQX0McugEpbIEiOt +e55OL1C0hjvHnsLHPkRuUOtQKru/gNl0bLqZ7mYqPNhJbh/58k+N4eoeTvCjMy65anWuiWjPbm16 +GH/3erZiijKDGYn8UqldiOK9dTC6DbvyJdxuYFliV7cSWIBtiOeGrajxzkuUHMW+d1d4l2gPqs2+ +eT1x4J+7ydQgCvyyI4W01xcFlAL70VRTlYKIbMXJBZ6L +=9sH1 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/types_test.go b/tests/types_test.go index e654635d..78eb7b97 100644 --- a/tests/types_test.go +++ b/tests/types_test.go @@ -56,6 +56,31 @@ type Message struct { References string `bdd:"references"` } +type MessageStruct struct { + From string `json:"from"` + To string `json:"to"` + CC string `json:"cc"` + BCC string `json:"bcc"` + Subject string `json:"subject"` + Date string `json:"date"` + + Content MessageSection `json:"content"` +} + +type MessageSection struct { + ContentType string `json:"content-type"` + ContentTypeBoundary string `json:"content-type-boundary"` + ContentTypeCharset string `json:"content-type-charset"` + ContentTypeName string `json:"content-type-name"` + ContentDisposition string `json:"content-disposition"` + ContentDispositionFilename string `json:"content-disposition-filename"` + Sections []MessageSection `json:"sections"` + + TransferEncoding string `json:"transfer-encoding"` + BodyContains string `json:"body-contains"` + BodyIs string `json:"body-is"` +} + func (msg Message) Build() []byte { var b []byte @@ -166,6 +191,129 @@ func newMessageFromIMAP(msg *imap.Message) Message { return message } +func newMessageStructFromIMAP(msg *imap.Message) MessageStruct { + section, err := imap.ParseBodySectionName("BODY[]") + if err != nil { + panic(err) + } + + literal, err := io.ReadAll(msg.GetBody(section)) + if err != nil { + panic(err) + } + + m, err := message.Parse(bytes.NewReader(literal)) + if err != nil { + panic(err) + } + var body string + switch { + case m.MIMEType == rfc822.TextPlain: + body = strings.TrimSpace(string(m.PlainBody)) + case m.MIMEType == rfc822.MultipartMixed: + _, body, _ = strings.Cut(string(m.MIMEBody), "\r\n\r\n") + default: + body = strings.TrimSpace(string(m.RichBody)) + } + + message := MessageStruct{ + Subject: msg.Envelope.Subject, + Date: msg.Envelope.Date.Format(time.RFC822Z), + From: formatAddressList(msg.Envelope.From), + To: formatAddressList(msg.Envelope.To), + CC: formatAddressList(msg.Envelope.Cc), + BCC: formatAddressList(msg.Envelope.Bcc), + + Content: parseMessageSection([]byte(strings.TrimSpace(string(literal))), strings.TrimSpace(body)), + } + return message +} + +func formatAddressList(list []*imap.Address) string { + var res string + for idx, address := range list { + if address.PersonalName != "" { + res += address.PersonalName + " <" + address.Address() + ">" + } else { + res += address.Address() + } + if idx < len(list)-1 { + res += "; " + } + } + return res +} + +func parseMessageSection(literal []byte, body string) MessageSection { + mimeType, boundary, charset, name := parseContentType(literal) + + headers, err := rfc822.Parse(literal).ParseHeader() + if err != nil { + panic(err) + } + + msgSect := MessageSection{ + ContentType: string(mimeType), + ContentTypeBoundary: boundary, + ContentTypeCharset: charset, + ContentTypeName: name, + TransferEncoding: headers.Get("content-transfer-encoding"), + BodyIs: body, + } + + contentDisposition := bytes.Split([]byte(headers.Get("content-disposition")), []byte(";")) + for id, value := range contentDisposition { + if id == 0 { + msgSect.ContentDisposition = strings.TrimSpace(string(value)) + continue + } + param := bytes.Split(value, []byte("=")) + if strings.TrimSpace(string(param[0])) == "filename" && len(param) >= 2 { + _, filename, _ := strings.Cut(string(value), "filename=") + filename = strings.Trim(filename, "\"") + msgSect.ContentDispositionFilename = strings.TrimSpace(filename) + } + } + + if msgSect.ContentTypeBoundary != "" { + sections := bytes.Split(literal, []byte("--"+msgSect.ContentTypeBoundary)) + // Remove last element that will be the -- from finale boundary + sections = sections[:len(sections)-1] + sections = sections[1:] + for _, v := range sections { + str := strings.TrimSpace(string(v)) + _, sectionBody, found := strings.Cut(str, "\r\n\r\n") + if !found { + if _, sectionBody, found = strings.Cut(str, "\n\n"); !found { + sectionBody = str + } + } + msgSect.Sections = append(msgSect.Sections, parseMessageSection([]byte(str), strings.TrimSpace(sectionBody))) + } + } + return msgSect +} + +func parseContentType(literal []byte) (rfc822.MIMEType, string, string, string) { + mimeType, params, err := rfc822.Parse(literal).ContentType() + if err != nil { + panic(err) + } + boundary, ok := params["boundary"] + if !ok { + boundary = "" + } + charset, ok := params["charset"] + if !ok { + charset = "" + } + name, ok := params["name"] + if !ok { + name = "" + } + return mimeType, boundary, charset, name +} + func matchMessages(have, want []Message) error { slices.SortFunc(have, func(a, b Message) bool { return a.Subject < b.Subject @@ -182,6 +330,73 @@ func matchMessages(have, want []Message) error { return nil } +func matchStructure(have []MessageStruct, want MessageStruct) error { + for _, msg := range have { + if want.From != "" && msg.From != want.From { + continue + } + if want.To != "" && msg.To != want.To { + continue + } + if want.BCC != "" && msg.BCC != want.BCC { + continue + } + if want.CC != "" && msg.CC != want.CC { + continue + } + if want.Subject != "" && msg.Subject != want.Subject { + continue + } + if want.Date != "" && want.Date != msg.Date { + continue + } + + if matchContent(msg.Content, want.Content) { + return nil + } + } + return fmt.Errorf("missing messages: have %#v, want %#v", have, want) +} + +func matchContent(have MessageSection, want MessageSection) bool { + if want.ContentType != "" && want.ContentType != have.ContentType { + return false + } + if want.ContentTypeBoundary != "" && want.ContentTypeBoundary != have.ContentTypeBoundary { + return false + } + if want.ContentTypeCharset != "" && want.ContentTypeCharset != have.ContentTypeCharset { + return false + } + if want.ContentTypeName != "" && want.ContentTypeName != have.ContentTypeName { + return false + } + if want.ContentDisposition != "" && want.ContentDisposition != have.ContentDisposition { + return false + } + if want.ContentDispositionFilename != "" && want.ContentDispositionFilename != have.ContentDispositionFilename { + return false + } + if want.TransferEncoding != "" && want.TransferEncoding != have.TransferEncoding { + return false + } + if want.BodyContains != "" && !strings.Contains(strings.TrimSpace(have.BodyIs), strings.TrimSpace(want.BodyContains)) { + return false + } + if want.BodyIs != "" && strings.TrimSpace(have.BodyIs) != strings.TrimSpace(want.BodyIs) { + return false + } + if len(have.Sections) != len(want.Sections) { + return false + } + for i, section := range want.Sections { + if !matchContent(have.Sections[i], section) { + return false + } + } + return true +} + type Mailbox struct { Name string `bdd:"name"` Total int `bdd:"total"` @@ -327,3 +542,12 @@ func mustParseBool(s string) bool { return v } + +type Contact struct { + Name string `bdd:"name"` + Email string `bdd:"email"` + Format string `bdd:"format"` + Scheme string `bdd:"scheme"` + Sign string `bdd:"signature"` + Encrypt string `bdd:"encryption"` +} diff --git a/tests/user_test.go b/tests/user_test.go index afc446fa..7670cb3d 100644 --- a/tests/user_test.go +++ b/tests/user_test.go @@ -558,7 +558,7 @@ func (s *scenario) createUserAccount(username, password string, disabled bool) e if _, err := s.t.runQuarkCmd( context.Background(), "user:create:subscription", - "--planID", "plus", + "--planID", "visionary2022", string(userDecID), ); err != nil { return err diff --git a/utils/credits.sh b/utils/credits.sh index 9ffe77d7..a0b4c24f 100755 --- a/utils/credits.sh +++ b/utils/credits.sh @@ -30,7 +30,7 @@ egrep $'^\t[^=>]*$' $LOCKFILE | sed -r 's/\t([^ ]*) v.*/\1/g' > $TEMPFILE1 egrep $'^\t.*=>.*v.*$' $LOCKFILE | sed -r 's/^.*=> ([^ ]*)( v.*)?/\1/g' >> $TEMPFILE1 cat $TEMPFILE1 | egrep -v 'therecipe/qt/internal|therecipe/env_.*_512|protontech' | sort | uniq > $TEMPFILE2 # Add non vendor credits -echo -e "\nQt 6.3.1 by Qt group\n" >> $TEMPFILE2 +echo -e "\nQt 6.4.3 by Qt group\n" >> $TEMPFILE2 # join lines sed -i -e ':a' -e 'N' -e '$!ba' -e 's|\n|;|g' $TEMPFILE2