mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
Compare commits
261 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43cbedafb8 | |||
| ac9ab8ab32 | |||
| f04350c046 | |||
| ed1b65731a | |||
| d12928b31c | |||
| 1ea06a95b7 | |||
| e290cd308b | |||
| 3d53bf7477 | |||
| 84c0b907d7 | |||
| b30455b641 | |||
| db9902e70b | |||
| f1f63c1d03 | |||
| 81a3c2aba8 | |||
| bbfc9beb04 | |||
| c4dba09ee6 | |||
| a5435eb1da | |||
| 54c56efdfa | |||
| fc64dbec59 | |||
| 5d3f084a2b | |||
| 606d6c0e3e | |||
| 9fbb6b4ca5 | |||
| 8688277ee6 | |||
| 63eb67760e | |||
| cffab028b2 | |||
| 8ea712b052 | |||
| ff0615167b | |||
| e2b361b9a6 | |||
| 1c6bbf1fae | |||
| e7713fa785 | |||
| 28ae54b5ca | |||
| 00aff40160 | |||
| ab289e6e01 | |||
| a28dc9f2f3 | |||
| 8a859082cd | |||
| 1d972835ff | |||
| 8469e0a661 | |||
| 6ea970bf97 | |||
| a05b90e803 | |||
| 239ad8b946 | |||
| d9fdbb35bc | |||
| 5769fb9466 | |||
| a4020cebd4 | |||
| 7a8760e2ef | |||
| 9552e72ba8 | |||
| c692c21b87 | |||
| bb15efa711 | |||
| e94d3be12d | |||
| 66569f71a0 | |||
| 9bfa79455e | |||
| 67e802e3a0 | |||
| 8a5e2007f6 | |||
| 5b92945626 | |||
| 4a8a7ef093 | |||
| 2cfda14b1a | |||
| 312993e08e | |||
| b1110b04c9 | |||
| d2bc60d9cb | |||
| 1d8f6c75c8 | |||
| 06daaf8d9f | |||
| cb436fff63 | |||
| 921a44f1a3 | |||
| d35af6b686 | |||
| 4cb938c57f | |||
| 232e98d812 | |||
| 6fadbde4a6 | |||
| d2fbbc3e25 | |||
| 1c7c342e19 | |||
| 8e49c84a12 | |||
| 754d80d097 | |||
| 63e272e270 | |||
| 54859a34b2 | |||
| 9b1feed68b | |||
| c9b6cc162b | |||
| bf3c90b8e9 | |||
| 8d63fb2301 | |||
| 7953306cc8 | |||
| 37352d44d2 | |||
| 2a1aeb208d | |||
| 94fbe260e4 | |||
| 6d4937222e | |||
| e33bad7bf1 | |||
| 70fdc91aff | |||
| bde8e45b37 | |||
| 6cb2d944d0 | |||
| cf0f59afc0 | |||
| 65d8fbbf31 | |||
| d919c0accf | |||
| 0ca07066db | |||
| 7fa1948c21 | |||
| 413ab1fc1e | |||
| 45c2102ff7 | |||
| 97fc964467 | |||
| bfde96dc88 | |||
| fdb5c0cbee | |||
| 5b4c6870b5 | |||
| a433da8782 | |||
| 5df95566b7 | |||
| 164fb23653 | |||
| 374194c13b | |||
| 1cd35defe5 | |||
| 56aa497b9d | |||
| 773a230d14 | |||
| 76d257af21 | |||
| 856efec886 | |||
| 46fd1d5a76 | |||
| f565fc4f69 | |||
| 2895f42a64 | |||
| 5751166ebc | |||
| e63afd3910 | |||
| 9b1daa0373 | |||
| 89bb7b6389 | |||
| 31670ad9eb | |||
| fb32d652bc | |||
| 346988e604 | |||
| 43df20c25d | |||
| 25ebcffde3 | |||
| b8ae5be58c | |||
| 26a3385f4e | |||
| dc002959eb | |||
| 8703faf345 | |||
| 3ac59d6943 | |||
| 8f5bd37aee | |||
| 5c69af4418 | |||
| 416f696863 | |||
| 789c1cc816 | |||
| 58736dd254 | |||
| a057138880 | |||
| 76087f1749 | |||
| 83935f3a03 | |||
| b93c10ad47 | |||
| 3309137b80 | |||
| 88c4737ba4 | |||
| e5db9b1ccc | |||
| 6e2e622a2f | |||
| 3a66063938 | |||
| 120ddbbcbb | |||
| 39b31abef8 | |||
| ebeca394c7 | |||
| 2206cb3f12 | |||
| cfd07cf893 | |||
| 2e2648fcd5 | |||
| 3070912416 | |||
| 51722eb1a4 | |||
| 5950eff083 | |||
| 5c67cc2e76 | |||
| 01db488caa | |||
| 6cbef1d786 | |||
| dd9a819ea2 | |||
| 401e56224b | |||
| 1ee52f0f55 | |||
| 9efaf9184c | |||
| a8f270405f | |||
| 38606888fe | |||
| 1b22c32ef9 | |||
| 7a1c7e8743 | |||
| 9449177553 | |||
| bbcedc655a | |||
| 40c97ab19e | |||
| 50dd046b82 | |||
| 7d13c99710 | |||
| 6d7c21b2c9 | |||
| f7434109be | |||
| 414d74d06a | |||
| 110cdbf3ae | |||
| ec4ceb4552 | |||
| ef62704030 | |||
| eaba6b6363 | |||
| e1723fc24b | |||
| 2073513d5e | |||
| 36f7d9672f | |||
| ef183e0758 | |||
| 0d2a803711 | |||
| 06b5276981 | |||
| b2d61da41f | |||
| e51c81fc03 | |||
| 26897f06c4 | |||
| 5ca9a7db37 | |||
| b34f5d072f | |||
| eeb514cc81 | |||
| 650ad49ab0 | |||
| 0e5715c4e3 | |||
| b0f1c3d4c5 | |||
| ba935a6cce | |||
| 1370ff78c5 | |||
| 109c15410a | |||
| 3210709810 | |||
| 8fd988d7c5 | |||
| bf89d548d3 | |||
| 51229cbb68 | |||
| 36c5c37dac | |||
| 5a434fafbc | |||
| ea1c2534df | |||
| 1cafbfcaaa | |||
| 2d44ccaee0 | |||
| 96517b7fb1 | |||
| bc381407a7 | |||
| ddc5e775b9 | |||
| ea26188dc0 | |||
| 159e1cee7d | |||
| 4394ad0e9b | |||
| 856bdd1321 | |||
| ff288145df | |||
| 83bbdbd63e | |||
| fa430ee0fb | |||
| 0303ba38e8 | |||
| 2a78b5c144 | |||
| a00b3cdb92 | |||
| 8d3e04679f | |||
| 21ff7b4b97 | |||
| 4ea161f7ad | |||
| dc584ea29b | |||
| 4a01c46aed | |||
| e8d9534b9c | |||
| 96904b160f | |||
| b535be72f8 | |||
| 40f2d8b30f | |||
| 95a1acec0d | |||
| 5ff074cc49 | |||
| 4f0660bb8c | |||
| 708184439e | |||
| b8a33b9618 | |||
| 1c385d5c9b | |||
| 96773f3225 | |||
| 0f320dbd80 | |||
| 6cb233473a | |||
| 1ac4e70115 | |||
| 07f93d276b | |||
| d29571fb01 | |||
| d6000d025e | |||
| 09ef3b20db | |||
| 405331d59b | |||
| eff7df2136 | |||
| 5823e3a99f | |||
| 26d866bbbd | |||
| d3f7be059d | |||
| b52706a3ca | |||
| aebe7baed0 | |||
| ef31e2917c | |||
| 9eea26459a | |||
| 5747b85543 | |||
| ff78a23084 | |||
| 2a95e1ab41 | |||
| ab76cab533 | |||
| dda2a5d01a | |||
| c2afb42fd4 | |||
| 1d53044803 | |||
| d3f8297eb4 | |||
| b02203e3d3 | |||
| 5c7e4e04f9 | |||
| d7dadd7578 | |||
| ab9a758d63 | |||
| cb0935be96 | |||
| 441b388f62 | |||
| cdbcd30d15 | |||
| acc7ca8d4a | |||
| 42e1dd4c41 | |||
| 4cbd3ca832 | |||
| de0b6c0737 | |||
| 1c344211d1 | |||
| c11a87c16a | |||
| 3bf4282037 |
289
.gitlab-ci.yml
289
.gitlab-ci.yml
@ -16,284 +16,35 @@
|
|||||||
# along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
# along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
---
|
---
|
||||||
image: gitlab.protontech.ch:4567/go/bridge-internal:test-go1.20
|
default:
|
||||||
|
tags:
|
||||||
|
- shared-small
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
GOPRIVATE: gitlab.protontech.ch
|
GOPRIVATE: gitlab.protontech.ch
|
||||||
GOMAXPROCS: $(( ${CI_TAG_CPU} / 2 ))
|
GOMAXPROCS: $(( ${CI_TAG_CPU} / 2 ))
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- apt update && apt-get -y install libsecret-1-dev
|
- |
|
||||||
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
if [ "$CI_JOB_NAME" != "grype-scan-code-dependencies" ]; then
|
||||||
|
apt update && apt-get -y install libsecret-1-dev
|
||||||
|
git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
||||||
|
fi
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
- analyse
|
||||||
- test
|
- test
|
||||||
|
- report
|
||||||
- build
|
- build
|
||||||
|
|
||||||
.rules-branch-and-MR-manual:
|
include:
|
||||||
rules:
|
- local: ci/setup.yml
|
||||||
- if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event"
|
- local: ci/rules.yml
|
||||||
when: manual
|
- local: ci/env.yml
|
||||||
allow_failure: true
|
- local: ci/test.yml
|
||||||
- when: never
|
- local: ci/report.yml
|
||||||
|
- local: ci/build.yml
|
||||||
|
- component: gitlab.protontech.ch/proton/devops/cicd-components/kits/devsecops/go@~latest
|
||||||
|
inputs:
|
||||||
|
stage: analyse
|
||||||
|
|
||||||
.rules-branch-manual-MR-and-devel-always:
|
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_BRANCH == "devel" || $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
when: always
|
|
||||||
allow_failure: false
|
|
||||||
- if: $CI_COMMIT_BRANCH
|
|
||||||
when: manual
|
|
||||||
allow_failure: true
|
|
||||||
- when: never
|
|
||||||
|
|
||||||
.rules-branch-manual-scheduled-and-test-branch-always:
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
|
||||||
when: always
|
|
||||||
allow_failure: false
|
|
||||||
- if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME=~ /^test/
|
|
||||||
when: always
|
|
||||||
allow_failure: false
|
|
||||||
- if: $CI_COMMIT_BRANCH
|
|
||||||
when: manual
|
|
||||||
allow_failure: true
|
|
||||||
- when: never
|
|
||||||
|
|
||||||
# ENV
|
|
||||||
.env-windows:
|
|
||||||
before_script:
|
|
||||||
- export BRIDGE_SYNC_FORCE_MINIMUM_SPEC=1
|
|
||||||
- export GOROOT=/c/Go1.20/
|
|
||||||
- export PATH=$GOROOT/bin:$PATH
|
|
||||||
- export GOARCH=amd64
|
|
||||||
- export GOPATH=~/go1.20
|
|
||||||
- export GO111MODULE=on
|
|
||||||
- export PATH="${GOPATH}/bin:${PATH}"
|
|
||||||
- export MSYSTEM=
|
|
||||||
- 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"
|
|
||||||
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
|
||||||
- git config --global safe.directory '*'
|
|
||||||
- git status --porcelain
|
|
||||||
cache: {}
|
|
||||||
tags:
|
|
||||||
- windows-bridge
|
|
||||||
|
|
||||||
.env-darwin:
|
|
||||||
before_script:
|
|
||||||
- export BRIDGE_SYNC_FORCE_MINIMUM_SPEC=1
|
|
||||||
- export PATH=/usr/local/bin:$PATH
|
|
||||||
- export PATH=/usr/local/opt/git/bin:$PATH
|
|
||||||
- export PATH=/usr/local/opt/make/libexec/gnubin:$PATH
|
|
||||||
- export PATH=/usr/local/opt/gnu-sed/libexec/gnubin:$PATH
|
|
||||||
- export GOROOT=~/local/opt/go@1.20
|
|
||||||
- export PATH="${GOROOT}/bin:$PATH"
|
|
||||||
- export GOPATH=~/go1.20
|
|
||||||
- export PATH="${GOPATH}/bin:$PATH"
|
|
||||||
- export QT6DIR=/opt/Qt/6.4.3/macos
|
|
||||||
- export PATH="${QT6DIR}/bin:$PATH"
|
|
||||||
- uname -a
|
|
||||||
cache: {}
|
|
||||||
tags:
|
|
||||||
- macos-m1-bridge
|
|
||||||
|
|
||||||
.env-linux-build:
|
|
||||||
image: gitlab.protontech.ch:4567/go/bridge-internal:build-go1.20-qt6.4.3
|
|
||||||
variables:
|
|
||||||
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
|
|
||||||
cache:
|
|
||||||
key: linux-vcpkg
|
|
||||||
paths:
|
|
||||||
- .cache
|
|
||||||
when: 'always'
|
|
||||||
before_script:
|
|
||||||
- mkdir -p .cache/bin
|
|
||||||
- export BRIDGE_SYNC_FORCE_MINIMUM_SPEC=1
|
|
||||||
- export PATH=$(pwd)/.cache/bin:$PATH
|
|
||||||
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
|
||||||
- export PATH=$PATH:$QT6DIR/bin
|
|
||||||
- $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove"
|
|
||||||
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
|
||||||
tags:
|
|
||||||
- large
|
|
||||||
|
|
||||||
# Stage: TEST
|
|
||||||
|
|
||||||
lint:
|
|
||||||
stage: test
|
|
||||||
extends:
|
|
||||||
- .rules-branch-manual-MR-and-devel-always
|
|
||||||
script:
|
|
||||||
- make lint
|
|
||||||
tags:
|
|
||||||
- medium
|
|
||||||
|
|
||||||
bug-report-preview:
|
|
||||||
stage: test
|
|
||||||
extends:
|
|
||||||
- .rules-branch-and-MR-manual
|
|
||||||
script:
|
|
||||||
- make lint-bug-report-preview
|
|
||||||
tags:
|
|
||||||
- medium
|
|
||||||
|
|
||||||
.script-test:
|
|
||||||
stage: test
|
|
||||||
extends:
|
|
||||||
- .rules-branch-manual-MR-and-devel-always
|
|
||||||
script:
|
|
||||||
- make test
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- coverage/**
|
|
||||||
|
|
||||||
test-linux:
|
|
||||||
extends:
|
|
||||||
- .script-test
|
|
||||||
tags:
|
|
||||||
- large
|
|
||||||
|
|
||||||
fuzz-linux:
|
|
||||||
stage: test
|
|
||||||
extends:
|
|
||||||
- .rules-branch-manual-MR-and-devel-always
|
|
||||||
script:
|
|
||||||
- make fuzz
|
|
||||||
tags:
|
|
||||||
- large
|
|
||||||
|
|
||||||
test-linux-race:
|
|
||||||
extends:
|
|
||||||
- test-linux
|
|
||||||
- .rules-branch-and-MR-manual
|
|
||||||
script:
|
|
||||||
- make test-race
|
|
||||||
|
|
||||||
test-integration:
|
|
||||||
extends:
|
|
||||||
- test-linux
|
|
||||||
script:
|
|
||||||
- make test-integration
|
|
||||||
|
|
||||||
test-integration-race:
|
|
||||||
extends:
|
|
||||||
- test-integration
|
|
||||||
- .rules-branch-and-MR-manual
|
|
||||||
script:
|
|
||||||
- make test-integration-race
|
|
||||||
|
|
||||||
test-integration-nightly:
|
|
||||||
extends:
|
|
||||||
- test-integration
|
|
||||||
- .rules-branch-manual-scheduled-and-test-branch-always
|
|
||||||
needs:
|
|
||||||
- test-integration
|
|
||||||
script:
|
|
||||||
- make test-integration-nightly
|
|
||||||
|
|
||||||
test-windows:
|
|
||||||
extends:
|
|
||||||
- .env-windows
|
|
||||||
- .script-test
|
|
||||||
|
|
||||||
test-darwin:
|
|
||||||
extends:
|
|
||||||
- .env-darwin
|
|
||||||
- .script-test
|
|
||||||
|
|
||||||
test-coverage:
|
|
||||||
stage: test
|
|
||||||
extends:
|
|
||||||
- .rules-branch-manual-scheduled-and-test-branch-always
|
|
||||||
script:
|
|
||||||
- ./utils/coverage.sh
|
|
||||||
coverage: '/total:.*\(statements\).*\d+\.\d+%/'
|
|
||||||
needs:
|
|
||||||
- test-linux
|
|
||||||
- test-windows
|
|
||||||
- test-darwin
|
|
||||||
- test-integration
|
|
||||||
- test-integration-nightly
|
|
||||||
tags:
|
|
||||||
- small
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- coverage*
|
|
||||||
- coverage/**
|
|
||||||
when: 'always'
|
|
||||||
reports:
|
|
||||||
coverage_report:
|
|
||||||
coverage_format: cobertura
|
|
||||||
path: coverage.xml
|
|
||||||
|
|
||||||
# Stage: BUILD
|
|
||||||
|
|
||||||
.script-build:
|
|
||||||
stage: build
|
|
||||||
needs: ["lint"]
|
|
||||||
extends:
|
|
||||||
- .rules-branch-and-MR-manual
|
|
||||||
script:
|
|
||||||
- make build
|
|
||||||
- git diff && git diff-index --quiet HEAD
|
|
||||||
- make vault-editor
|
|
||||||
artifacts:
|
|
||||||
expire_in: 1 day
|
|
||||||
when: always
|
|
||||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
|
||||||
paths:
|
|
||||||
- bridge_*.tgz
|
|
||||||
- vault-editor
|
|
||||||
|
|
||||||
build-linux:
|
|
||||||
extends:
|
|
||||||
- .script-build
|
|
||||||
- .env-linux-build
|
|
||||||
|
|
||||||
build-linux-qa:
|
|
||||||
extends:
|
|
||||||
- build-linux
|
|
||||||
- .rules-branch-manual-MR-and-devel-always
|
|
||||||
variables:
|
|
||||||
BUILD_TAGS: "build_qa"
|
|
||||||
|
|
||||||
build-darwin:
|
|
||||||
extends:
|
|
||||||
- .script-build
|
|
||||||
- .env-darwin
|
|
||||||
|
|
||||||
build-darwin-qa:
|
|
||||||
extends:
|
|
||||||
- build-darwin
|
|
||||||
variables:
|
|
||||||
BUILD_TAGS: "build_qa"
|
|
||||||
|
|
||||||
build-windows:
|
|
||||||
extends:
|
|
||||||
- .script-build
|
|
||||||
- .env-windows
|
|
||||||
|
|
||||||
build-windows-qa:
|
|
||||||
extends:
|
|
||||||
- build-windows
|
|
||||||
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...
|
|
||||||
|
|||||||
@ -2,11 +2,12 @@
|
|||||||
run:
|
run:
|
||||||
timeout: 10m
|
timeout: 10m
|
||||||
skip-dirs:
|
skip-dirs:
|
||||||
- pkg/mime
|
|
||||||
- extern
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
|
exclude-dirs:
|
||||||
|
- pkg/mime
|
||||||
|
- extern
|
||||||
exclude:
|
exclude:
|
||||||
- Using the variable on range scope `tt` in function literal
|
- Using the variable on range scope `tt` in function literal
|
||||||
# For now we are missing a lot of comments.
|
# For now we are missing a lot of comments.
|
||||||
|
|||||||
2
.grype.yaml
Normal file
2
.grype.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Check out for configuration details: https://github.com/anchore/grype?tab=readme-ov-file#configuration
|
||||||
|
fail-on-severity: "medium"
|
||||||
@ -3,7 +3,7 @@
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
* 64-bit OS:
|
* 64-bit OS:
|
||||||
- the go-rfc5322 module cannot currently be compiled for 32-bit OSes
|
- the go-rfc5322 module cannot currently be compiled for 32-bit OSes
|
||||||
* Go 1.20
|
* Go 1.21.9
|
||||||
* Bash with basic build utils: make, gcc, sed, find, grep, ...
|
* Bash with basic build utils: make, gcc, sed, find, grep, ...
|
||||||
- For Windows, it is recommended to use MinGW 64bit shell from [MSYS2](https://www.msys2.org/)
|
- For Windows, it is recommended to use MinGW 64bit shell from [MSYS2](https://www.msys2.org/)
|
||||||
* GCC (Linux), msvc (Windows) or Xcode (macOS)
|
* GCC (Linux), msvc (Windows) or Xcode (macOS)
|
||||||
|
|||||||
@ -50,6 +50,7 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [uuid](https://github.com/google/uuid) available under [license](https://github.com/google/uuid/blob/master/LICENSE)
|
* [uuid](https://github.com/google/uuid) available under [license](https://github.com/google/uuid/blob/master/LICENSE)
|
||||||
* [go-multierror](https://github.com/hashicorp/go-multierror) available under [license](https://github.com/hashicorp/go-multierror/blob/master/LICENSE)
|
* [go-multierror](https://github.com/hashicorp/go-multierror) available under [license](https://github.com/hashicorp/go-multierror/blob/master/LICENSE)
|
||||||
* [html2text](https://github.com/jaytaylor/html2text) available under [license](https://github.com/jaytaylor/html2text/blob/master/LICENSE)
|
* [html2text](https://github.com/jaytaylor/html2text) available under [license](https://github.com/jaytaylor/html2text/blob/master/LICENSE)
|
||||||
|
* [go-locale](https://github.com/jeandeaual/go-locale) available under [license](https://github.com/jeandeaual/go-locale/blob/master/LICENSE)
|
||||||
* [go-keychain](https://github.com/keybase/go-keychain) available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE)
|
* [go-keychain](https://github.com/keybase/go-keychain) available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE)
|
||||||
* [dns](https://github.com/miekg/dns) available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
|
* [dns](https://github.com/miekg/dns) available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
|
||||||
* [memory](https://github.com/pbnjay/memory) available under [license](https://github.com/pbnjay/memory/blob/master/LICENSE)
|
* [memory](https://github.com/pbnjay/memory) available under [license](https://github.com/pbnjay/memory/blob/master/LICENSE)
|
||||||
@ -131,7 +132,8 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [tools](https://golang.org/x/tools) available under [license](https://cs.opensource.google/go/x/tools/+/master:LICENSE)
|
* [tools](https://golang.org/x/tools) available under [license](https://cs.opensource.google/go/x/tools/+/master:LICENSE)
|
||||||
* [genproto](https://google.golang.org/genproto) available under [license](https://pkg.go.dev/google.golang.org/genproto?tab=licenses)
|
* [genproto](https://google.golang.org/genproto) available under [license](https://pkg.go.dev/google.golang.org/genproto?tab=licenses)
|
||||||
* [yaml](https://gopkg.in/yaml.v3) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)
|
* [yaml](https://gopkg.in/yaml.v3) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)
|
||||||
* [docker-credential-helpers](https://github.com/ProtonMail/docker-credential-helpers) available under [license](https://github.com/ProtonMail/docker-credential-helpers/blob/master/LICENSE)
|
|
||||||
* [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
|
* [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
|
||||||
|
* [go-smtp](https://github.com/ProtonMail/go-smtp) available under [license](https://github.com/ProtonMail/go-smtp/blob/master/LICENSE)
|
||||||
|
* [resty](https://github.com/LBeernaertProton/resty/v2) available under [license](https://github.com/LBeernaertProton/resty/v2/blob/master/LICENSE)
|
||||||
* [go-keychain](https://github.com/cuthix/go-keychain) available under [license](https://github.com/cuthix/go-keychain/blob/master/LICENSE)
|
* [go-keychain](https://github.com/cuthix/go-keychain) available under [license](https://github.com/cuthix/go-keychain/blob/master/LICENSE)
|
||||||
<!-- END AUTOGEN -->
|
<!-- END AUTOGEN -->
|
||||||
|
|||||||
277
Changelog.md
277
Changelog.md
@ -3,6 +3,270 @@
|
|||||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
|
|
||||||
|
## Colorado Bridge 3.13.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* BRIDGE-37: added message broadcasting functionality.
|
||||||
|
* BRIDGE-122: added observability service.
|
||||||
|
* BRIDGE-119: added support for Feature Flags.
|
||||||
|
* BRIDGE-116: added command-line switches to enable/disable keychain check on macOS.
|
||||||
|
* BRIDGE-88: added context menu for quick actions on input labels: cut, copy, paste.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* BRIDGE-81: KB article suggestion updates + more weight for long keywords.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* BRIDGE-67: Added detection for username changes on macOS & automatic reconfiguration.
|
||||||
|
* BRIDGE-138: Remove deprecated doc.
|
||||||
|
|
||||||
|
|
||||||
|
## Bastei Bridge 3.12.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* BRIDGE-75: Bridge repair button.
|
||||||
|
* BRIDGE-79: Add New Outlook for Mac KB disclaimer.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* BRIDGE-16: Bump version Go 1.21.9 Qt 6.4.3.
|
||||||
|
* BRIDGE-23: Update gluon to go 1.21.
|
||||||
|
* BRIDGE-22: Update gpa to go 1.21.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* BRIDGE-90: Disable repair button when bridge cannot connect to proton servers; bump GPA.
|
||||||
|
* BRIDGE-69: Explicitly handle semver panic for last bridge version from vault.
|
||||||
|
* BRIDGE-29: Bump gluon version.
|
||||||
|
* BRIDGE-49: Configure gitleaks baseline and grype config.
|
||||||
|
* BRIDGE-21: Missing panic handling.
|
||||||
|
* BRIDGE-17: Broken telemetry heartbeat test.
|
||||||
|
* BRIDGE-10: Bumped gluon version.
|
||||||
|
|
||||||
|
|
||||||
|
## Alcantara Bridge 3.11.1
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* BRIDGE-70: Hotfix for blocked smtp/imap port causing bridge to quit.
|
||||||
|
|
||||||
|
|
||||||
|
## Alcantara Bridge 3.11.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* GODT-3185: Report cases which leads to wrong address key used.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* BRIDGE-14: HV3 implementation.
|
||||||
|
* BRIDGE-15: Certificate install is now also done during Outlook setup on macOS.
|
||||||
|
* GODT-3146: Start servers on startup, keep running even when no users are active.
|
||||||
|
* BRIDGE-19: Update checksum validation use warning instead of error on non-existing files.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* BRIDGE-8: Fix bridge double sessionID issue in logs.
|
||||||
|
* BRIDGE-7: Modify keychain test on macOS.
|
||||||
|
* BRIDGE-4: Logs not being created when invalid flag is passed.
|
||||||
|
* BRIDGE-5: Add tooltip to tray icon.
|
||||||
|
* GODT-3163: Filter MBOX format delimiter.
|
||||||
|
|
||||||
|
|
||||||
|
## Zaehringen Bridge 3.10.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* GODT-3199: Add package log field.
|
||||||
|
* GODT-3220: Add more test scenarios.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-3193: Preserve attachment encoding.
|
||||||
|
* GODT-3214: Encrypt only with primary key.
|
||||||
|
* GODT-2662: Use tart runner for darwin jobs.
|
||||||
|
* GODT-1602: Test: run integration tests against black 🖤.
|
||||||
|
* GODT-3257: Test: quad9 provider test not working on CI.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-3290: Fix test failing because of leap day.
|
||||||
|
|
||||||
|
|
||||||
|
## Ypsilon Bridge 3.9.1
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-3235: Update bridge update key.
|
||||||
|
|
||||||
|
|
||||||
|
## Ypsilon Bridge 3.9.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* GODT-3230: Scripts for removing Bridge from device.
|
||||||
|
* GODT-3195: Add OS info to the log.
|
||||||
|
* GODT-3156: Add time zone info to the bridge log.
|
||||||
|
* GODT-3162: Test: Add test scenarios for KB article suggestions.
|
||||||
|
* Test: Add scenarios for checking messages sent from Web Client.
|
||||||
|
* GODT-3162: Test: Add step definition for checking KB article suggestions.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-3160: Bump version Go 1.21.6.
|
||||||
|
* GODT-3160: Load pipeline env from bridge internal.
|
||||||
|
* GODT-3052: Test: Replace attachments and inline content in feature tests with the smallest valid versions.
|
||||||
|
* GODT-3155: Customize log formatter for easier parsing.
|
||||||
|
* GODT-3172: Detect missing keychain item.
|
||||||
|
* GODT-3172: Do not list, just retrieve vault key.
|
||||||
|
* Log the message received time when handling message creation event.
|
||||||
|
* Set log as artefact for all integration test.
|
||||||
|
* Get better logging arround keychain list initialisation.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-3229: Escape reserved XML characters in Apple configuration profile.
|
||||||
|
* GODT-3228: Get rid of fork of docker-credential-helpers.
|
||||||
|
* GODT-3176: Assume inline if content id is present.
|
||||||
|
* GODT-3160: Ignore non-called vulnerabilities.
|
||||||
|
* GODT-3160: Updated external dependencies reported by govulncheck.
|
||||||
|
* GODT-3203: Crash in chunkDivide.
|
||||||
|
* Fix for SMTP connection mode toggle in bridge-gui-tester.
|
||||||
|
* GODT-3183: Fix database indices.
|
||||||
|
* GODT-3187: Fix numberOfDay computation when changing year and day.
|
||||||
|
* GODT-3188: Happy new year.
|
||||||
|
|
||||||
|
|
||||||
|
## Xikou Bridge 3.8.2
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-3235: Update bridge update key.
|
||||||
|
|
||||||
|
|
||||||
|
## Xikou Bridge 3.8.1
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* GODT-3121: Suggest relevant KB articles in the in-app bug report form.
|
||||||
|
* GODT-2001: Add govulncheck to scan for vulnerabilities.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Keep nighlty-job log as artifact.
|
||||||
|
* Test: Improve TestMetadata_JobCorrectlyFinishesAfterCancel.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-3153: Do not take into account full address when hasing messages.
|
||||||
|
|
||||||
|
|
||||||
|
## Xikou Bridge 3.8.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Test: Add test scenarios to add an /Answered flag to a replied message and revert.
|
||||||
|
* GODT-3046: Added links to KB in error messages.
|
||||||
|
* Test(GODT-3113): Inline HTML message and HTML attachment is getting altered.
|
||||||
|
* Test(GODT-3124): Attempt to fix 401 during login.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-3134: Br tag triggers installer.
|
||||||
|
* Added update events to bridge GUI tester.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-3142: Pass br tag if available.
|
||||||
|
* GODT-3151: Fix feature test with non modified HTML part.
|
||||||
|
* GODT-3151: Only modify HTML Meta content if UTF-8 charset override is needed.
|
||||||
|
* GODT-2851: Add empty text part if no text part when importing multipart.
|
||||||
|
* GODT-3102: Distinguish Vault Decryption from Serialization Errors.
|
||||||
|
* GODT-3124: Handling of sync child jobs.
|
||||||
|
* GODT-3148: Bump go-sysinfo to get rid of linker warning on macOS Sonoma.
|
||||||
|
* GODT-3124: Flaky tests.
|
||||||
|
* GODT-3022: Handle multipart/related on fake server.
|
||||||
|
* GODT-3133: Fix GetSystemLanguage.
|
||||||
|
* GODT-3124: Race condition in sync task waiter.
|
||||||
|
* GODT-3124: Race conditions reported by race check.
|
||||||
|
* GODT-2797: Encode attached key name and use same pubkey name as web-app.
|
||||||
|
* Fix case of IMAP login error.
|
||||||
|
* GODT-3132: Do not allow sending on disabled accounts.
|
||||||
|
* GODT-3046: fix typo spotted during KB article review.
|
||||||
|
* GODT-3129: Bad Event during after address order change.
|
||||||
|
* GODT-3117: Improve GetAllContacts and GetAllContactsEmail.
|
||||||
|
|
||||||
|
|
||||||
|
## Wakato Bridge 3.7.1
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Test(GODT-2740): Sending Plain text messages to internal recipient.
|
||||||
|
* Test(GODT-2892): Create fake log file.
|
||||||
|
* GODT-3122: Added test, changed interface for accessing display name.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Remove debug prints.
|
||||||
|
* GODT-2576: Forward and $Forward Flag Support.
|
||||||
|
* GODT-3053: Use smaller bridge window on small screens.
|
||||||
|
* GODT-3113: Only force UTF-8 charset for HTML part when needed.
|
||||||
|
* GODT-3113: Do not render HTML for attachment.
|
||||||
|
* GODT-3112: Replaced error message when bridge exists prematurely. Added a link to support form.
|
||||||
|
* GODT-2947: Remove 'blame it on the weather' error part from go-smtp.
|
||||||
|
* GODT-3010: Log MimeType parsing issue.
|
||||||
|
* GODT-3104: Added log entry for cert install status on startup on macOS.
|
||||||
|
* GODT-2277: Move Keychain helpers creation in main.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-3054: Only delete drafts after message has been Sent.
|
||||||
|
* GODT-2576: Correctly handle Forwarded messages from Thunderbird.
|
||||||
|
* GODT-3122: Use display name as 'Email Account Name' in macOS profile.
|
||||||
|
* GODT-3125: Heartbeat crash on exit.
|
||||||
|
* GODT-2617: Validate user can send from the SMTP sender address.
|
||||||
|
* GODT-3123: Trigger bad event on empty EventID on existing accounts.
|
||||||
|
* GODT-3118: Do not reset EventID when migrating sync settings.
|
||||||
|
* GODT-3116: Panic on closed channel.
|
||||||
|
* GODT-1623: Throttle SMTP failed requests.
|
||||||
|
* GODT-3047: Fixed 'disk full' error message.
|
||||||
|
* GODT-3054: Delete draft create from reply.
|
||||||
|
* GODT-3048: WKD Policy behavior.
|
||||||
|
|
||||||
|
|
||||||
|
## Wakato Bridge 3.7.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Test(GODT-1224): Add testing around package creation.
|
||||||
|
* Add debug_assemble binary.
|
||||||
|
* Test(GODT-2723): Add importing a message with remote content.
|
||||||
|
* Test(GODT-2737): Sending HTML messages to internal.
|
||||||
|
* Test(GODT-3036): Keep inline attachment order on GPA Fake Server.
|
||||||
|
* GODT-3015: Add simple algorithm to deal with multiple attachment for bug report.
|
||||||
|
* Test: make message structure check more verbose.
|
||||||
|
* Test: Add test around account settings.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-3097: Warn about PGPInline encryption scheme which will be deprecated.
|
||||||
|
* Test: Support multiple users when waiting for sync event.
|
||||||
|
* Test: Update fake server with defautl draft content-type and test it.
|
||||||
|
* Test: be less aggressive while checking for message structure.
|
||||||
|
* GODT-2996: Set password fields to hidden when resetting the login form.
|
||||||
|
* GODT-2990: Change runner tags.
|
||||||
|
* GODT-2835: Bump GPA adding support for AsyncAttachments for BugReport +...
|
||||||
|
* GODT-2940: Allow 3 attempts for mailbox password.
|
||||||
|
* GODT-3095: Update GOpenPGP.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-3106: Broken import route.
|
||||||
|
* GODT-3041: Fix Invalid Or Missing message signature during send.
|
||||||
|
* GODT-3087: Exclude attachment content-disposition part when determining...
|
||||||
|
* GODT-2887: Inline images with Apple Mail.
|
||||||
|
* GODT-3100: Fix issue where a fatal error that bubble up to cli.Run() is not written in the log file.
|
||||||
|
* GODT-3094: Clean up old update files on bridge startup.
|
||||||
|
* GODT-3012: Fix multipart request retries.
|
||||||
|
* GODT-2935: Do not allow parentID into drafts.
|
||||||
|
* GODT-2935: Correct error message when draft fails to create.
|
||||||
|
* GODT-2970: Correctly handle rename of Inbox.
|
||||||
|
* GODT-2969: Prevent duration corruption for config status event.
|
||||||
|
* Fixed type in QA installer CI job name.
|
||||||
|
* GODT-3019: Fix title of main window when no account is connected.
|
||||||
|
* GODT-3013: IMAP service getting "stuck".
|
||||||
|
* GODT-2966: Allow permissive parsing of MediaType parameters for import.
|
||||||
|
* GODT-2966: Add more test regarding quoted/unquoted filename in attachment.
|
||||||
|
* GODT-2490: Fix sync progress not being reset when toggling split mode.
|
||||||
|
* GODT-2515: Customized notification of unavailable keychain on macOS.
|
||||||
|
|
||||||
|
|
||||||
|
## Vasco da Gama Bridge 3.6.1
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-3033: Unable to receive new mail.
|
||||||
|
|
||||||
|
|
||||||
|
## Umshiang Bridge 3.5.4
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-3033: Unable to receive new mail.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Vasco da Gama Bridge 3.6.0
|
## Vasco da Gama Bridge 3.6.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@ -21,6 +285,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* GODT-2664: Trigger QA installer.
|
* GODT-2664: Trigger QA installer.
|
||||||
|
|
||||||
### Fixed
|
### 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-2989: Allow to send bug report when no account connected.
|
||||||
* GODT-2988: Fix setup wizard KB links.
|
* GODT-2988: Fix setup wizard KB links.
|
||||||
* GODT-2968: Use proper base64 encoded string even for bad password test.
|
* GODT-2968: Use proper base64 encoded string even for bad password test.
|
||||||
@ -32,6 +297,18 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* GODT-2929: Message dedup with different text transfer encoding.
|
* GODT-2929: Message dedup with different text transfer encoding.
|
||||||
|
|
||||||
|
|
||||||
|
## Umshiang Bridge 3.5.3
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-3004: Update gopenpgp and dependencies.
|
||||||
|
|
||||||
|
|
||||||
|
## Umshiang Bridge 3.5.2
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-3003: Ensure IMAP State is reset after vault corruption.
|
||||||
|
* GODT-3001: Only create system labels during system label sync.
|
||||||
|
|
||||||
|
|
||||||
## Umshiang Bridge 3.5.1
|
## Umshiang Bridge 3.5.1
|
||||||
|
|
||||||
|
|||||||
33
Makefile
33
Makefile
@ -1,17 +1,18 @@
|
|||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
|
export CGO_ENABLED=1
|
||||||
|
|
||||||
# By default, the target OS is the same as the host OS,
|
# By default, the target OS is the same as the host OS,
|
||||||
# but this can be overridden by setting TARGET_OS to "windows"/"darwin"/"linux".
|
# but this can be overridden by setting TARGET_OS to "windows"/"darwin"/"linux".
|
||||||
GOOS:=$(shell go env GOOS)
|
GOOS:=$(shell go env GOOS)
|
||||||
TARGET_CMD?=Desktop-Bridge
|
TARGET_CMD?=Desktop-Bridge
|
||||||
TARGET_OS?=${GOOS}
|
TARGET_OS?=${GOOS}
|
||||||
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
ROOT_DIR:=$(realpath .)
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
||||||
|
|
||||||
# Keep version hardcoded so app build works also without Git repository.
|
# Keep version hardcoded so app build works also without Git repository.
|
||||||
BRIDGE_APP_VERSION?=3.6.0+git
|
BRIDGE_APP_VERSION?=3.13.0+git
|
||||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||||
APP_FULL_NAME:=Proton Mail Bridge
|
APP_FULL_NAME:=Proton Mail Bridge
|
||||||
APP_VENDOR:=Proton AG
|
APP_VENDOR:=Proton AG
|
||||||
@ -19,8 +20,8 @@ SRC_ICO:=bridge.ico
|
|||||||
SRC_ICNS:=Bridge.icns
|
SRC_ICNS:=Bridge.icns
|
||||||
SRC_SVG:=bridge.svg
|
SRC_SVG:=bridge.svg
|
||||||
EXE_NAME:=proton-bridge
|
EXE_NAME:=proton-bridge
|
||||||
REVISION:=$(shell ./utils/get_revision.sh)
|
REVISION:=$(shell "${ROOT_DIR}/utils/get_revision.sh" rev)
|
||||||
TAG:=$(shell ./utils/get_revision.sh tag)
|
TAG:=$(shell "${ROOT_DIR}/utils/get_revision.sh" tag)
|
||||||
BUILD_TIME:=$(shell date +%FT%T%z)
|
BUILD_TIME:=$(shell date +%FT%T%z)
|
||||||
MACOS_MIN_VERSION_ARM64=11.0
|
MACOS_MIN_VERSION_ARM64=11.0
|
||||||
MACOS_MIN_VERSION_AMD64=10.15
|
MACOS_MIN_VERSION_AMD64=10.15
|
||||||
@ -101,9 +102,9 @@ endif
|
|||||||
|
|
||||||
ifeq "${GOOS}" "windows"
|
ifeq "${GOOS}" "windows"
|
||||||
go-build-finalize= \
|
go-build-finalize= \
|
||||||
$(if $(4),powershell Copy-Item ${ROOT_DIR}/${RESOURCE_FILE} ${4} &&,) \
|
$(if $(4),cp "${ROOT_DIR}/${RESOURCE_FILE}" ${4} &&,) \
|
||||||
$(call go-build,$(1),$(2),$(3)) \
|
$(call go-build,$(1),$(2),$(3)) \
|
||||||
$(if $(4), && powershell Remove-Item ${4} -Force,)
|
$(if $(4), && rm -f ${4},)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
${EXE_NAME}: gofiles ${RESOURCE_FILE}
|
${EXE_NAME}: gofiles ${RESOURCE_FILE}
|
||||||
@ -117,7 +118,10 @@ versioner:
|
|||||||
go build ${BUILD_FLAGS} -o versioner utils/versioner/main.go
|
go build ${BUILD_FLAGS} -o versioner utils/versioner/main.go
|
||||||
|
|
||||||
vault-editor:
|
vault-editor:
|
||||||
$(call go-build-finalize,"-tags=debug","vault-editor","./utils/vault-editor/main.go")
|
$(call go-build-finalize,-tags=debug,"vault-editor","./utils/vault-editor/main.go")
|
||||||
|
|
||||||
|
bridge-rollout:
|
||||||
|
$(call go-build-finalize,, "bridge-rollout","./utils/bridge-rollout/bridge-rollout.go")
|
||||||
|
|
||||||
hasher:
|
hasher:
|
||||||
go build -o hasher utils/hasher/main.go
|
go build -o hasher utils/hasher/main.go
|
||||||
@ -164,7 +168,7 @@ ${EXE_TARGET}: check-build-essentials ${EXE_NAME}
|
|||||||
BRIDGE_BUILD_TIME=${BUILD_TIME} \
|
BRIDGE_BUILD_TIME=${BUILD_TIME} \
|
||||||
BRIDGE_GUI_BUILD_CONFIG=Release \
|
BRIDGE_GUI_BUILD_CONFIG=Release \
|
||||||
BRIDGE_BUILD_ENV=${BUILD_ENV} \
|
BRIDGE_BUILD_ENV=${BUILD_ENV} \
|
||||||
BRIDGE_INSTALL_PATH=${ROOT_DIR}/${DEPLOY_DIR}/${GOOS} \
|
BRIDGE_INSTALL_PATH="${ROOT_DIR}/${DEPLOY_DIR}/${GOOS}" \
|
||||||
./build.sh install
|
./build.sh install
|
||||||
mv "${ROOT_DIR}/${BRIDGE_EXE}" "$(ROOT_DIR)/${EXE_TARGET}"
|
mv "${ROOT_DIR}/${BRIDGE_EXE}" "$(ROOT_DIR)/${EXE_TARGET}"
|
||||||
|
|
||||||
@ -185,7 +189,7 @@ ${RESOURCE_FILE}: ./dist/info.rc ./dist/${SRC_ICO} .FORCE
|
|||||||
|
|
||||||
## Dev dependencies
|
## Dev dependencies
|
||||||
.PHONY: install-devel-tools install-linter install-go-mod-outdated install-git-hooks
|
.PHONY: install-devel-tools install-linter install-go-mod-outdated install-git-hooks
|
||||||
LINTVER:="v1.52.2"
|
LINTVER:="v1.59.1"
|
||||||
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
||||||
|
|
||||||
install-dev-dependencies: install-devel-tools install-linter install-go-mod-outdated
|
install-dev-dependencies: install-devel-tools install-linter install-go-mod-outdated
|
||||||
@ -260,7 +264,8 @@ test-integration-race: gofiles
|
|||||||
|
|
||||||
test-integration-nightly: gofiles
|
test-integration-nightly: gofiles
|
||||||
mkdir -p coverage/integration
|
mkdir -p coverage/integration
|
||||||
go test \
|
gotestsum \
|
||||||
|
--junitfile tests/result/feature-tests.xml -- \
|
||||||
-v -timeout=90m -p=1 -count=1 -tags=test_integration \
|
-v -timeout=90m -p=1 -count=1 -tags=test_integration \
|
||||||
${GOCOVERAGE} \
|
${GOCOVERAGE} \
|
||||||
github.com/ProtonMail/proton-bridge/v3/tests \
|
github.com/ProtonMail/proton-bridge/v3/tests \
|
||||||
@ -304,6 +309,7 @@ ApplyStageInput,BuildStageInput,BuildStageOutput,DownloadStageInput,DownloadStag
|
|||||||
StateProvider,Regulator,UpdateApplier,MessageBuilder,APIClient,Reporter,DownloadRateModifier \
|
StateProvider,Regulator,UpdateApplier,MessageBuilder,APIClient,Reporter,DownloadRateModifier \
|
||||||
> tmp
|
> tmp
|
||||||
mv tmp internal/services/syncservice/mocks_test.go
|
mv tmp internal/services/syncservice/mocks_test.go
|
||||||
|
mockgen --package mocks github.com/ProtonMail/gluon/connector IMAPStateWrite > internal/services/imapservice/mocks/mocks.go
|
||||||
|
|
||||||
lint: gofiles lint-golang lint-license lint-dependencies lint-changelog lint-bug-report
|
lint: gofiles lint-golang lint-license lint-dependencies lint-changelog lint-bug-report
|
||||||
|
|
||||||
@ -327,13 +333,6 @@ lint-bug-report:
|
|||||||
lint-bug-report-preview:
|
lint-bug-report-preview:
|
||||||
python3 utils/validate_bug_report_file.py --file "internal/frontend/bridge-gui/bridge-gui/qml/Resources/bug_report_flow.json" --preview
|
python3 utils/validate_bug_report_file.py --file "internal/frontend/bridge-gui/bridge-gui/qml/Resources/bug_report_flow.json" --preview
|
||||||
|
|
||||||
gobinsec: gobinsec-cache.yml build
|
|
||||||
gobinsec -wait -cache -config utils/gobinsec_conf.yml ${EXE_TARGET} ${DEPLOY_DIR}/${TARGET_OS}/${LAUNCHER_EXE}
|
|
||||||
|
|
||||||
gobinsec-cache.yml:
|
|
||||||
./utils/gobinsec_update.sh
|
|
||||||
cp ./utils/gobinsec_update/gobinsec-cache-valid.yml ./gobinsec-cache.yml
|
|
||||||
|
|
||||||
updates: install-go-mod-outdated
|
updates: install-go-mod-outdated
|
||||||
# Uncomment the "-ci" to fail the job if something can be updated.
|
# Uncomment the "-ci" to fail the job if something can be updated.
|
||||||
go list -u -m -json all | go-mod-outdated -update -direct #-ci
|
go list -u -m -json all | go-mod-outdated -update -direct #-ci
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
# Proton Mail Bridge and Import Export app
|
# Proton Mail Bridge and Import Export app
|
||||||
Copyright (c) 2023 Proton AG
|
Copyright (c) 2024 Proton AG
|
||||||
|
|
||||||
This repository holds the Proton Mail Bridge and the Proton Mail Import-Export applications.
|
This repository holds the Proton Mail Bridge and the Proton Mail Import-Export applications.
|
||||||
For a detailed build information see [BUILDS](./BUILDS.md).
|
For a detailed build information see [BUILDS](./BUILDS.md).
|
||||||
|
|||||||
72
ci/build.yml
Normal file
72
ci/build.yml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
.script-build:
|
||||||
|
stage: build
|
||||||
|
needs: ["lint"]
|
||||||
|
extends:
|
||||||
|
- .rules-branch-and-MR-manual
|
||||||
|
script:
|
||||||
|
- which go && go version
|
||||||
|
- which gcc && gcc --version
|
||||||
|
- which qmake && qmake --version
|
||||||
|
- git rev-parse --short=10 HEAD
|
||||||
|
- make build
|
||||||
|
- git diff && git diff-index --quiet HEAD
|
||||||
|
- make vault-editor
|
||||||
|
- make bridge-rollout
|
||||||
|
artifacts:
|
||||||
|
expire_in: 1 day
|
||||||
|
when: always
|
||||||
|
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||||
|
paths:
|
||||||
|
- bridge_*.tgz
|
||||||
|
- vault-editor
|
||||||
|
- bridge-rollout
|
||||||
|
build-linux:
|
||||||
|
extends:
|
||||||
|
- .script-build
|
||||||
|
- .env-linux-build
|
||||||
|
|
||||||
|
build-linux-qa:
|
||||||
|
extends:
|
||||||
|
- build-linux
|
||||||
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
|
variables:
|
||||||
|
BUILD_TAGS: "build_qa"
|
||||||
|
|
||||||
|
build-darwin:
|
||||||
|
extends:
|
||||||
|
- .script-build
|
||||||
|
- .env-darwin
|
||||||
|
|
||||||
|
build-darwin-qa:
|
||||||
|
extends:
|
||||||
|
- build-darwin
|
||||||
|
variables:
|
||||||
|
BUILD_TAGS: "build_qa"
|
||||||
|
|
||||||
|
build-windows:
|
||||||
|
extends:
|
||||||
|
- .script-build
|
||||||
|
- .env-windows
|
||||||
|
|
||||||
|
build-windows-qa:
|
||||||
|
extends:
|
||||||
|
- build-windows
|
||||||
|
variables:
|
||||||
|
BUILD_TAGS: "build_qa"
|
||||||
|
|
||||||
|
trigger-qa-installer:
|
||||||
|
stage: build
|
||||||
|
needs: ["lint"]
|
||||||
|
extends:
|
||||||
|
- .rules-br-tag-always-branch-and-MR-manual
|
||||||
|
variables:
|
||||||
|
APP: bridge
|
||||||
|
WORKFLOW: build-all
|
||||||
|
SRC_TAG: $CI_COMMIT_BRANCH
|
||||||
|
TAG: $CI_COMMIT_TAG
|
||||||
|
SRC_HASH: $CI_COMMIT_SHA
|
||||||
|
trigger:
|
||||||
|
project: "jcuth/bridge-release"
|
||||||
|
branch: master
|
||||||
59
ci/env.yml
Normal file
59
ci/env.yml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
.env-windows:
|
||||||
|
extends:
|
||||||
|
- .image-windows-virt-build
|
||||||
|
before_script:
|
||||||
|
- !reference [.before-script-windows-virt-build, before_script]
|
||||||
|
- !reference [.before-script-git-config, before_script]
|
||||||
|
- mkdir -p .cache/bin
|
||||||
|
- export PATH=$(pwd)/.cache/bin:$PATH
|
||||||
|
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
||||||
|
variables:
|
||||||
|
GOARCH: amd64
|
||||||
|
BRIDGE_SYNC_FORCE_MINIMUM_SPEC: 1
|
||||||
|
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
|
||||||
|
cache:
|
||||||
|
key: windows-vcpkg-go-0
|
||||||
|
paths:
|
||||||
|
- .cache
|
||||||
|
when: 'always'
|
||||||
|
|
||||||
|
.env-darwin:
|
||||||
|
extends:
|
||||||
|
- .image-darwin-build
|
||||||
|
before_script:
|
||||||
|
- !reference [.before-script-darwin-tart-build, before_script]
|
||||||
|
- !reference [.before-script-git-config, before_script]
|
||||||
|
- mkdir -p .cache/bin
|
||||||
|
- export PATH=$(pwd)/.cache/bin:$PATH
|
||||||
|
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
||||||
|
variables:
|
||||||
|
BRIDGE_SYNC_FORCE_MINIMUM_SPEC: 1
|
||||||
|
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
|
||||||
|
cache:
|
||||||
|
key: darwin-go-and-vcpkg
|
||||||
|
paths:
|
||||||
|
- .cache
|
||||||
|
when: 'always'
|
||||||
|
|
||||||
|
.env-linux-build:
|
||||||
|
extends:
|
||||||
|
- .image-linux-build
|
||||||
|
variables:
|
||||||
|
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
|
||||||
|
cache:
|
||||||
|
key: linux-vcpkg
|
||||||
|
paths:
|
||||||
|
- .cache
|
||||||
|
when: 'always'
|
||||||
|
before_script:
|
||||||
|
- export BRIDGE_SYNC_FORCE_MINIMUM_SPEC=1
|
||||||
|
- !reference [.before-script-git-config, before_script]
|
||||||
|
- mkdir -p .cache/bin
|
||||||
|
- export PATH=$(pwd)/.cache/bin:$PATH
|
||||||
|
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
||||||
|
tags:
|
||||||
|
- shared-large
|
||||||
|
|
||||||
25
ci/report.yml
Normal file
25
ci/report.yml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
include:
|
||||||
|
- project: 'tpe/testmo-reporter'
|
||||||
|
ref: master
|
||||||
|
file: '/scenarios/testmo-script.yml'
|
||||||
|
|
||||||
|
testmo-upload:
|
||||||
|
stage: report
|
||||||
|
extends:
|
||||||
|
- .testmo-upload
|
||||||
|
- .rules-branch-manual-scheduled-and-test-branch-always
|
||||||
|
needs:
|
||||||
|
- test-integration-nightly
|
||||||
|
before_script: []
|
||||||
|
variables:
|
||||||
|
TESTMO_TOKEN: "$TESTMO_TOKEN"
|
||||||
|
TESTMO_URL: "https://proton.testmo.net"
|
||||||
|
PROJECT_ID: "9"
|
||||||
|
NAME: "Nightly integration tests"
|
||||||
|
MILESTONE: "Nightly integration tests"
|
||||||
|
SOURCE: "test-integration-nightly"
|
||||||
|
TAGS: "$CI_COMMIT_REF_SLUG"
|
||||||
|
RESULT_FOLDER: "tests/result/*.xml"
|
||||||
58
ci/rules.yml
Normal file
58
ci/rules.yml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
.rules-branch-and-MR-manual:
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
- when: never
|
||||||
|
|
||||||
|
.rules-branch-manual-MR-and-devel-always:
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == "devel" || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
when: always
|
||||||
|
allow_failure: false
|
||||||
|
- if: $CI_COMMIT_BRANCH
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
- when: never
|
||||||
|
|
||||||
|
.rules-branch-manual-br-tag-and-MR-and-devel-always:
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == "devel" || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
when: always
|
||||||
|
allow_failure: false
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_TAG =~ /^br-\d+/
|
||||||
|
when: always
|
||||||
|
allow_failure: false
|
||||||
|
- if: $CI_COMMIT_BRANCH
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
- when: never
|
||||||
|
|
||||||
|
.rules-branch-manual-scheduled-and-test-branch-always:
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||||
|
when: always
|
||||||
|
allow_failure: false
|
||||||
|
- if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME=~ /^test/
|
||||||
|
when: always
|
||||||
|
allow_failure: false
|
||||||
|
- if: $CI_COMMIT_BRANCH
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
- when: never
|
||||||
|
|
||||||
|
.rules-br-tag-always-branch-and-MR-manual:
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == 'push' && $CI_COMMIT_BRANCH
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_TAG =~ /^br-\d+/
|
||||||
|
when: always
|
||||||
|
- when: never
|
||||||
|
|
||||||
7
ci/setup.yml
Normal file
7
ci/setup.yml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
include:
|
||||||
|
- project: 'go/bridge-internal'
|
||||||
|
ref: 'master'
|
||||||
|
file: 'ci/runners-setup.yml'
|
||||||
|
|
||||||
153
ci/test.yml
Normal file
153
ci/test.yml
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
lint:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .image-linux-test
|
||||||
|
- .rules-branch-manual-br-tag-and-MR-and-devel-always
|
||||||
|
script:
|
||||||
|
- make lint
|
||||||
|
tags:
|
||||||
|
- shared-medium
|
||||||
|
|
||||||
|
lint-bug-report-preview:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .image-linux-test
|
||||||
|
- .rules-branch-and-MR-manual
|
||||||
|
script:
|
||||||
|
- make lint-bug-report-preview
|
||||||
|
tags:
|
||||||
|
- shared-medium
|
||||||
|
|
||||||
|
.script-test:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
|
script:
|
||||||
|
- which go && go version
|
||||||
|
- which gcc && gcc --version
|
||||||
|
- make test
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- coverage/**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
test-linux:
|
||||||
|
extends:
|
||||||
|
- .image-linux-test
|
||||||
|
- .script-test
|
||||||
|
tags:
|
||||||
|
- shared-large
|
||||||
|
|
||||||
|
test-windows:
|
||||||
|
extends:
|
||||||
|
- .env-windows
|
||||||
|
- .script-test
|
||||||
|
|
||||||
|
test-darwin:
|
||||||
|
extends:
|
||||||
|
- .env-darwin
|
||||||
|
- .script-test
|
||||||
|
|
||||||
|
fuzz-linux:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .image-linux-test
|
||||||
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
|
script:
|
||||||
|
- make fuzz
|
||||||
|
tags:
|
||||||
|
- shared-large
|
||||||
|
|
||||||
|
test-linux-race:
|
||||||
|
extends:
|
||||||
|
- test-linux
|
||||||
|
- .rules-branch-and-MR-manual
|
||||||
|
script:
|
||||||
|
- make test-race
|
||||||
|
|
||||||
|
test-integration:
|
||||||
|
extends:
|
||||||
|
- test-linux
|
||||||
|
script:
|
||||||
|
- make test-integration | tee -a integration-job.log
|
||||||
|
after_script:
|
||||||
|
- |
|
||||||
|
grep "Error: " integration-job.log
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
paths:
|
||||||
|
- integration-job.log
|
||||||
|
|
||||||
|
test-integration-race:
|
||||||
|
extends:
|
||||||
|
- test-integration
|
||||||
|
- .rules-branch-and-MR-manual
|
||||||
|
script:
|
||||||
|
- make test-integration-race | tee -a integration-race-job.log
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
paths:
|
||||||
|
- integration-race-job.log
|
||||||
|
|
||||||
|
|
||||||
|
test-integration-nightly:
|
||||||
|
extends:
|
||||||
|
- test-integration
|
||||||
|
- .rules-branch-manual-scheduled-and-test-branch-always
|
||||||
|
needs:
|
||||||
|
- test-integration
|
||||||
|
script:
|
||||||
|
- make test-integration-nightly | tee -a nightly-job.log
|
||||||
|
after_script:
|
||||||
|
- |
|
||||||
|
grep "Error: " nightly-job.log
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
paths:
|
||||||
|
- tests/result/feature-tests.xml
|
||||||
|
- nightly-job.log
|
||||||
|
|
||||||
|
test-coverage:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .image-linux-test
|
||||||
|
- .rules-branch-manual-scheduled-and-test-branch-always
|
||||||
|
script:
|
||||||
|
- ./utils/coverage.sh
|
||||||
|
coverage: '/total:.*\(statements\).*\d+\.\d+%/'
|
||||||
|
needs:
|
||||||
|
- test-linux
|
||||||
|
- test-windows
|
||||||
|
- test-darwin
|
||||||
|
- test-integration
|
||||||
|
- test-integration-nightly
|
||||||
|
tags:
|
||||||
|
- shared-small
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- coverage*
|
||||||
|
- coverage/**
|
||||||
|
when: 'always'
|
||||||
|
reports:
|
||||||
|
coverage_report:
|
||||||
|
coverage_format: cobertura
|
||||||
|
path: coverage.xml
|
||||||
|
|
||||||
|
go-vuln-check:
|
||||||
|
extends:
|
||||||
|
- .image-linux-test
|
||||||
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
|
stage: test
|
||||||
|
tags:
|
||||||
|
- shared-medium
|
||||||
|
script:
|
||||||
|
- ./utils/govulncheck.sh
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
paths:
|
||||||
|
- vulns*
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -19,11 +19,17 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/app"
|
"github.com/ProtonMail/proton-bridge/v3/internal/app"
|
||||||
"github.com/bradenaw/juniper/xslices"
|
"github.com/bradenaw/juniper/xslices"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -44,7 +50,72 @@ import (
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := app.New().Run(xslices.Filter(os.Args, func(arg string) bool { return !strings.Contains(arg, "-psn_") })); err != nil {
|
appErr := app.New().Run(xslices.Filter(os.Args, func(arg string) bool { return !strings.Contains(arg, "-psn_") }))
|
||||||
logrus.Fatal(err)
|
if appErr != nil {
|
||||||
|
_ = app.WithLocations(func(l *locations.Locations) error {
|
||||||
|
logsPath, err := l.ProvideLogsPath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the session ID if its specified
|
||||||
|
var sessionID logging.SessionID
|
||||||
|
if flagVal, found := getFlagValue(os.Args, app.FlagSessionID); found {
|
||||||
|
sessionID = logging.SessionID(flagVal)
|
||||||
|
} else {
|
||||||
|
sessionID = logging.NewSessionID()
|
||||||
|
}
|
||||||
|
|
||||||
|
closer, err := logging.Init(
|
||||||
|
logsPath,
|
||||||
|
sessionID,
|
||||||
|
logging.BridgeShortAppName,
|
||||||
|
logging.DefaultMaxLogFileSize,
|
||||||
|
logging.DefaultPruningSize,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = logging.Close(closer)
|
||||||
|
}()
|
||||||
|
|
||||||
|
logrus.
|
||||||
|
WithField("appName", constants.FullAppName).
|
||||||
|
WithField("version", constants.Version).
|
||||||
|
WithField("revision", constants.Revision).
|
||||||
|
WithField("tag", constants.Tag).
|
||||||
|
WithField("build", constants.BuildTime).
|
||||||
|
WithField("runtime", runtime.GOOS).
|
||||||
|
WithField("args", os.Args).
|
||||||
|
WithField("SentryID", sentry.GetProtectedHostname()).WithError(appErr).Error("Failed to initialize bridge")
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFlagValue - obtains the value of a specified tag
|
||||||
|
// The flag can be of the following form `-flag value`, `--flag value`, `-flag=value` or `--flags=value`.
|
||||||
|
func getFlagValue(argList []string, flag string) (string, bool) {
|
||||||
|
eqPrefix1 := "-" + flag + "="
|
||||||
|
eqPrefix2 := "--" + flag + "="
|
||||||
|
|
||||||
|
for i := 0; i < len(argList); i++ {
|
||||||
|
arg := argList[i]
|
||||||
|
if strings.HasPrefix(arg, eqPrefix1) {
|
||||||
|
val := strings.TrimPrefix(arg, eqPrefix1)
|
||||||
|
return val, len(val) > 0
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(arg, eqPrefix2) {
|
||||||
|
val := strings.TrimPrefix(arg, eqPrefix2)
|
||||||
|
return val, len(val) > 0
|
||||||
|
}
|
||||||
|
if (arg == "-"+flag || arg == "--"+flag) && i+1 < len(argList) {
|
||||||
|
return argList[i+1], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|||||||
47
cmd/Desktop-Bridge/main_test.go
Normal file
47
cmd/Desktop-Bridge/main_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetFlagValue(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
args []string
|
||||||
|
flag string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{[]string{"session-id", ""}, "session-id", ""},
|
||||||
|
{[]string{"-session-id", ""}, "session-id", ""},
|
||||||
|
{[]string{"--session-id", ""}, "session-id", ""},
|
||||||
|
{[]string{"session-id", "test"}, "session-id", ""},
|
||||||
|
{[]string{"-session-id", "test"}, "session-id", "test"},
|
||||||
|
{[]string{"--session-id", "test"}, "session-id", "test"},
|
||||||
|
{[]string{"session-id=test"}, "session-id", ""},
|
||||||
|
{[]string{"-session-id=test"}, "session-id", "test"},
|
||||||
|
{[]string{"--session-id=test"}, "session-id", "test"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
val, _ := getFlagValue(tt.args, tt.flag)
|
||||||
|
require.Equal(t, val, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -40,6 +40,7 @@ import (
|
|||||||
"github.com/elastic/go-sysinfo/types"
|
"github.com/elastic/go-sysinfo/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/sys/execabs"
|
"golang.org/x/sys/execabs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -53,9 +54,12 @@ const (
|
|||||||
FlagCLIShort = "c"
|
FlagCLIShort = "c"
|
||||||
FlagNonInteractive = "noninteractive"
|
FlagNonInteractive = "noninteractive"
|
||||||
FlagNonInteractiveShort = "n"
|
FlagNonInteractiveShort = "n"
|
||||||
FlagLauncher = "--launcher"
|
FlagLauncher = "launcher"
|
||||||
FlagWait = "--wait"
|
FlagWait = "wait"
|
||||||
FlagSessionID = "--session-id"
|
FlagSessionID = "session-id"
|
||||||
|
HyphenatedFlagLauncher = "--" + FlagLauncher
|
||||||
|
HyphenatedFlagWait = "--" + FlagWait
|
||||||
|
HyphenatedFlagSessionID = "--" + FlagSessionID
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() { //nolint:funlen
|
func main() { //nolint:funlen
|
||||||
@ -151,7 +155,7 @@ func main() { //nolint:funlen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := execabs.Command(exe, appendLauncherPath(launcher, append(args, FlagSessionID, string(sessionID)))...) //nolint:gosec
|
cmd := execabs.Command(exe, appendLauncherPath(launcher, appendOrModifySessionID(args, string(sessionID)))...) //nolint:gosec
|
||||||
|
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
@ -173,19 +177,14 @@ func main() { //nolint:funlen
|
|||||||
|
|
||||||
// appendLauncherPath add launcher path if missing.
|
// appendLauncherPath add launcher path if missing.
|
||||||
func appendLauncherPath(path string, args []string) []string {
|
func appendLauncherPath(path string, args []string) []string {
|
||||||
if !sliceContains(args, FlagLauncher) {
|
if !slices.Contains(args, HyphenatedFlagLauncher) {
|
||||||
res := append([]string{}, args...)
|
res := append([]string{}, args...)
|
||||||
res = append(res, FlagLauncher, path)
|
res = append(res, HyphenatedFlagLauncher, path)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
// sliceContains checks if a value is present in a list.
|
|
||||||
func sliceContains[T comparable](list []T, s T) bool {
|
|
||||||
return xslices.Any(list, func(arg T) bool { return arg == s })
|
|
||||||
}
|
|
||||||
|
|
||||||
// inCLIMode detect if CLI mode is asked.
|
// inCLIMode detect if CLI mode is asked.
|
||||||
func inCLIMode(args []string) bool {
|
func inCLIMode(args []string) bool {
|
||||||
return hasFlag(args, FlagCLI) || hasFlag(args, FlagCLIShort) || hasFlag(args, FlagNonInteractive) || hasFlag(args, FlagNonInteractiveShort)
|
return hasFlag(args, FlagCLI) || hasFlag(args, FlagCLIShort) || hasFlag(args, FlagNonInteractive) || hasFlag(args, FlagNonInteractiveShort)
|
||||||
@ -193,7 +192,12 @@ func inCLIMode(args []string) bool {
|
|||||||
|
|
||||||
// hasFlag checks if a flag is present in a list.
|
// hasFlag checks if a flag is present in a list.
|
||||||
func hasFlag(args []string, flag string) bool {
|
func hasFlag(args []string, flag string) bool {
|
||||||
return xslices.Any(args, func(arg string) bool { return (arg == "-"+flag) || (arg == "--"+flag) })
|
return flagIndex(args, flag) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// flagIndex returns the position of the first occurrence of a flag int args, or -1 if the flag is not present.
|
||||||
|
func flagIndex(args []string, flag string) int {
|
||||||
|
return slices.IndexFunc(args, func(arg string) bool { return (arg == "-"+flag) || (arg == "--"+flag) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// findAndStrip check if a value is present in s list and remove all occurrences of the value from this list.
|
// findAndStrip check if a value is present in s list and remove all occurrences of the value from this list.
|
||||||
@ -211,7 +215,7 @@ func findAndStripWait(args []string) ([]string, bool, []string) {
|
|||||||
hasFlag := false
|
hasFlag := false
|
||||||
values := make([]string, 0)
|
values := make([]string, 0)
|
||||||
for k, v := range res {
|
for k, v := range res {
|
||||||
if v != FlagWait {
|
if v != HyphenatedFlagWait {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if k+1 >= len(res) {
|
if k+1 >= len(res) {
|
||||||
@ -222,7 +226,7 @@ func findAndStripWait(args []string) ([]string, bool, []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hasFlag {
|
if hasFlag {
|
||||||
res, _ = findAndStrip(res, FlagWait)
|
res, _ = findAndStrip(res, HyphenatedFlagWait)
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
res, _ = findAndStrip(res, v)
|
res, _ = findAndStrip(res, v)
|
||||||
}
|
}
|
||||||
@ -230,6 +234,23 @@ func findAndStripWait(args []string) ([]string, bool, []string) {
|
|||||||
return res, hasFlag, values
|
return res, hasFlag, values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return args with the sessionID flag and value added or modified. The original slice is not modified.
|
||||||
|
func appendOrModifySessionID(args []string, sessionID string) []string {
|
||||||
|
index := flagIndex(args, FlagSessionID)
|
||||||
|
if index < 0 {
|
||||||
|
return append(args, HyphenatedFlagSessionID, sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if index == len(args)-1 {
|
||||||
|
return append(args, sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := slices.Clone(args)
|
||||||
|
res[index+1] = sessionID
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func getPathToUpdatedExecutable(
|
func getPathToUpdatedExecutable(
|
||||||
name string,
|
name string,
|
||||||
ver *versioner.Versioner,
|
ver *versioner.Versioner,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -20,61 +20,62 @@ package main
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bradenaw/juniper/xslices"
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSliceContains(t *testing.T) {
|
|
||||||
assert.True(t, sliceContains([]string{"a", "b", "c"}, "a"))
|
|
||||||
assert.True(t, sliceContains([]int{1, 2, 3}, 2))
|
|
||||||
assert.False(t, sliceContains([]string{"a", "b", "c"}, "A"))
|
|
||||||
assert.False(t, sliceContains([]int{1, 2, 3}, 4))
|
|
||||||
assert.False(t, sliceContains([]string{}, "a"))
|
|
||||||
assert.True(t, sliceContains([]string{"a", "a"}, "a"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAndStrip(t *testing.T) {
|
func TestFindAndStrip(t *testing.T) {
|
||||||
list := []string{"a", "b", "c", "c", "b", "c"}
|
list := []string{"a", "b", "c", "c", "b", "c"}
|
||||||
|
|
||||||
result, found := findAndStrip(list, "a")
|
result, found := findAndStrip(list, "a")
|
||||||
assert.True(t, found)
|
assert.True(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{"b", "c", "c", "b", "c"}))
|
assert.Equal(t, result, []string{"b", "c", "c", "b", "c"})
|
||||||
|
|
||||||
result, found = findAndStrip(list, "c")
|
result, found = findAndStrip(list, "c")
|
||||||
assert.True(t, found)
|
assert.True(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{"a", "b", "b"}))
|
assert.Equal(t, result, []string{"a", "b", "b"})
|
||||||
|
|
||||||
result, found = findAndStrip([]string{"c", "c", "c"}, "c")
|
result, found = findAndStrip([]string{"c", "c", "c"}, "c")
|
||||||
assert.True(t, found)
|
assert.True(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{}))
|
assert.Equal(t, result, []string{})
|
||||||
|
|
||||||
result, found = findAndStrip(list, "A")
|
result, found = findAndStrip(list, "A")
|
||||||
assert.False(t, found)
|
assert.False(t, found)
|
||||||
assert.True(t, xslices.Equal(result, list))
|
assert.Equal(t, result, list)
|
||||||
|
|
||||||
result, found = findAndStrip([]string{}, "a")
|
result, found = findAndStrip([]string{}, "a")
|
||||||
assert.False(t, found)
|
assert.False(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{}))
|
assert.Equal(t, result, []string{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindAndStripWait(t *testing.T) {
|
func TestFindAndStripWait(t *testing.T) {
|
||||||
result, found, values := findAndStripWait([]string{"a", "b", "c"})
|
result, found, values := findAndStripWait([]string{"a", "b", "c"})
|
||||||
assert.False(t, found)
|
assert.False(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{"a", "b", "c"}))
|
assert.Equal(t, result, []string{"a", "b", "c"})
|
||||||
assert.True(t, xslices.Equal(values, []string{}))
|
assert.Equal(t, values, []string{})
|
||||||
|
|
||||||
result, found, values = findAndStripWait([]string{"a", "--wait", "b"})
|
result, found, values = findAndStripWait([]string{"a", "--wait", "b"})
|
||||||
assert.True(t, found)
|
assert.True(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{"a"}))
|
assert.Equal(t, result, []string{"a"})
|
||||||
assert.True(t, xslices.Equal(values, []string{"b"}))
|
assert.Equal(t, values, []string{"b"})
|
||||||
|
|
||||||
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c"})
|
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c"})
|
||||||
assert.True(t, found)
|
assert.True(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{"a"}))
|
assert.Equal(t, result, []string{"a"})
|
||||||
assert.True(t, xslices.Equal(values, []string{"b", "c"}))
|
assert.Equal(t, values, []string{"b", "c"})
|
||||||
|
|
||||||
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c", "--wait", "d"})
|
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c", "--wait", "d"})
|
||||||
assert.True(t, found)
|
assert.True(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{"a"}))
|
assert.Equal(t, result, []string{"a"})
|
||||||
assert.True(t, xslices.Equal(values, []string{"b", "c", "d"}))
|
assert.Equal(t, values, []string{"b", "c", "d"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendOrModifySessionID(t *testing.T) {
|
||||||
|
sessionID := string(logging.NewSessionID())
|
||||||
|
assert.Equal(t, appendOrModifySessionID(nil, sessionID), []string{"--session-id", sessionID})
|
||||||
|
assert.Equal(t, appendOrModifySessionID([]string{}, sessionID), []string{"--session-id", sessionID})
|
||||||
|
assert.Equal(t, appendOrModifySessionID([]string{"--cli"}, sessionID), []string{"--cli", "--session-id", sessionID})
|
||||||
|
assert.Equal(t, appendOrModifySessionID([]string{"--cli", "--session-id"}, sessionID), []string{"--cli", "--session-id", sessionID})
|
||||||
|
assert.Equal(t, appendOrModifySessionID([]string{"--cli", "--session-id"}, sessionID), []string{"--cli", "--session-id", sessionID})
|
||||||
|
assert.Equal(t, appendOrModifySessionID([]string{"--session-id", "<oldID>", "--cli"}, sessionID), []string{"--session-id", sessionID, "--cli"})
|
||||||
}
|
}
|
||||||
|
|||||||
135
doc/bridge.md
135
doc/bridge.md
@ -1,135 +0,0 @@
|
|||||||
# Bridge
|
|
||||||
|
|
||||||
## Main blocks
|
|
||||||
|
|
||||||
This is basic overview of the main bridge blocks.
|
|
||||||
|
|
||||||
Note connection between IMAP/SMTP and PMAPI. IMAP and SMTP packages are in the queue to be refactored
|
|
||||||
and we would like to try to have functionality in bridge core or bridge utilities (such as messages)
|
|
||||||
than direct usage of PMAPI from IMAP or SMTP. Also database (BoltDB) should be moved to bridge core.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
S[Server]
|
|
||||||
C[Client]
|
|
||||||
U[User]
|
|
||||||
|
|
||||||
subgraph "Bridge app"
|
|
||||||
Core[Bridge core]
|
|
||||||
API[PMAPI]
|
|
||||||
Store
|
|
||||||
DB[BoltDB]
|
|
||||||
Frontend["Qt / CLI"]
|
|
||||||
IMAP
|
|
||||||
SMTP
|
|
||||||
|
|
||||||
IMAP --> Store
|
|
||||||
IMAP --> Core
|
|
||||||
SMTP --> Core
|
|
||||||
SMTP --> API
|
|
||||||
Core --> API
|
|
||||||
Core --> Store
|
|
||||||
Store --> API
|
|
||||||
Store --> DB
|
|
||||||
Frontend --> Core
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
C --> IMAP
|
|
||||||
C --> SMTP
|
|
||||||
U --> Frontend
|
|
||||||
API --> S
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code structure
|
|
||||||
|
|
||||||
More detailed graph of main types used in Bridge app and connection between them. Here is already
|
|
||||||
communication to PMAPI only from bridge core which is not true, yet. IMAP and SMTP are still calling
|
|
||||||
PMAPI directly.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
|
|
||||||
C["Client (e.g. Thunderbird)"]
|
|
||||||
PM[Proton Mail Server]
|
|
||||||
|
|
||||||
subgraph "Bridge app"
|
|
||||||
subgraph "Bridge core"
|
|
||||||
B[Bridge]
|
|
||||||
U[User]
|
|
||||||
|
|
||||||
B --> U
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Store
|
|
||||||
StoreU[Store User]
|
|
||||||
StoreA[Address]
|
|
||||||
StoreM[Mailbox]
|
|
||||||
|
|
||||||
StoreU --> StoreA
|
|
||||||
StoreA --> StoreM
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Credentials
|
|
||||||
CredStore[Store]
|
|
||||||
Creds[Credentials]
|
|
||||||
|
|
||||||
CredStore --> Creds
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Frontend
|
|
||||||
CLI
|
|
||||||
Qt
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph IMAP
|
|
||||||
IB[IMAP backend]
|
|
||||||
IA[IMAP address]
|
|
||||||
IM[IMAP mailbox]
|
|
||||||
|
|
||||||
IB --> B
|
|
||||||
IB --> IA
|
|
||||||
IA --> IM
|
|
||||||
IA --> U
|
|
||||||
IA --> StoreA
|
|
||||||
IM --> StoreM
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph SMTP
|
|
||||||
SB[SMTP backend]
|
|
||||||
SS[SMTP session]
|
|
||||||
|
|
||||||
SB --> B
|
|
||||||
SB --> SS
|
|
||||||
SS --> U
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph PMAPI
|
|
||||||
AC[Client]
|
|
||||||
end
|
|
||||||
|
|
||||||
C --> IB
|
|
||||||
C --> SB
|
|
||||||
|
|
||||||
CLI --> B
|
|
||||||
Qt --> B
|
|
||||||
|
|
||||||
U --> CredStore
|
|
||||||
U --> Creds
|
|
||||||
|
|
||||||
U --> StoreU
|
|
||||||
|
|
||||||
StoreU --> AC
|
|
||||||
StoreA --> AC
|
|
||||||
StoreM --> AC
|
|
||||||
|
|
||||||
B --> AC
|
|
||||||
U --> AC
|
|
||||||
|
|
||||||
AC --> PM
|
|
||||||
```
|
|
||||||
|
|
||||||
## How to debug
|
|
||||||
|
|
||||||
Run `make run-debug` which starts [Delve](https://github.com/go-delve/delve).
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
# Communication
|
|
||||||
|
|
||||||
## First login and sync
|
|
||||||
|
|
||||||
When user logs in to the bridge for the first time, immediately starts the first sync.
|
|
||||||
First sync downloads all headers of all e-mails and creates database to have proper UIDs
|
|
||||||
and indexes for IMAP. See [database](database.md) for more information.
|
|
||||||
|
|
||||||
By default, whenever it's possible, sync downloads only all e-mails maiblox which already
|
|
||||||
have list of labels so we can construct all mailboxes (inbox, sent, trash, custom folders
|
|
||||||
and labels) without need to download each e-mail headers many times.
|
|
||||||
|
|
||||||
Note that we need to download also bodies to calculate size of the e-mail and set proper
|
|
||||||
content type (clients uses content type for guess if e-mail contains attachment)--but only
|
|
||||||
body, not attachment. Also it's downloaded only for the first time. After that we store
|
|
||||||
those information in our database so next time we only sync headers, labels and so on.
|
|
||||||
|
|
||||||
First sync takes some time. List of 150 messages takes about second and then we need to
|
|
||||||
download bodies for each message. We still need to do some optimalizations. Anyway, if
|
|
||||||
user has reasonable amount of e-mails, there is good chance user will see e-mails in the
|
|
||||||
client right after adding account.
|
|
||||||
|
|
||||||
When account is added to client, client start the sync. This sync will ask Bridge app
|
|
||||||
for all headers (done quickly) and then starts to download all bodies and attachment.
|
|
||||||
Unfortunately for some e-mail more than once if the same e-mail is in more mailboxes
|
|
||||||
(e.g. inbox and all mail)--there is no way to tell over IMAP it's the same message.
|
|
||||||
|
|
||||||
After successful login of client to IMAP, Bridge starts event loop. That periodicly ask
|
|
||||||
servers (each 30 seconds) for new updates (new message, keys, …).
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant S as Server
|
|
||||||
participant B as Bridge
|
|
||||||
participant C as Client
|
|
||||||
|
|
||||||
Note right of B: Set up PM account<br/>by user
|
|
||||||
|
|
||||||
loop First sync
|
|
||||||
B ->> S: Fetch body and attachments
|
|
||||||
Note right of B: Build local database<br/>(e-mail UIDs)
|
|
||||||
end
|
|
||||||
|
|
||||||
Note right of C: Set up IMAP/SMTP<br/>by user
|
|
||||||
|
|
||||||
C ->> B: IMAP login
|
|
||||||
B ->> S: Authenticate user
|
|
||||||
Note right of B: Create IMAP user
|
|
||||||
|
|
||||||
loop Event loop, every 30 sec
|
|
||||||
B ->> S: Fetch e-mail headers
|
|
||||||
B ->> C: Send IMAP IDLE response
|
|
||||||
end
|
|
||||||
|
|
||||||
C ->> B: IMAP LIST directories
|
|
||||||
|
|
||||||
loop Client sync
|
|
||||||
C ->> B: IMAP SELECT directory
|
|
||||||
C ->> B: IMAP SEARCH e-mails UIDs
|
|
||||||
C ->> B: IMAP FETCH of e-mail UID
|
|
||||||
B ->> S: Fetch body and attachments
|
|
||||||
Note right of B: Decrypt message<br/>and attachment
|
|
||||||
B ->> C: IMAP response
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## IMAP IDLE extension
|
|
||||||
|
|
||||||
IMAP IDLE is extension, it has to be supported by both client and server. IMAP server (in our case
|
|
||||||
the bridge) supports it so clients can use it. It works by issuing `IDLE` command by the client and
|
|
||||||
keeps the connection open. When the server has some update, server (the bridge) will respond to that
|
|
||||||
by `EXISTS` (new message), `APPEND` (imported message), `EXPUNGE` (deleted message) or `MOVE` response.
|
|
||||||
|
|
||||||
Even when there is connection with IDLE open, server can mark the client as inactive. Therefore,
|
|
||||||
it's recommended the client should reissue the connection after each 29 minutes. This is not the
|
|
||||||
real push and can fail!
|
|
||||||
|
|
||||||
Our event loop is also simple pull and it will trigger IMAP IDLE when we get some new update from
|
|
||||||
the server. Would be good to have push from the server, but we need to wait for the support on API.
|
|
||||||
|
|
||||||
RFC: https://tools.ietf.org/html/rfc2177
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant S as Server
|
|
||||||
participant B as Bridge
|
|
||||||
participant C as Client
|
|
||||||
|
|
||||||
C ->> B: IMAP IDLE
|
|
||||||
|
|
||||||
loop Every 30 seconds
|
|
||||||
S ->> B: Checking events
|
|
||||||
B ->> C: IMAP response
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Sending e-mails
|
|
||||||
|
|
||||||
E-mail are sent over standard SMTP protocol. Our bridge takes the message, encrypts and sent it
|
|
||||||
further to our server which will then send the message to its final destination. The important
|
|
||||||
and tricky part is encryption. See [encryption](encryption.md) or [PMEL document](https://docs.google.com/document/d/1lEBkG0DC5FOWlumInKtu4a9Cc1Eszp48ZhFy9UpPQso/edit)
|
|
||||||
for more information.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant S as Server
|
|
||||||
participant B as Bridge
|
|
||||||
participant C as Client
|
|
||||||
|
|
||||||
C ->> B: SMTP send e-mail
|
|
||||||
Note right of B: Encrypt messages
|
|
||||||
B ->> S: Send encrypted e-mail
|
|
||||||
B ->> C: Respond OK
|
|
||||||
```
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
# Database
|
|
||||||
|
|
||||||
Bridge needs to have a small database to pair our IDs with IMAP UIDs and indexes. IMAP protocol
|
|
||||||
requires every message to have an unique UID in mailbox. In this context, mailbox is not an account,
|
|
||||||
but a folder or label. This means that one message can have more UIDs, one for each mailbox (folder),
|
|
||||||
and that two messages can have the same UID, but each for different mailbox (folder).
|
|
||||||
|
|
||||||
IMAP index is just an index. Look at it like to an array: `["UID1", "UID2", "UID3"]`. We can access
|
|
||||||
message by UID or index; for example index 2 and UID `UID2`. When this message is deleted, we need
|
|
||||||
to re-index all following messages. The array will look now like `["UID1", "UID3"]` and the last
|
|
||||||
message can be accessed by index 2 or UID `UID3`.
|
|
||||||
|
|
||||||
See RFCs for more information:
|
|
||||||
|
|
||||||
* https://tools.ietf.org/html/rfc822
|
|
||||||
* https://tools.ietf.org/html/rfc3501
|
|
||||||
|
|
||||||
Our database is currently built on BBolt and have those buckets (key-value storage):
|
|
||||||
|
|
||||||
* Message metadata bucket:
|
|
||||||
|
|
||||||
* `[metadataBucket][API_ID] -> pmapi.Message{subject, from, to, size, other headers...}` (without body or attachment)
|
|
||||||
|
|
||||||
* Mapping buckets
|
|
||||||
|
|
||||||
* `[mailboxesBucket][addressID-mailboxID][api_ids][API_ID] -> UID`
|
|
||||||
* `[mailboxesBucket][addressID-mailboxID][imap_ids][UID] -> API_ID`
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
# Encryption
|
|
||||||
|
|
||||||
Encryption is done in PMAPI, bridge utils and bridge itself. The best would be to keep encryption
|
|
||||||
in PMAPI and bridge utils (in package such as messages). All packages are using our high-level
|
|
||||||
GopenPGP library on top of OpenPGP.
|
|
||||||
|
|
||||||
## `gopenpgp.KeyRing`
|
|
||||||
|
|
||||||
We use one `KeyRing` per address. Our usage then contains all keys for specific address. Primary
|
|
||||||
key is always on the first position, then there old ones to be able to decrypt last e-mail.
|
|
||||||
OpenPGP encrypts given message with all available keys, so we need to first get first (primary)
|
|
||||||
key for encryption to have message encrypted only once with primary key.
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# Bridge Documentation
|
|
||||||
|
|
||||||
Documentation pages in order to read for a novice:
|
|
||||||
|
|
||||||
* [Bridge code](bridge.md)
|
|
||||||
* [Internal Bridge database](database.md)
|
|
||||||
* [Communication between Bridge, Client and Server](communication.md)
|
|
||||||
* [Encryption](encryption.md)
|
|
||||||
|
|
||||||
103
doc/updates.md
103
doc/updates.md
@ -1,103 +0,0 @@
|
|||||||
# Update mechanism of Bridge
|
|
||||||
|
|
||||||
There are multiple options how to change version of application:
|
|
||||||
* Automatic in-app update
|
|
||||||
* Manual in-app update
|
|
||||||
* Manual install
|
|
||||||
|
|
||||||
In-app update ends with restarting bridge into new version. Automatic in-app
|
|
||||||
update is downloading, verifying and installing the new version immediately
|
|
||||||
without user confirmation. For manual in-app update user needs to confirm first.
|
|
||||||
Update is done from special update file published on website.
|
|
||||||
|
|
||||||
The manual installation requires user to download, verify and install manually
|
|
||||||
using installer for given OS.
|
|
||||||
|
|
||||||
The bridge is installed and executed differently for given OS:
|
|
||||||
|
|
||||||
* Windows and Linux apps are using launcher mechanism:
|
|
||||||
* There is system protected installation path which is created on first
|
|
||||||
install. It contains bridge exe and launcher exe. When users starts
|
|
||||||
bridge the launcher is executed first. It will check update path compare
|
|
||||||
version with installed one. The newer version then is then executed.
|
|
||||||
* Update mechanism means to replace files in update folder which is located
|
|
||||||
in user space.
|
|
||||||
|
|
||||||
* macOS app does not use launcher
|
|
||||||
* No launcher, only one executable
|
|
||||||
* In-App update replaces the bridge files in installation path directly
|
|
||||||
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
subgraph Frontend
|
|
||||||
U[User requests<br>version check]
|
|
||||||
ManIns((Notify user about<br>manual install<br>is needed))
|
|
||||||
R((Notify user<br>about restart))
|
|
||||||
ManUp((Notify user about<br>manual update))
|
|
||||||
NF((Notify user about<br>force update))
|
|
||||||
|
|
||||||
ManUp -->|Install| InstFront[Install]
|
|
||||||
InstFront -->|Ok| R
|
|
||||||
InstFront -->|Error| ManIns
|
|
||||||
|
|
||||||
U --> CheckFront[Check online]
|
|
||||||
CheckFront -->|Ok| IAFront{Is new version<br>and applicable?}
|
|
||||||
CheckFront -->|Error| ManIns
|
|
||||||
|
|
||||||
IAFront -->|No| Latest((Notify user<br>has latest version))
|
|
||||||
IAFront -->|Yes| CanInstall{Can update?}
|
|
||||||
CanInstall -->|No| ManIns
|
|
||||||
CanInstall -->|Yes| NotifOrInstall{Is automatic<br>update enabled?}
|
|
||||||
NotifOrInstall -->|Manual| ManUp
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
subgraph Backend
|
|
||||||
W[Wait for next check]
|
|
||||||
|
|
||||||
W --> Check[Check online]
|
|
||||||
|
|
||||||
Check --> NV{Has new<br>version?}
|
|
||||||
Check -->|Error| W
|
|
||||||
NV -->|No new version| W
|
|
||||||
IA{Is install<br>applicable?}
|
|
||||||
NV -->|New version<br>available| IA
|
|
||||||
IA -->|Local rollout<br>not enough| W
|
|
||||||
IA -->|Yes| AU{Is automatic\nupdate enabled?}
|
|
||||||
|
|
||||||
AU -->|Yes| CanUp{Can update?}
|
|
||||||
CanUp -->|No| ManIns
|
|
||||||
|
|
||||||
CanUp -->|Yes| Ins[Install]
|
|
||||||
Ins -->|Error| ManIns
|
|
||||||
Ins -->|Ok| R
|
|
||||||
|
|
||||||
AU -->|No| ManUp
|
|
||||||
ManUp -->|Ignore| W
|
|
||||||
|
|
||||||
|
|
||||||
F[Force update]
|
|
||||||
F --> NF
|
|
||||||
end
|
|
||||||
|
|
||||||
ManIns --> Web[Open web page]
|
|
||||||
NF --> Web
|
|
||||||
ManUp --> Web
|
|
||||||
R --> Re[Restart]
|
|
||||||
NF --> Q[Quit bridge]
|
|
||||||
NotifOrInstall -->|Automatic| W
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
The non-trivial is to combine the update with setting change:
|
|
||||||
* turn off/on automatic in-app updates
|
|
||||||
* change from stable to beta or back
|
|
||||||
|
|
||||||
_TODO fill flow chart details_
|
|
||||||
|
|
||||||
|
|
||||||
We are not support downgrade functionality. Only some circumstances can lead to
|
|
||||||
downgrading the app version.
|
|
||||||
|
|
||||||
_TODO fill flow chart details_
|
|
||||||
2
extern/vcpkg
vendored
2
extern/vcpkg
vendored
Submodule extern/vcpkg updated: d4d39d71b3...fba75d0906
50
go.mod
50
go.mod
@ -1,22 +1,24 @@
|
|||||||
module github.com/ProtonMail/proton-bridge/v3
|
module github.com/ProtonMail/proton-bridge/v3
|
||||||
|
|
||||||
go 1.20
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.21.9
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||||
github.com/Masterminds/semver/v3 v3.2.0
|
github.com/Masterminds/semver/v3 v3.2.0
|
||||||
github.com/ProtonMail/gluon v0.17.1-0.20230911134257-5eb2eeebbef5
|
github.com/ProtonMail/gluon v0.17.1-0.20240514133734-79cdd0fec41c
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20230925123025-331ad8e6d5ee
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240829112804-d663a2ef90c2
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton
|
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton
|
||||||
github.com/PuerkitoBio/goquery v1.8.1
|
github.com/PuerkitoBio/goquery v1.8.1
|
||||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||||
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37
|
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37
|
||||||
github.com/bradenaw/juniper v0.12.0
|
github.com/bradenaw/juniper v0.12.0
|
||||||
github.com/cucumber/godog v0.12.5
|
github.com/cucumber/godog v0.12.5
|
||||||
github.com/cucumber/messages-go/v16 v16.0.1
|
github.com/cucumber/messages-go/v16 v16.0.1
|
||||||
github.com/docker/docker-credential-helpers v0.6.3
|
github.com/docker/docker-credential-helpers v0.8.1
|
||||||
github.com/elastic/go-sysinfo v1.8.1
|
github.com/elastic/go-sysinfo v1.11.2-0.20231129083954-35e55cd2a542
|
||||||
github.com/emersion/go-imap v1.2.1
|
github.com/emersion/go-imap v1.2.1
|
||||||
github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
||||||
github.com/emersion/go-message v0.16.0
|
github.com/emersion/go-message v0.16.0
|
||||||
@ -32,28 +34,29 @@ require (
|
|||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/hashicorp/go-multierror v1.1.1
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba
|
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20220711133428-7de61946b173
|
||||||
github.com/keybase/go-keychain v0.0.0
|
github.com/keybase/go-keychain v0.0.0
|
||||||
github.com/miekg/dns v1.1.50
|
github.com/miekg/dns v1.1.50
|
||||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pkg/profile v1.7.0
|
github.com/pkg/profile v1.7.0
|
||||||
github.com/sirupsen/logrus v1.9.2
|
github.com/sirupsen/logrus v1.9.2
|
||||||
github.com/stretchr/testify v1.8.3
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/urfave/cli/v2 v2.24.4
|
github.com/urfave/cli/v2 v2.24.4
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5
|
github.com/vmihailenco/msgpack/v5 v5.3.5
|
||||||
go.uber.org/goleak v1.2.1
|
go.uber.org/goleak v1.2.1
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||||
golang.org/x/net v0.10.0
|
golang.org/x/net v0.24.0
|
||||||
golang.org/x/sys v0.8.0
|
golang.org/x/sys v0.19.0
|
||||||
golang.org/x/text v0.9.0
|
golang.org/x/text v0.14.0
|
||||||
google.golang.org/grpc v1.53.0
|
google.golang.org/grpc v1.56.3
|
||||||
google.golang.org/protobuf v1.30.0
|
google.golang.org/protobuf v1.33.0
|
||||||
howett.net/plist v1.0.0
|
howett.net/plist v1.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 // indirect
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
||||||
github.com/ProtonMail/go-srp v0.0.7 // indirect
|
github.com/ProtonMail/go-srp v0.0.7 // indirect
|
||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
||||||
@ -61,11 +64,11 @@ require (
|
|||||||
github.com/bytedance/sonic v1.9.1 // indirect
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
github.com/chzyer/test v1.0.0 // indirect
|
github.com/chzyer/test v1.0.0 // indirect
|
||||||
github.com/cloudflare/circl v1.3.3 // indirect
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/cronokirby/saferith v0.33.0 // indirect
|
github.com/cronokirby/saferith v0.33.0 // indirect
|
||||||
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
|
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
|
||||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
github.com/danieljoos/wincred v1.2.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/elastic/go-windows v1.0.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-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
|
||||||
@ -79,7 +82,7 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/gofrs/uuid v4.3.0+incompatible // indirect
|
github.com/gofrs/uuid v4.3.0+incompatible // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||||
@ -92,14 +95,14 @@ require (
|
|||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.2 // indirect
|
github.com/rivo/uniseg v0.4.2 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
@ -110,16 +113,17 @@ require (
|
|||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
|
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.9.0 // indirect
|
golang.org/x/crypto v0.22.0 // indirect
|
||||||
golang.org/x/mod v0.8.0 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/sync v0.2.0 // indirect
|
golang.org/x/sync v0.3.0 // indirect
|
||||||
golang.org/x/tools v0.6.0 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148 // indirect
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.1.0
|
|
||||||
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7
|
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7
|
||||||
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20230517073537-fc1740a83768
|
github.com/emersion/go-smtp => github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865
|
||||||
|
github.com/go-resty/resty/v2 => github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a
|
||||||
|
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77
|
||||||
)
|
)
|
||||||
|
|||||||
179
go.sum
179
go.sum
@ -11,42 +11,64 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl
|
|||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
fyne.io/fyne v1.4.2/go.mod h1:xL4c3WmpE/Tvz5CEm5vqsaizU/EeOCm9DYlL2GtTSiM=
|
||||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 h1:l6surSnJ3RP4qA1qmKJ+hQn3UjytosdoG27WGjrDlVs=
|
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 h1:l6surSnJ3RP4qA1qmKJ+hQn3UjytosdoG27WGjrDlVs=
|
||||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557/go.mod h1:sTrmvD/TxuypdOERsDOS7SndZg0rzzcCi1b6wQMXUYM=
|
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557/go.mod h1:sTrmvD/TxuypdOERsDOS7SndZg0rzzcCi1b6wQMXUYM=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||||
|
github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a h1:eQO/GF/+H8/9udc9QAgieFr+jr1tjXlJo35RAhsUbWY=
|
||||||
|
github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||||
|
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||||
|
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
github.com/ProtonMail/gluon v0.17.1-0.20240514133734-79cdd0fec41c h1:P3SvCACt13Zqdj0IRDB4bgwqI68+oMB2j0uVuPQyoTw=
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
github.com/ProtonMail/gluon v0.17.1-0.20240514133734-79cdd0fec41c/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||||
github.com/ProtonMail/gluon v0.17.1-0.20230911134257-5eb2eeebbef5 h1:O4BusNL870VgVVDSUX2Oaz8A/fNtJhakUKwx0YBIdn8=
|
|
||||||
github.com/ProtonMail/gluon v0.17.1-0.20230911134257-5eb2eeebbef5/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo=
|
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230322105811-d73448b7e800/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 h1:bdoKdh0f66/lrgVfYlxw0aqISY/KOqXmFJyGt7rGmnc=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek=
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
|
||||||
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/DyZ/qGfMT9htAT7HxqIEbZHsatsx+m8AoV6fc=
|
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/DyZ/qGfMT9htAT7HxqIEbZHsatsx+m8AoV6fc=
|
||||||
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
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 h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
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.20230925123025-331ad8e6d5ee h1:CzFXOiflEZZqT3HQqj2I5AkIprRbc/c6/lToPdEKzxM=
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240612082117-0f92424eed80 h1:cP4+6RFn9vVgYnoDwxBU4EtIAZA+eM4rzOaSZNqZ1xg=
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20230925123025-331ad8e6d5ee/go.mod h1:Y3ea3i1UbqHz5vq43odmAAd6lmR4nx0ZIQ32tqMfxTY=
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240612082117-0f92424eed80/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240808145610-88df257767f6 h1:nERxOYS4ndSgWEr834YYkb1j0bZK/dJAmhoyYB1MtNY=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240808145610-88df257767f6/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240819131705-149e50199c5b h1:zifGh4LS5HwQIaVCccSe5/oJGTOjFeVObMRl3QJoJ3k=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240819131705-149e50199c5b/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240821081056-dd607af0f917 h1:Ma6PfXFDuw7rYYq28FXNW6ubhYquRUmBuLyZrjJWHUE=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240821081056-dd607af0f917/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240822150235-7a6190889179 h1:6Xo0iRYa4GBgZ2HA+IR3KdqiML8Z10h2F9TYe+9n1+M=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240822150235-7a6190889179/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827084449-71096377c391 h1:PW6bE+mhsfAx4+wDCCNjhFrCNiiuMjY6j7RwqRUdPKI=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827084449-71096377c391/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827122236-ca6bb6449bba h1:QtDxgIbgPqRQg7VT+nIUJlaOyNFAoGyg59oW3Hji/0A=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827122236-ca6bb6449bba/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827132526-849231fc34a1 h1:gATlMoj4raG32WyGGh8SpipoQeR2AlU7g+8NAMicTcw=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240827132526-849231fc34a1/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240829112804-d663a2ef90c2 h1:yx0iejqB5c21HIN5jn9IsbyzUns0dPUUaGfyUHF3TmQ=
|
||||||
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240829112804-d663a2ef90c2/go.mod h1:3A0cpdo0BIenIPjTG6u8EbzJ8uuJy7rVvM/NaynjCKA=
|
||||||
|
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
|
||||||
|
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||||
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
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/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton h1:YS6M20yvjCJPR1r4ADW5TPn6rahs4iAyZaACei86bEc=
|
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton h1:8tqHYM6IGsdEc6Vxf1TWiwpHNj8yIEQNACPhxsDagrk=
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton/go.mod h1:S1lYsaGHykYpxxh2SnJL6ypcAlANKj5NRSY6HxKryKQ=
|
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton/go.mod h1:omVkSsfPAhmptzPF/piMXb16wKIWUvVhZbVW7sJKh0A=
|
||||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||||
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
|
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
|
||||||
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
|
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
|
||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
|
||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
|
||||||
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37 h1:28uU3TtuvQ6KRndxg9TrC868jBWmSKgh0GTXkACCXmA=
|
github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37 h1:28uU3TtuvQ6KRndxg9TrC868jBWmSKgh0GTXkACCXmA=
|
||||||
@ -64,6 +86,7 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
|
|||||||
github.com/bradenaw/juniper v0.12.0 h1:Q/7icpPQD1nH/La5DobQfNEtwyrBSiSu47jOQx7lJEM=
|
github.com/bradenaw/juniper v0.12.0 h1:Q/7icpPQD1nH/La5DobQfNEtwyrBSiSu47jOQx7lJEM=
|
||||||
github.com/bradenaw/juniper v0.12.0/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI=
|
github.com/bradenaw/juniper v0.12.0/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI=
|
||||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
|
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
@ -80,8 +103,9 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
|||||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
|
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||||
|
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
@ -99,18 +123,27 @@ github.com/cucumber/godog v0.12.5/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6T
|
|||||||
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
||||||
github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
|
github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
|
||||||
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
||||||
github.com/cuthix/go-keychain v0.0.0-20230517073537-fc1740a83768 h1:Jrcoxtrk4qpuzKIYPlEkjIK0M+bABs0oW2QzrOuwlzk=
|
github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77 h1:sdB/yJMbubPQothFl6KYCOrMBRgy0pZbBXIWoJqSFLo=
|
||||||
github.com/cuthix/go-keychain v0.0.0-20230517073537-fc1740a83768/go.mod h1:ZoZU1fnBy3mOLWr3Pg+Y2+nTKtu6ypDte2kZg9HvSwY=
|
github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77/go.mod h1:ZoZU1fnBy3mOLWr3Pg+Y2+nTKtu6ypDte2kZg9HvSwY=
|
||||||
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs=
|
||||||
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps=
|
||||||
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/elastic/go-sysinfo v1.8.1 h1:4Yhj+HdV6WjbCRgGdZpPJ8lZQlXZLKDAeIkmQ/VRvi4=
|
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||||
github.com/elastic/go-sysinfo v1.8.1/go.mod h1:JfllUnzoQV/JRYymbH3dO1yggI3mV2oTKSXsDHM+uIM=
|
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
|
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||||
|
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
|
||||||
|
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||||
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/elastic/go-sysinfo v1.11.2-0.20231129083954-35e55cd2a542 h1:IFTm6NBbfSgZCaeEzorQhH4T7ZERl4j+1u7oXWzmJcM=
|
||||||
|
github.com/elastic/go-sysinfo v1.11.2-0.20231129083954-35e55cd2a542/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ=
|
||||||
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
|
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
|
||||||
github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
|
github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
|
||||||
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
||||||
@ -120,8 +153,6 @@ github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:sPwp
|
|||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y=
|
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y=
|
||||||
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-smtp v0.15.1-0.20221021114529-49b17434419d h1:hFRM6zCBSc+Xa0rBOqSlG6Qe9dKC/2vLhGAuZlWxTsc=
|
|
||||||
github.com/emersion/go-smtp v0.15.1-0.20221021114529-49b17434419d/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 h1:hQ1wTMaKcGfobYRT88RM8NFNyX+IQHvagkm/tqViU98=
|
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3 h1:hQ1wTMaKcGfobYRT88RM8NFNyX+IQHvagkm/tqViU98=
|
||||||
@ -134,6 +165,9 @@ github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNu
|
|||||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
|
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
|
||||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
|
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/fyne-io/mobile v0.1.2-0.20201127155338-06aeb98410cc/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
|
||||||
|
github.com/fyne-io/mobile v0.1.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
github.com/getsentry/sentry-go v0.15.0 h1:CP9bmA7pralrVUedYZsmIHWpq/pBtXTSew7xvVpfLaA=
|
github.com/getsentry/sentry-go v0.15.0 h1:CP9bmA7pralrVUedYZsmIHWpq/pBtXTSew7xvVpfLaA=
|
||||||
@ -144,29 +178,35 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
|
|||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
|
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
|
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
|
||||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc=
|
github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc=
|
||||||
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
@ -178,8 +218,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
|||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
@ -240,12 +280,16 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
|||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||||
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba h1:QFQpJdgbON7I0jr2hYW7Bs+XV0qjc3d5tZoDnRFnqTg=
|
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba h1:QFQpJdgbON7I0jr2hYW7Bs+XV0qjc3d5tZoDnRFnqTg=
|
||||||
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20220711133428-7de61946b173 h1:jOONCXyzHWM+ukp+weX77o//U3pMeOj62CNxChJLxIU=
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20220711133428-7de61946b173/go.mod h1:uO/uctjf8AcWhNfp5Ili6oPtyFrAoQXEtVY3N798VkQ=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
|
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
|
||||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
|
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
|
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
@ -263,11 +307,14 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
|
|||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
|
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
@ -282,8 +329,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
|||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||||
@ -303,9 +350,16 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
|||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.1.1/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||||
|
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||||
@ -315,6 +369,7 @@ github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNc
|
|||||||
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
|
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
|
||||||
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
|
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@ -332,8 +387,8 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q
|
|||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||||
@ -341,6 +396,7 @@ github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
|
|||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
@ -364,6 +420,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@ -380,8 +438,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
@ -397,6 +456,7 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV
|
|||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
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=
|
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 h1:DxppxFKRqJ8WD6oJ3+ZXKDY0iMONQDl5UTg2aTyHh8k=
|
||||||
@ -419,9 +479,11 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||||
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -431,6 +493,7 @@ golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERs
|
|||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@ -442,6 +505,7 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
|
|||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||||
@ -460,18 +524,22 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -480,11 +548,12 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -499,44 +568,60 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@ -550,11 +635,13 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn
|
|||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
@ -582,20 +669,21 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
|||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148 h1:muK+gVBJBfFb4SejshDBlN2/UgxCCOKH9Y34ljqEGOc=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||||
google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
||||||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
@ -607,6 +695,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -26,6 +26,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/ProtonMail/gluon/async"
|
"github.com/ProtonMail/gluon/async"
|
||||||
@ -41,7 +42,9 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/pkg/restarter"
|
"github.com/ProtonMail/proton-bridge/v3/pkg/restarter"
|
||||||
|
"github.com/elastic/go-sysinfo"
|
||||||
"github.com/pkg/profile"
|
"github.com/pkg/profile"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -76,11 +79,13 @@ const (
|
|||||||
|
|
||||||
// Hidden flags.
|
// Hidden flags.
|
||||||
const (
|
const (
|
||||||
flagLauncher = "launcher"
|
flagLauncher = "launcher"
|
||||||
flagNoWindow = "no-window"
|
flagNoWindow = "no-window"
|
||||||
flagParentPID = "parent-pid"
|
flagParentPID = "parent-pid"
|
||||||
flagSoftwareRenderer = "software-renderer"
|
flagSoftwareRenderer = "software-renderer"
|
||||||
flagSessionID = "session-id"
|
flagEnableKeychainTest = "enable-keychain-test"
|
||||||
|
flagDisableKeychainTest = "disable-keychain-test"
|
||||||
|
FlagSessionID = "session-id"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -88,6 +93,20 @@ const (
|
|||||||
appShortName = "bridge"
|
appShortName = "bridge"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var cliFlagEnableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
|
||||||
|
Name: flagEnableKeychainTest,
|
||||||
|
Usage: "Enable the keychain test",
|
||||||
|
Hidden: true,
|
||||||
|
Value: false,
|
||||||
|
} //nolint:gochecknoglobals
|
||||||
|
|
||||||
|
var cliFlagDisableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
|
||||||
|
Name: flagDisableKeychainTest,
|
||||||
|
Usage: "Disable the keychain test",
|
||||||
|
Hidden: true,
|
||||||
|
Value: false,
|
||||||
|
}
|
||||||
|
|
||||||
func New() *cli.App {
|
func New() *cli.App {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
|
|
||||||
@ -162,9 +181,12 @@ func New() *cli.App {
|
|||||||
Value: false,
|
Value: false,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: flagSessionID,
|
Name: FlagSessionID,
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
},
|
},
|
||||||
|
// the two flags below were introduced by BRIDGE-116
|
||||||
|
cliFlagEnableKeychainTest,
|
||||||
|
cliFlagDisableKeychainTest,
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Action = run
|
app.Action = run
|
||||||
@ -204,7 +226,7 @@ func run(c *cli.Context) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Restart the app if requested.
|
// Restart the app if requested.
|
||||||
return withRestarter(exe, func(restarter *restarter.Restarter) error {
|
err = withRestarter(exe, func(restarter *restarter.Restarter) error {
|
||||||
// Handle crashes with various actions.
|
// Handle crashes with various actions.
|
||||||
return withCrashHandler(restarter, reporter, func(crashHandler *crash.Handler, quitCh <-chan struct{}) error {
|
return withCrashHandler(restarter, reporter, func(crashHandler *crash.Handler, quitCh <-chan struct{}) error {
|
||||||
migrationErr := migrateOldVersions()
|
migrationErr := migrateOldVersions()
|
||||||
@ -234,53 +256,57 @@ func run(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return withSingleInstance(settings, locations.GetLockFile(), version, func() error {
|
return withSingleInstance(settings, locations.GetLockFile(), version, func() error {
|
||||||
// Unlock the encrypted vault.
|
// Look for available keychains
|
||||||
return WithVault(locations, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
|
skipKeychainTest := checkSkipKeychainTest(c, settings)
|
||||||
if !v.Migrated() {
|
return WithKeychainList(crashHandler, skipKeychainTest, func(keychains *keychain.List) error {
|
||||||
// Migrate old settings into the vault.
|
// Unlock the encrypted vault.
|
||||||
if err := migrateOldSettings(v); err != nil {
|
return WithVault(locations, keychains, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
|
||||||
logrus.WithError(err).Error("Failed to migrate old settings")
|
if !v.Migrated() {
|
||||||
}
|
// Migrate old settings into the vault.
|
||||||
|
if err := migrateOldSettings(v); err != nil {
|
||||||
// Migrate old accounts into the vault.
|
logrus.WithError(err).Error("Failed to migrate old settings")
|
||||||
if err := migrateOldAccounts(locations, v); err != nil {
|
|
||||||
logrus.WithError(err).Error("Failed to migrate old accounts")
|
|
||||||
}
|
|
||||||
|
|
||||||
// The vault has been migrated.
|
|
||||||
if err := v.SetMigrated(); err != nil {
|
|
||||||
logrus.WithError(err).Error("Failed to mark vault as migrated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
|
||||||
"lastVersion": v.GetLastVersion().String(),
|
|
||||||
"showAllMail": v.GetShowAllMail(),
|
|
||||||
"updateCh": v.GetUpdateChannel(),
|
|
||||||
"autoUpdate": v.GetAutoUpdate(),
|
|
||||||
"rollout": v.GetUpdateRollout(),
|
|
||||||
"DoH": v.GetProxyAllowed(),
|
|
||||||
}).Info("Vault loaded")
|
|
||||||
|
|
||||||
// Load the cookies from the vault.
|
|
||||||
return withCookieJar(v, func(cookieJar http.CookieJar) error {
|
|
||||||
// Create a new bridge instance.
|
|
||||||
return withBridge(c, exe, locations, version, identifier, crashHandler, reporter, v, cookieJar, func(b *bridge.Bridge, eventCh <-chan events.Event) error {
|
|
||||||
if insecure {
|
|
||||||
logrus.Warn("The vault key could not be retrieved; the vault will not be encrypted")
|
|
||||||
b.PushError(bridge.ErrVaultInsecure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if corrupt {
|
// Migrate old accounts into the vault.
|
||||||
logrus.Warn("The vault is corrupt and has been wiped")
|
if err := migrateOldAccounts(locations, keychains, v); err != nil {
|
||||||
b.PushError(bridge.ErrVaultCorrupt)
|
logrus.WithError(err).Error("Failed to migrate old accounts")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start telemetry heartbeat process
|
// The vault has been migrated.
|
||||||
b.StartHeartbeat(b)
|
if err := v.SetMigrated(); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to mark vault as migrated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Run the frontend.
|
logrus.WithFields(logrus.Fields{
|
||||||
return runFrontend(c, crashHandler, restarter, locations, b, eventCh, quitCh, c.Int(flagParentPID))
|
"lastVersion": v.GetLastVersion().String(),
|
||||||
|
"showAllMail": v.GetShowAllMail(),
|
||||||
|
"updateCh": v.GetUpdateChannel(),
|
||||||
|
"autoUpdate": v.GetAutoUpdate(),
|
||||||
|
"rollout": v.GetUpdateRollout(),
|
||||||
|
"DoH": v.GetProxyAllowed(),
|
||||||
|
}).Info("Vault loaded")
|
||||||
|
|
||||||
|
// Load the cookies from the vault.
|
||||||
|
return withCookieJar(v, func(cookieJar http.CookieJar) error {
|
||||||
|
// Create a new bridge instance.
|
||||||
|
return withBridge(c, exe, locations, version, identifier, crashHandler, reporter, v, cookieJar, keychains, func(b *bridge.Bridge, eventCh <-chan events.Event) error {
|
||||||
|
if insecure {
|
||||||
|
logrus.Warn("The vault key could not be retrieved; the vault will not be encrypted")
|
||||||
|
b.PushError(bridge.ErrVaultInsecure)
|
||||||
|
}
|
||||||
|
|
||||||
|
if corrupt {
|
||||||
|
logrus.Warn("The vault is corrupt and has been wiped")
|
||||||
|
b.PushError(bridge.ErrVaultCorrupt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old updates files
|
||||||
|
b.RemoveOldUpdates()
|
||||||
|
|
||||||
|
// Run the frontend.
|
||||||
|
return runFrontend(c, crashHandler, restarter, locations, b, eventCh, quitCh, c.Int(flagParentPID))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -290,6 +316,13 @@ func run(c *cli.Context) error {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// if an error occurs, it must be logged now because we're about to close the log file.
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's another instance already running, try to raise it and exit.
|
// If there's another instance already running, try to raise it and exit.
|
||||||
@ -333,7 +366,7 @@ func withLogging(c *cli.Context, crashHandler *crash.Handler, locations *locatio
|
|||||||
logrus.WithField("path", logsPath).Debug("Received logs path")
|
logrus.WithField("path", logsPath).Debug("Received logs path")
|
||||||
|
|
||||||
// Initialize logging.
|
// Initialize logging.
|
||||||
sessionID := logging.NewSessionIDFromString(c.String(flagSessionID))
|
sessionID := logging.NewSessionIDFromString(c.String(FlagSessionID))
|
||||||
var closer io.Closer
|
var closer io.Closer
|
||||||
if closer, err = logging.Init(
|
if closer, err = logging.Init(
|
||||||
logsPath,
|
logsPath,
|
||||||
@ -360,6 +393,24 @@ func withLogging(c *cli.Context, crashHandler *crash.Handler, locations *locatio
|
|||||||
WithField("SentryID", sentry.GetProtectedHostname()).
|
WithField("SentryID", sentry.GetProtectedHostname()).
|
||||||
Info("Run app")
|
Info("Run app")
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
logrus.
|
||||||
|
WithField("timeZone", now.Format("MST")).
|
||||||
|
WithField("offset", now.Format("-07:00:00")).
|
||||||
|
Info("Time zone info")
|
||||||
|
|
||||||
|
host, err := sysinfo.Host()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Could not retrieve operating system info")
|
||||||
|
} else {
|
||||||
|
osInfo := host.Info().OS
|
||||||
|
logrus.
|
||||||
|
WithField("name", osInfo.Name).
|
||||||
|
WithField("version", osInfo.Version).
|
||||||
|
WithField("build", osInfo.Build).
|
||||||
|
Info("Operating system info")
|
||||||
|
}
|
||||||
|
|
||||||
return fn(closer)
|
return fn(closer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,6 +521,14 @@ func withCookieJar(vault *vault.Vault, fn func(http.CookieJar) error) error {
|
|||||||
return fn(persister)
|
return fn(persister)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithKeychainList init the list of usable keychains.
|
||||||
|
func WithKeychainList(panicHandler async.PanicHandler, skipKeychainTest bool, fn func(*keychain.List) error) error {
|
||||||
|
logrus.Debug("Creating keychain list")
|
||||||
|
defer logrus.Debug("Keychain list stop")
|
||||||
|
defer async.HandlePanic(panicHandler)
|
||||||
|
return fn(keychain.NewList(skipKeychainTest))
|
||||||
|
}
|
||||||
|
|
||||||
func setDeviceCookies(jar *cookies.Jar) error {
|
func setDeviceCookies(jar *cookies.Jar) error {
|
||||||
url, err := url.Parse(constants.APIHost)
|
url, err := url.Parse(constants.APIHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -487,3 +546,35 @@ func setDeviceCookies(jar *cookies.Jar) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkSkipKeychainTest(c *cli.Context, settingsDir string) bool {
|
||||||
|
if runtime.GOOS != "darwin" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
enable := c.Bool(flagEnableKeychainTest)
|
||||||
|
disable := c.Bool(flagDisableKeychainTest)
|
||||||
|
|
||||||
|
skip, err := vault.GetShouldSkipKeychainTest(settingsDir)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Could not load keychain settings.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enable) && (!disable) {
|
||||||
|
return skip
|
||||||
|
}
|
||||||
|
|
||||||
|
// if both switches are passed, 'enable' has priority
|
||||||
|
if disable {
|
||||||
|
skip = true
|
||||||
|
}
|
||||||
|
if enable {
|
||||||
|
skip = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vault.SetShouldSkipKeychainTest(settingsDir, skip); err != nil {
|
||||||
|
logrus.WithError(err).Error("Could not save keychain settings.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return skip
|
||||||
|
}
|
||||||
|
|||||||
65
internal/app/app_test.go
Normal file
65
internal/app/app_test.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckSkipKeychainTest(t *testing.T) {
|
||||||
|
var expectedResult bool
|
||||||
|
dir := t.TempDir()
|
||||||
|
app := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cliFlagEnableKeychainTest,
|
||||||
|
cliFlagDisableKeychainTest,
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
require.Equal(t, expectedResult, checkSkipKeychainTest(c, dir))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
noArgs := []string{"appName"}
|
||||||
|
enableArgs := []string{"appName", "-" + flagEnableKeychainTest}
|
||||||
|
disableArgs := []string{"appName", "-" + flagDisableKeychainTest}
|
||||||
|
bothArgs := []string{"appName", "-" + flagDisableKeychainTest, "-" + flagEnableKeychainTest}
|
||||||
|
|
||||||
|
const trueOnlyOnMac = runtime.GOOS == "darwin"
|
||||||
|
|
||||||
|
expectedResult = false
|
||||||
|
require.NoError(t, app.Run(noArgs))
|
||||||
|
|
||||||
|
expectedResult = trueOnlyOnMac
|
||||||
|
require.NoError(t, app.Run(disableArgs))
|
||||||
|
require.NoError(t, app.Run(noArgs))
|
||||||
|
|
||||||
|
expectedResult = false
|
||||||
|
require.NoError(t, app.Run(enableArgs))
|
||||||
|
require.NoError(t, app.Run(noArgs))
|
||||||
|
|
||||||
|
expectedResult = trueOnlyOnMac
|
||||||
|
require.NoError(t, app.Run(disableArgs))
|
||||||
|
|
||||||
|
expectedResult = false
|
||||||
|
require.NoError(t, app.Run(bothArgs))
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -37,6 +37,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/versioner"
|
"github.com/ProtonMail/proton-bridge/v3/internal/versioner"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@ -55,6 +56,7 @@ func withBridge(
|
|||||||
reporter *sentry.Reporter,
|
reporter *sentry.Reporter,
|
||||||
vault *vault.Vault,
|
vault *vault.Vault,
|
||||||
cookieJar http.CookieJar,
|
cookieJar http.CookieJar,
|
||||||
|
keychains *keychain.List,
|
||||||
fn func(*bridge.Bridge, <-chan events.Event) error,
|
fn func(*bridge.Bridge, <-chan events.Event) error,
|
||||||
) error {
|
) error {
|
||||||
logrus.Debug("Creating bridge")
|
logrus.Debug("Creating bridge")
|
||||||
@ -97,6 +99,7 @@ func withBridge(
|
|||||||
autostarter,
|
autostarter,
|
||||||
updater,
|
updater,
|
||||||
version,
|
version,
|
||||||
|
keychains,
|
||||||
|
|
||||||
// The API stuff.
|
// The API stuff.
|
||||||
constants.APIHost,
|
constants.APIHost,
|
||||||
@ -110,6 +113,7 @@ func withBridge(
|
|||||||
crashHandler,
|
crashHandler,
|
||||||
reporter,
|
reporter,
|
||||||
imap.DefaultEpochUIDValidityGenerator(),
|
imap.DefaultEpochUIDValidityGenerator(),
|
||||||
|
nil,
|
||||||
|
|
||||||
// The logging stuff.
|
// The logging stuff.
|
||||||
c.String(flagLogIMAP) == "client" || c.String(flagLogIMAP) == "all",
|
c.String(flagLogIMAP) == "client" || c.String(flagLogIMAP) == "all",
|
||||||
@ -155,7 +159,7 @@ func newUpdater(locations *locations.Locations) (*updater.Updater, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return updater.NewUpdater(
|
return updater.NewUpdater(
|
||||||
updater.NewInstaller(versioner.New(updatesDir)),
|
versioner.New(updatesDir),
|
||||||
verifier,
|
verifier,
|
||||||
constants.UpdateName,
|
constants.UpdateName,
|
||||||
runtime.GOOS,
|
runtime.GOOS,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -43,7 +43,7 @@ import (
|
|||||||
|
|
||||||
// nolint:gosec
|
// nolint:gosec
|
||||||
func migrateKeychainHelper(locations *locations.Locations) error {
|
func migrateKeychainHelper(locations *locations.Locations) error {
|
||||||
logrus.Info("Migrating keychain helper")
|
logrus.Trace("Checking if keychain helper needs to be migrated")
|
||||||
|
|
||||||
settings, err := locations.ProvideSettingsPath()
|
settings, err := locations.ProvideSettingsPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -75,7 +75,11 @@ func migrateKeychainHelper(locations *locations.Locations) error {
|
|||||||
return fmt.Errorf("failed to unmarshal old prefs file: %w", err)
|
return fmt.Errorf("failed to unmarshal old prefs file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return vault.SetHelper(settings, prefs.Helper)
|
err = vault.SetHelper(settings, prefs.Helper)
|
||||||
|
if err == nil {
|
||||||
|
logrus.Info("Keychain helper has been migrated")
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint:gosec
|
// nolint:gosec
|
||||||
@ -122,7 +126,7 @@ func migrateOldSettingsWithDir(configDir string, v *vault.Vault) error {
|
|||||||
return v.SetBridgeTLSCertKey(certPEM, keyPEM)
|
return v.SetBridgeTLSCertKey(certPEM, keyPEM)
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateOldAccounts(locations *locations.Locations, v *vault.Vault) error {
|
func migrateOldAccounts(locations *locations.Locations, keychains *keychain.List, v *vault.Vault) error {
|
||||||
logrus.Info("Migrating accounts")
|
logrus.Info("Migrating accounts")
|
||||||
|
|
||||||
settings, err := locations.ProvideSettingsPath()
|
settings, err := locations.ProvideSettingsPath()
|
||||||
@ -134,8 +138,7 @@ func migrateOldAccounts(locations *locations.Locations, v *vault.Vault) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get helper: %w", err)
|
return fmt.Errorf("failed to get helper: %w", err)
|
||||||
}
|
}
|
||||||
|
keychain, err := keychain.NewKeychain(helper, "bridge", keychains.GetHelpers(), keychains.GetDefaultHelper())
|
||||||
keychain, err := keychain.NewKeychain(helper, "bridge")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create keychain: %w", err)
|
return fmt.Errorf("failed to create keychain: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -35,7 +35,6 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/pkg/algo"
|
"github.com/ProtonMail/proton-bridge/v3/pkg/algo"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
dockerCredentials "github.com/docker/docker-credential-helpers/credentials"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -43,7 +42,7 @@ func TestMigratePrefsToVaultWithKeys(t *testing.T) {
|
|||||||
// Create a new vault.
|
// Create a new vault.
|
||||||
vault, corrupt, err := vault.New(t.TempDir(), t.TempDir(), []byte("my secret key"), async.NoopPanicHandler{})
|
vault, corrupt, err := vault.New(t.TempDir(), t.TempDir(), []byte("my secret key"), async.NoopPanicHandler{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, corrupt)
|
require.NoError(t, corrupt)
|
||||||
|
|
||||||
// load the old prefs file.
|
// load the old prefs file.
|
||||||
configDir := filepath.Join("testdata", "with_keys")
|
configDir := filepath.Join("testdata", "with_keys")
|
||||||
@ -64,7 +63,7 @@ func TestMigratePrefsToVaultWithoutKeys(t *testing.T) {
|
|||||||
// Create a new vault.
|
// Create a new vault.
|
||||||
vault, corrupt, err := vault.New(t.TempDir(), t.TempDir(), []byte("my secret key"), async.NoopPanicHandler{})
|
vault, corrupt, err := vault.New(t.TempDir(), t.TempDir(), []byte("my secret key"), async.NoopPanicHandler{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, corrupt)
|
require.NoError(t, corrupt)
|
||||||
|
|
||||||
// load the old prefs file.
|
// load the old prefs file.
|
||||||
configDir := filepath.Join("testdata", "without_keys")
|
configDir := filepath.Join("testdata", "without_keys")
|
||||||
@ -133,11 +132,9 @@ func TestKeychainMigration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUserMigration(t *testing.T) {
|
func TestUserMigration(t *testing.T) {
|
||||||
keychainHelper := keychain.NewTestHelper()
|
kcl := keychain.NewTestKeychainsList()
|
||||||
|
|
||||||
keychain.Helpers["mock"] = func(string) (dockerCredentials.Helper, error) { return keychainHelper, nil }
|
kc, err := keychain.NewKeychain("mock", "bridge", kcl.GetHelpers(), kcl.GetDefaultHelper())
|
||||||
|
|
||||||
kc, err := keychain.NewKeychain("mock", "bridge")
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.NoError(t, kc.Put("brokenID", "broken"))
|
require.NoError(t, kc.Put("brokenID", "broken"))
|
||||||
@ -176,9 +173,9 @@ func TestUserMigration(t *testing.T) {
|
|||||||
|
|
||||||
v, corrupt, err := vault.New(settingsFolder, settingsFolder, token, async.NoopPanicHandler{})
|
v, corrupt, err := vault.New(settingsFolder, settingsFolder, token, async.NoopPanicHandler{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, corrupt)
|
require.NoError(t, corrupt)
|
||||||
|
|
||||||
require.NoError(t, migrateOldAccounts(locations, v))
|
require.NoError(t, migrateOldAccounts(locations, kcl, v))
|
||||||
require.Equal(t, []string{wantCredentials.UserID}, v.GetUserIDs())
|
require.Equal(t, []string{wantCredentials.UserID}, v.GetUserIDs())
|
||||||
|
|
||||||
require.NoError(t, v.GetUser(wantCredentials.UserID, func(u *vault.User) {
|
require.NoError(t, v.GetUser(wantCredentials.UserID, func(u *vault.User) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -22,6 +22,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/ProtonMail/gluon/async"
|
"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/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
"github.com/ProtonMail/proton-bridge/v3/internal/locations"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
@ -29,30 +30,37 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WithVault(locations *locations.Locations, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
|
func WithVault(locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
|
||||||
logrus.Debug("Creating vault")
|
logrus.Debug("Creating vault")
|
||||||
defer logrus.Debug("Vault stopped")
|
defer logrus.Debug("Vault stopped")
|
||||||
|
|
||||||
// Create the encVault.
|
// Create the encVault.
|
||||||
encVault, insecure, corrupt, err := newVault(locations, panicHandler)
|
encVault, insecure, corrupt, err := newVault(locations, keychains, panicHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create vault: %w", err)
|
return fmt.Errorf("could not create vault: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"insecure": insecure,
|
"insecure": insecure,
|
||||||
"corrupt": corrupt,
|
"corrupt": corrupt != nil,
|
||||||
}).Debug("Vault created")
|
}).Debug("Vault created")
|
||||||
|
|
||||||
|
if corrupt != nil {
|
||||||
|
logrus.WithError(corrupt).Warn("Failed to load existing vault, vault has been reset")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, _ := encVault.GetBridgeTLSCert()
|
||||||
|
certs.NewInstaller().LogCertInstallStatus(cert)
|
||||||
|
|
||||||
// GODT-1950: Add teardown actions (e.g. to close the vault).
|
// GODT-1950: Add teardown actions (e.g. to close the vault).
|
||||||
|
|
||||||
return fn(encVault, insecure, corrupt)
|
return fn(encVault, insecure, corrupt != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVault(locations *locations.Locations, panicHandler async.PanicHandler) (*vault.Vault, bool, bool, error) {
|
func newVault(locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler) (*vault.Vault, bool, error, error) {
|
||||||
vaultDir, err := locations.ProvideSettingsPath()
|
vaultDir, err := locations.ProvideSettingsPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, false, fmt.Errorf("could not get vault dir: %w", err)
|
return nil, false, nil, fmt.Errorf("could not get vault dir: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.WithField("vaultDir", vaultDir).Debug("Loading vault from directory")
|
logrus.WithField("vaultDir", vaultDir).Debug("Loading vault from directory")
|
||||||
@ -62,7 +70,7 @@ func newVault(locations *locations.Locations, panicHandler async.PanicHandler) (
|
|||||||
insecure bool
|
insecure bool
|
||||||
)
|
)
|
||||||
|
|
||||||
if key, err := loadVaultKey(vaultDir); err != nil {
|
if key, err := loadVaultKey(vaultDir, keychains); err != nil {
|
||||||
logrus.WithError(err).Error("Could not load/create vault key")
|
logrus.WithError(err).Error("Could not load/create vault key")
|
||||||
insecure = true
|
insecure = true
|
||||||
|
|
||||||
@ -74,36 +82,37 @@ func newVault(locations *locations.Locations, panicHandler async.PanicHandler) (
|
|||||||
|
|
||||||
gluonCacheDir, err := locations.ProvideGluonCachePath()
|
gluonCacheDir, err := locations.ProvideGluonCachePath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, false, fmt.Errorf("could not provide gluon path: %w", err)
|
return nil, false, nil, fmt.Errorf("could not provide gluon path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
vault, corrupt, err := vault.New(vaultDir, gluonCacheDir, vaultKey, panicHandler)
|
vault, corrupt, err := vault.New(vaultDir, gluonCacheDir, vaultKey, panicHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, false, fmt.Errorf("could not create vault: %w", err)
|
return nil, false, corrupt, fmt.Errorf("could not create vault: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return vault, insecure, corrupt, nil
|
return vault, insecure, corrupt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadVaultKey(vaultDir string) ([]byte, error) {
|
func loadVaultKey(vaultDir string, keychains *keychain.List) ([]byte, error) {
|
||||||
helper, err := vault.GetHelper(vaultDir)
|
helper, err := vault.GetHelper(vaultDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not get keychain helper: %w", err)
|
return nil, fmt.Errorf("could not get keychain helper: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
kc, err := keychain.NewKeychain(helper, constants.KeyChainName)
|
kc, err := keychain.NewKeychain(helper, constants.KeyChainName, keychains.GetHelpers(), keychains.GetDefaultHelper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not create keychain: %w", err)
|
return nil, fmt.Errorf("could not create keychain: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
has, err := vault.HasVaultKey(kc)
|
key, err := vault.GetVaultKey(kc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if keychain.IsErrKeychainNoItem(err) {
|
||||||
|
logrus.WithError(err).Warn("no vault key found, generating new")
|
||||||
|
return vault.NewVaultKey(kc)
|
||||||
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("could not check for vault key: %w", err)
|
return nil, fmt.Errorf("could not check for vault key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if has {
|
return key, nil
|
||||||
return vault.GetVaultKey(kc)
|
|
||||||
}
|
|
||||||
|
|
||||||
return vault.NewVaultKey(kc)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -40,7 +40,7 @@ func defaultAPIOptions(
|
|||||||
proton.WithAppVersion(constants.AppVersion(version.Original())),
|
proton.WithAppVersion(constants.AppVersion(version.Original())),
|
||||||
proton.WithCookieJar(cookieJar),
|
proton.WithCookieJar(cookieJar),
|
||||||
proton.WithTransport(transport),
|
proton.WithTransport(transport),
|
||||||
proton.WithLogger(logrus.StandardLogger()),
|
proton.WithLogger(logrus.WithField("pkg", "gpa/client")),
|
||||||
proton.WithPanicHandler(panicHandler),
|
proton.WithPanicHandler(panicHandler),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -24,6 +24,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -41,15 +45,21 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
"github.com/ProtonMail/proton-bridge/v3/internal/sentry"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapsmtpserver"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/notifications"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
"github.com/bradenaw/juniper/xslices"
|
"github.com/bradenaw/juniper/xslices"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var usernameChangeRegex = regexp.MustCompile(`^/Users/([^/]+)/`)
|
||||||
|
|
||||||
type Bridge struct {
|
type Bridge struct {
|
||||||
// vault holds bridge-specific data, such as preferences and known users (authorized or not).
|
// vault holds bridge-specific data, such as preferences and known users (authorized or not).
|
||||||
vault *vault.Vault
|
vault *vault.Vault
|
||||||
@ -74,7 +84,7 @@ type Bridge struct {
|
|||||||
installCh chan installJob
|
installCh chan installJob
|
||||||
|
|
||||||
// heartbeat is the telemetry heartbeat for metrics.
|
// heartbeat is the telemetry heartbeat for metrics.
|
||||||
heartbeat telemetry.Heartbeat
|
heartbeat *heartBeatState
|
||||||
|
|
||||||
// curVersion is the current version of the bridge,
|
// curVersion is the current version of the bridge,
|
||||||
// newVersion is the version that was installed by the updater.
|
// newVersion is the version that was installed by the updater.
|
||||||
@ -82,6 +92,9 @@ type Bridge struct {
|
|||||||
newVersion *semver.Version
|
newVersion *semver.Version
|
||||||
newVersionLock safe.RWMutex
|
newVersionLock safe.RWMutex
|
||||||
|
|
||||||
|
// keychains is the utils that own usable keychains found in the OS.
|
||||||
|
keychains *keychain.List
|
||||||
|
|
||||||
// focusService is used to raise the bridge window when needed.
|
// focusService is used to raise the bridge window when needed.
|
||||||
focusService *focus.Service
|
focusService *focus.Service
|
||||||
|
|
||||||
@ -124,13 +137,21 @@ type Bridge struct {
|
|||||||
// goUpdate triggers a check/install of updates.
|
// goUpdate triggers a check/install of updates.
|
||||||
goUpdate func()
|
goUpdate func()
|
||||||
|
|
||||||
// goHeartbeat triggers a check/sending if heartbeat is needed.
|
|
||||||
goHeartbeat func()
|
|
||||||
|
|
||||||
serverManager *imapsmtpserver.Service
|
serverManager *imapsmtpserver.Service
|
||||||
syncService *syncservice.Service
|
syncService *syncservice.Service
|
||||||
|
|
||||||
|
// unleashService is responsible for polling the feature flags and caching
|
||||||
|
unleashService *unleash.Service
|
||||||
|
|
||||||
|
// observabilityService is responsible for handling calls to the observability system
|
||||||
|
observabilityService *observability.Service
|
||||||
|
|
||||||
|
// notificationStore is used for notification deduplication
|
||||||
|
notificationStore *notifications.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var logPkg = logrus.WithField("pkg", "bridge") //nolint:gochecknoglobals
|
||||||
|
|
||||||
// New creates a new bridge.
|
// New creates a new bridge.
|
||||||
func New(
|
func New(
|
||||||
locator Locator, // the locator to provide paths to store data
|
locator Locator, // the locator to provide paths to store data
|
||||||
@ -138,6 +159,7 @@ func New(
|
|||||||
autostarter Autostarter, // the autostarter to manage autostart settings
|
autostarter Autostarter, // the autostarter to manage autostart settings
|
||||||
updater Updater, // the updater to fetch and install updates
|
updater Updater, // the updater to fetch and install updates
|
||||||
curVersion *semver.Version, // the current version of the bridge
|
curVersion *semver.Version, // the current version of the bridge
|
||||||
|
keychains *keychain.List, // usable keychains
|
||||||
|
|
||||||
apiURL string, // the URL of the API to use
|
apiURL string, // the URL of the API to use
|
||||||
cookieJar http.CookieJar, // the cookie jar to use
|
cookieJar http.CookieJar, // the cookie jar to use
|
||||||
@ -148,6 +170,7 @@ func New(
|
|||||||
panicHandler async.PanicHandler,
|
panicHandler async.PanicHandler,
|
||||||
reporter reporter.Reporter,
|
reporter reporter.Reporter,
|
||||||
uidValidityGenerator imap.UIDValidityGenerator,
|
uidValidityGenerator imap.UIDValidityGenerator,
|
||||||
|
heartBeatManager telemetry.HeartbeatManager,
|
||||||
|
|
||||||
logIMAPClient, logIMAPServer bool, // whether to log IMAP client/server activity
|
logIMAPClient, logIMAPServer bool, // whether to log IMAP client/server activity
|
||||||
logSMTP bool, // whether to log SMTP activity
|
logSMTP bool, // whether to log SMTP activity
|
||||||
@ -163,6 +186,7 @@ func New(
|
|||||||
|
|
||||||
// bridge is the bridge.
|
// bridge is the bridge.
|
||||||
bridge, err := newBridge(
|
bridge, err := newBridge(
|
||||||
|
context.Background(),
|
||||||
tasks,
|
tasks,
|
||||||
imapEventCh,
|
imapEventCh,
|
||||||
|
|
||||||
@ -171,6 +195,7 @@ func New(
|
|||||||
autostarter,
|
autostarter,
|
||||||
updater,
|
updater,
|
||||||
curVersion,
|
curVersion,
|
||||||
|
keychains,
|
||||||
panicHandler,
|
panicHandler,
|
||||||
reporter,
|
reporter,
|
||||||
|
|
||||||
@ -178,6 +203,7 @@ func New(
|
|||||||
identifier,
|
identifier,
|
||||||
proxyCtl,
|
proxyCtl,
|
||||||
uidValidityGenerator,
|
uidValidityGenerator,
|
||||||
|
heartBeatManager,
|
||||||
logIMAPClient, logIMAPServer, logSMTP,
|
logIMAPClient, logIMAPServer, logSMTP,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -196,6 +222,7 @@ func New(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newBridge(
|
func newBridge(
|
||||||
|
ctx context.Context,
|
||||||
tasks *async.Group,
|
tasks *async.Group,
|
||||||
imapEventCh chan imapEvents.Event,
|
imapEventCh chan imapEvents.Event,
|
||||||
|
|
||||||
@ -204,6 +231,7 @@ func newBridge(
|
|||||||
autostarter Autostarter,
|
autostarter Autostarter,
|
||||||
updater Updater,
|
updater Updater,
|
||||||
curVersion *semver.Version,
|
curVersion *semver.Version,
|
||||||
|
keychains *keychain.List,
|
||||||
panicHandler async.PanicHandler,
|
panicHandler async.PanicHandler,
|
||||||
reporter reporter.Reporter,
|
reporter reporter.Reporter,
|
||||||
|
|
||||||
@ -211,6 +239,7 @@ func newBridge(
|
|||||||
identifier identifier.Identifier,
|
identifier identifier.Identifier,
|
||||||
proxyCtl ProxyController,
|
proxyCtl ProxyController,
|
||||||
uidValidityGenerator imap.UIDValidityGenerator,
|
uidValidityGenerator imap.UIDValidityGenerator,
|
||||||
|
heartbeatManager telemetry.HeartbeatManager,
|
||||||
|
|
||||||
logIMAPClient, logIMAPServer, logSMTP bool,
|
logIMAPClient, logIMAPServer, logSMTP bool,
|
||||||
) (*Bridge, error) {
|
) (*Bridge, error) {
|
||||||
@ -236,6 +265,8 @@ func newBridge(
|
|||||||
return nil, fmt.Errorf("failed to create focus service: %w", err)
|
return nil, fmt.Errorf("failed to create focus service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unleashService := unleash.NewBridgeService(ctx, api, locator, panicHandler)
|
||||||
|
|
||||||
bridge := &Bridge{
|
bridge := &Bridge{
|
||||||
vault: vault,
|
vault: vault,
|
||||||
|
|
||||||
@ -256,9 +287,13 @@ func newBridge(
|
|||||||
newVersion: curVersion,
|
newVersion: curVersion,
|
||||||
newVersionLock: safe.NewRWMutex(),
|
newVersionLock: safe.NewRWMutex(),
|
||||||
|
|
||||||
|
keychains: keychains,
|
||||||
|
|
||||||
panicHandler: panicHandler,
|
panicHandler: panicHandler,
|
||||||
reporter: reporter,
|
reporter: reporter,
|
||||||
|
|
||||||
|
heartbeat: newHeartBeatState(ctx, panicHandler),
|
||||||
|
|
||||||
focusService: focusService,
|
focusService: focusService,
|
||||||
autostarter: autostarter,
|
autostarter: autostarter,
|
||||||
locator: locator,
|
locator: locator,
|
||||||
@ -272,6 +307,12 @@ func newBridge(
|
|||||||
|
|
||||||
tasks: tasks,
|
tasks: tasks,
|
||||||
syncService: syncservice.NewService(reporter, panicHandler),
|
syncService: syncservice.NewService(reporter, panicHandler),
|
||||||
|
|
||||||
|
unleashService: unleashService,
|
||||||
|
|
||||||
|
observabilityService: observability.NewService(ctx, panicHandler),
|
||||||
|
|
||||||
|
notificationStore: notifications.NewStore(locator.ProvideNotificationsCachePath),
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.serverManager = imapsmtpserver.NewService(context.Background(),
|
bridge.serverManager = imapsmtpserver.NewService(context.Background(),
|
||||||
@ -284,11 +325,24 @@ func newBridge(
|
|||||||
&bridgeIMAPSMTPTelemetry{b: bridge},
|
&bridgeIMAPSMTPTelemetry{b: bridge},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Check whether username has changed and correct (macOS only)
|
||||||
|
bridge.verifyUsernameChange()
|
||||||
|
|
||||||
if err := bridge.serverManager.Init(context.Background(), bridge.tasks, &bridgeEventSubscription{b: bridge}); err != nil {
|
if err := bridge.serverManager.Init(context.Background(), bridge.tasks, &bridgeEventSubscription{b: bridge}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.syncService.Run(bridge.tasks)
|
if heartbeatManager == nil {
|
||||||
|
bridge.heartbeat.init(bridge, bridge)
|
||||||
|
} else {
|
||||||
|
bridge.heartbeat.init(bridge, heartbeatManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.syncService.Run()
|
||||||
|
|
||||||
|
bridge.unleashService.Run()
|
||||||
|
|
||||||
|
bridge.observabilityService.Run()
|
||||||
|
|
||||||
return bridge, nil
|
return bridge, nil
|
||||||
}
|
}
|
||||||
@ -303,7 +357,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
|
|||||||
|
|
||||||
// Handle connection up/down events.
|
// Handle connection up/down events.
|
||||||
bridge.api.AddStatusObserver(func(status proton.Status) {
|
bridge.api.AddStatusObserver(func(status proton.Status) {
|
||||||
logrus.Info("API status changed: ", status)
|
logPkg.Info("API status changed: ", status)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case status == proton.StatusUp:
|
case status == proton.StatusUp:
|
||||||
@ -318,7 +372,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
|
|||||||
|
|
||||||
// If any call returns a bad version code, we need to update.
|
// If any call returns a bad version code, we need to update.
|
||||||
bridge.api.AddErrorHandler(proton.AppVersionBadCode, func() {
|
bridge.api.AddErrorHandler(proton.AppVersionBadCode, func() {
|
||||||
logrus.Warn("App version is bad")
|
logPkg.Warn("App version is bad")
|
||||||
bridge.publish(events.UpdateForced{})
|
bridge.publish(events.UpdateForced{})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -331,7 +385,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
|
|||||||
// Log all manager API requests (client requests are logged separately).
|
// Log all manager API requests (client requests are logged separately).
|
||||||
bridge.api.AddPostRequestHook(func(_ *resty.Client, r *resty.Response) error {
|
bridge.api.AddPostRequestHook(func(_ *resty.Client, r *resty.Response) error {
|
||||||
if _, ok := proton.ClientIDFromContext(r.Request.Context()); !ok {
|
if _, ok := proton.ClientIDFromContext(r.Request.Context()); !ok {
|
||||||
logrus.Infof("[MANAGER] %v: %v %v", r.Status(), r.Request.Method, r.Request.URL)
|
logrus.WithField("pkg", "gpa/manager").Infof("%v: %v %v", r.Status(), r.Request.Method, r.Request.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -340,7 +394,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
|
|||||||
// Publish a TLS issue event if a TLS issue is encountered.
|
// Publish a TLS issue event if a TLS issue is encountered.
|
||||||
bridge.tasks.Once(func(ctx context.Context) {
|
bridge.tasks.Once(func(ctx context.Context) {
|
||||||
async.RangeContext(ctx, tlsReporter.GetTLSIssueCh(), func(struct{}) {
|
async.RangeContext(ctx, tlsReporter.GetTLSIssueCh(), func(struct{}) {
|
||||||
logrus.Warn("TLS issue encountered")
|
logPkg.Warn("TLS issue encountered")
|
||||||
bridge.publish(events.TLSIssue{})
|
bridge.publish(events.TLSIssue{})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -348,7 +402,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
|
|||||||
// Publish a raise event if the focus service is called.
|
// Publish a raise event if the focus service is called.
|
||||||
bridge.tasks.Once(func(ctx context.Context) {
|
bridge.tasks.Once(func(ctx context.Context) {
|
||||||
async.RangeContext(ctx, bridge.focusService.GetRaiseCh(), func(struct{}) {
|
async.RangeContext(ctx, bridge.focusService.GetRaiseCh(), func(struct{}) {
|
||||||
logrus.Info("Focus service requested raise")
|
logPkg.Info("Focus service requested raise")
|
||||||
bridge.publish(events.Raise{})
|
bridge.publish(events.Raise{})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -356,7 +410,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
|
|||||||
// Handle any IMAP events that are forwarded to the bridge from gluon.
|
// Handle any IMAP events that are forwarded to the bridge from gluon.
|
||||||
bridge.tasks.Once(func(ctx context.Context) {
|
bridge.tasks.Once(func(ctx context.Context) {
|
||||||
async.RangeContext(ctx, bridge.imapEventCh, func(event imapEvents.Event) {
|
async.RangeContext(ctx, bridge.imapEventCh, func(event imapEvents.Event) {
|
||||||
logrus.WithField("event", fmt.Sprintf("%T", event)).Debug("Received IMAP event")
|
logPkg.WithField("event", fmt.Sprintf("%T", event)).Debug("Received IMAP event")
|
||||||
bridge.handleIMAPEvent(event)
|
bridge.handleIMAPEvent(event)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -364,7 +418,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
|
|||||||
// Attempt to load users from the vault when triggered.
|
// Attempt to load users from the vault when triggered.
|
||||||
bridge.goLoad = bridge.tasks.Trigger(func(ctx context.Context) {
|
bridge.goLoad = bridge.tasks.Trigger(func(ctx context.Context) {
|
||||||
if err := bridge.loadUsers(ctx); err != nil {
|
if err := bridge.loadUsers(ctx); err != nil {
|
||||||
logrus.WithError(err).Error("Failed to load users")
|
logPkg.WithError(err).Error("Failed to load users")
|
||||||
if netErr := new(proton.NetError); !errors.As(err, &netErr) {
|
if netErr := new(proton.NetError); !errors.As(err, &netErr) {
|
||||||
sentry.ReportError(bridge.reporter, "Failed to load users", err)
|
sentry.ReportError(bridge.reporter, "Failed to load users", err)
|
||||||
}
|
}
|
||||||
@ -377,7 +431,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
|
|||||||
|
|
||||||
// Check for updates when triggered.
|
// Check for updates when triggered.
|
||||||
bridge.goUpdate = bridge.tasks.PeriodicOrTrigger(constants.UpdateCheckInterval, 0, func(ctx context.Context) {
|
bridge.goUpdate = bridge.tasks.PeriodicOrTrigger(constants.UpdateCheckInterval, 0, func(ctx context.Context) {
|
||||||
logrus.Info("Checking for updates")
|
logPkg.Info("Checking for updates")
|
||||||
|
|
||||||
version, err := bridge.updater.GetVersionInfo(ctx, bridge.api, bridge.vault.GetUpdateChannel())
|
version, err := bridge.updater.GetVersionInfo(ctx, bridge.api, bridge.vault.GetUpdateChannel())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -415,7 +469,13 @@ func (bridge *Bridge) GetErrors() []error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) Close(ctx context.Context) {
|
func (bridge *Bridge) Close(ctx context.Context) {
|
||||||
logrus.Info("Closing bridge")
|
logPkg.Info("Closing bridge")
|
||||||
|
|
||||||
|
// Stop observability service
|
||||||
|
bridge.observabilityService.Stop()
|
||||||
|
|
||||||
|
// Stop heart beat before closing users.
|
||||||
|
bridge.heartbeat.stop()
|
||||||
|
|
||||||
// Close all users.
|
// Close all users.
|
||||||
safe.Lock(func() {
|
safe.Lock(func() {
|
||||||
@ -426,15 +486,20 @@ func (bridge *Bridge) Close(ctx context.Context) {
|
|||||||
|
|
||||||
// Close the servers
|
// Close the servers
|
||||||
if err := bridge.serverManager.CloseServers(ctx); err != nil {
|
if err := bridge.serverManager.CloseServers(ctx); err != nil {
|
||||||
logrus.WithError(err).Error("Failed to close servers")
|
logPkg.WithError(err).Error("Failed to close servers")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bridge.syncService.Close()
|
||||||
|
|
||||||
// Stop all ongoing tasks.
|
// Stop all ongoing tasks.
|
||||||
bridge.tasks.CancelAndWait()
|
bridge.tasks.CancelAndWait()
|
||||||
|
|
||||||
// Close the focus service.
|
// Close the focus service.
|
||||||
bridge.focusService.Close()
|
bridge.focusService.Close()
|
||||||
|
|
||||||
|
// Close the unleash service.
|
||||||
|
bridge.unleashService.Close()
|
||||||
|
|
||||||
// Close the watchers.
|
// Close the watchers.
|
||||||
bridge.watchersLock.Lock()
|
bridge.watchersLock.Lock()
|
||||||
defer bridge.watchersLock.Unlock()
|
defer bridge.watchersLock.Unlock()
|
||||||
@ -450,12 +515,12 @@ func (bridge *Bridge) publish(event events.Event) {
|
|||||||
bridge.watchersLock.RLock()
|
bridge.watchersLock.RLock()
|
||||||
defer bridge.watchersLock.RUnlock()
|
defer bridge.watchersLock.RUnlock()
|
||||||
|
|
||||||
logrus.WithField("event", event).Debug("Publishing event")
|
logPkg.WithField("event", event).Debug("Publishing event")
|
||||||
|
|
||||||
for _, watcher := range bridge.watchers {
|
for _, watcher := range bridge.watchers {
|
||||||
if watcher.IsWatching(event) {
|
if watcher.IsWatching(event) {
|
||||||
if ok := watcher.Send(event); !ok {
|
if ok := watcher.Send(event); !ok {
|
||||||
logrus.WithField("event", event).Warn("Failed to send event to watcher")
|
logPkg.WithField("event", event).Warn("Failed to send event to watcher")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -487,26 +552,14 @@ func (bridge *Bridge) remWatcher(watcher *watcher.Watcher[events.Event]) {
|
|||||||
watcher.Close()
|
watcher.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) onStatusUp(ctx context.Context) {
|
func (bridge *Bridge) onStatusUp(_ context.Context) {
|
||||||
logrus.Info("Handling API status up")
|
logPkg.Info("Handling API status up")
|
||||||
|
|
||||||
safe.RLock(func() {
|
|
||||||
for _, user := range bridge.users {
|
|
||||||
user.OnStatusUp(ctx)
|
|
||||||
}
|
|
||||||
}, bridge.usersLock)
|
|
||||||
|
|
||||||
bridge.goLoad()
|
bridge.goLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) onStatusDown(ctx context.Context) {
|
func (bridge *Bridge) onStatusDown(ctx context.Context) {
|
||||||
logrus.Info("Handling API status down")
|
logPkg.Info("Handling API status down")
|
||||||
|
|
||||||
safe.RLock(func() {
|
|
||||||
for _, user := range bridge.users {
|
|
||||||
user.OnStatusDown(ctx)
|
|
||||||
}
|
|
||||||
}, bridge.usersLock)
|
|
||||||
|
|
||||||
for backoff := time.Second; ; backoff = min(backoff*2, 30*time.Second) {
|
for backoff := time.Second; ; backoff = min(backoff*2, 30*time.Second) {
|
||||||
select {
|
select {
|
||||||
@ -514,10 +567,10 @@ func (bridge *Bridge) onStatusDown(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
|
|
||||||
case <-time.After(backoff):
|
case <-time.After(backoff):
|
||||||
logrus.Info("Pinging API")
|
logPkg.Info("Pinging API")
|
||||||
|
|
||||||
if err := bridge.api.Ping(ctx); err != nil {
|
if err := bridge.api.Ping(ctx); err != nil {
|
||||||
logrus.WithError(err).Warn("Ping failed, API is still unreachable")
|
logPkg.WithError(err).Warn("Ping failed, API is still unreachable")
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -525,6 +578,49 @@ func (bridge *Bridge) onStatusDown(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) Repair() {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
userIDs := bridge.GetUserIDs()
|
||||||
|
|
||||||
|
for _, userID := range userIDs {
|
||||||
|
logPkg.Info("Initiating repair for userID:", userID)
|
||||||
|
|
||||||
|
userInfo, err := bridge.GetUserInfo(userID)
|
||||||
|
if err != nil {
|
||||||
|
logPkg.WithError(err).Error("Failed getting user info for repair; ID:", userID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if userInfo.State != Connected {
|
||||||
|
logPkg.Info("User is not connected. Repair will be executed on following successful log in.", userID)
|
||||||
|
if err := bridge.vault.GetUser(userID, func(user *vault.User) {
|
||||||
|
if err := user.SetShouldSync(true); err != nil {
|
||||||
|
logPkg.WithError(err).Error("Failed setting vault should sync for user:", userID)
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
logPkg.WithError(err).Error("Unable to get user vault when scheduling repair:", userID)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bridgeUser, ok := bridge.users[userID]
|
||||||
|
if !ok {
|
||||||
|
logPkg.Info("UserID does not exist in bridge user map", userID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func(userID string) {
|
||||||
|
defer wg.Done()
|
||||||
|
if err = bridgeUser.TriggerRepair(); err != nil {
|
||||||
|
logPkg.WithError(err).Error("Failed re-syncing IMAP for userID", userID)
|
||||||
|
}
|
||||||
|
}(userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
|
func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
|
||||||
cert, err := tls.X509KeyPair(vault.GetBridgeTLSCert())
|
cert, err := tls.X509KeyPair(vault.GetBridgeTLSCert())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -544,3 +640,75 @@ func min(a, b time.Duration) time.Duration {
|
|||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) HasAPIConnection() bool {
|
||||||
|
return bridge.api.GetStatus() == proton.StatusUp
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyUsernameChange - works only on macOS
|
||||||
|
// it attempts to check whether a username change has taken place by comparing the gluon DB path (which is static and provided by bridge)
|
||||||
|
// to the gluon Cache path - which can be modified by the user and is stored in the vault;
|
||||||
|
// if a username discrepancy is detected, and the cache folder does not exist with the "old" username
|
||||||
|
// then we verify whether the gluon cache exists using the "new" username (provided by the DB path in this case)
|
||||||
|
// if so we modify the cache directory in the user vault.
|
||||||
|
func (bridge *Bridge) verifyUsernameChange() {
|
||||||
|
if runtime.GOOS != "darwin" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gluonDBPath, err := bridge.GetGluonDataDir()
|
||||||
|
if err != nil {
|
||||||
|
logPkg.WithError(err).Error("Failed to get gluon db path")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gluonCachePath := bridge.GetGluonCacheDir()
|
||||||
|
// If the cache folder exists even on another user account or is in `/Users/Shared` we would still be able to access it
|
||||||
|
// though it depends on the permissions; this is an edge-case.
|
||||||
|
if _, err := os.Stat(gluonCachePath); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newCacheDir := GetUpdatedCachePath(gluonDBPath, gluonCachePath)
|
||||||
|
if newCacheDir == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(newCacheDir); err == nil {
|
||||||
|
logPkg.Info("Username change detected. Trying to restore gluon cache directory")
|
||||||
|
if err = bridge.vault.SetGluonDir(newCacheDir); err != nil {
|
||||||
|
logPkg.WithError(err).Error("Failed to restore gluon cache directory")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logPkg.Info("Successfully restored gluon cache directory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUpdatedCachePath(gluonDBPath, gluonCachePath string) string {
|
||||||
|
// If gluon cache is moved to an external drive; regex find will fail; as is expected
|
||||||
|
cachePathMatches := usernameChangeRegex.FindStringSubmatch(gluonCachePath)
|
||||||
|
if cachePathMatches == nil || len(cachePathMatches) < 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheUsername := cachePathMatches[1]
|
||||||
|
dbPathMatches := usernameChangeRegex.FindStringSubmatch(gluonDBPath)
|
||||||
|
if dbPathMatches == nil || len(dbPathMatches) < 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsername := dbPathMatches[1]
|
||||||
|
if cacheUsername == dbUsername {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Replace(gluonCachePath, "/Users/"+cacheUsername+"/", "/Users/"+dbUsername+"/", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) GetFeatureFlagValue(key string) bool {
|
||||||
|
return bridge.unleashService.GetFlagValue(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) PushObservabilityMetric(metric proton.ObservabilityMetric) {
|
||||||
|
bridge.observabilityService.AddMetric(metric)
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -49,6 +49,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
"github.com/ProtonMail/proton-bridge/v3/internal/user"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/tests"
|
"github.com/ProtonMail/proton-bridge/v3/tests"
|
||||||
"github.com/bradenaw/juniper/xslices"
|
"github.com/bradenaw/juniper/xslices"
|
||||||
imapid "github.com/emersion/go-imap-id"
|
imapid "github.com/emersion/go-imap-id"
|
||||||
@ -75,7 +76,7 @@ func init() {
|
|||||||
|
|
||||||
func TestBridge_ConnStatus(t *testing.T) {
|
func TestBridge_ConnStatus(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Get a stream of connection status events.
|
// Get a stream of connection status events.
|
||||||
eventCh, done := bridge.GetEvents(events.ConnStatusUp{}, events.ConnStatusDown{})
|
eventCh, done := bridge.GetEvents(events.ConnStatusUp{}, events.ConnStatusDown{})
|
||||||
defer done()
|
defer done()
|
||||||
@ -124,7 +125,7 @@ func TestBridge_TLSIssue(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Focus(t *testing.T) {
|
func TestBridge_Focus(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Get a stream of TLS issue events.
|
// Get a stream of TLS issue events.
|
||||||
raiseCh, done := bridge.GetEvents(events.Raise{})
|
raiseCh, done := bridge.GetEvents(events.Raise{})
|
||||||
defer done()
|
defer done()
|
||||||
@ -155,7 +156,7 @@ func TestBridge_UserAgent(t *testing.T) {
|
|||||||
calls = append(calls, call)
|
calls = append(calls, call)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Set the platform to something other than the default.
|
// Set the platform to something other than the default.
|
||||||
bridge.SetCurrentPlatform("platform")
|
bridge.SetCurrentPlatform("platform")
|
||||||
|
|
||||||
@ -182,21 +183,12 @@ func TestBridge_UserAgent_Persistence(t *testing.T) {
|
|||||||
_, _, err := s.CreateUser(otherUser, otherPassword)
|
_, _, err := s.CreateUser(otherUser, otherPassword)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
imapWaiter := waitForIMAPServerReady(b)
|
|
||||||
defer imapWaiter.Done()
|
|
||||||
|
|
||||||
smtpWaiter := waitForSMTPServerReady(b)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
currentUserAgent := b.GetCurrentUserAgent()
|
currentUserAgent := b.GetCurrentUserAgent()
|
||||||
require.Contains(t, currentUserAgent, useragent.DefaultUserAgent)
|
require.Contains(t, currentUserAgent, useragent.DefaultUserAgent)
|
||||||
|
|
||||||
require.NoError(t, getErr(b.LoginFull(ctx, otherUser, otherPassword, nil, nil)))
|
require.NoError(t, getErr(b.LoginFull(ctx, otherUser, otherPassword, nil, nil)))
|
||||||
|
|
||||||
imapWaiter.Wait()
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() { _ = imapClient.Logout() }()
|
defer func() { _ = imapClient.Logout() }()
|
||||||
@ -219,7 +211,7 @@ func TestBridge_UserAgent_Persistence(t *testing.T) {
|
|||||||
require.Contains(t, b.GetCurrentUserAgent(), "MyFancyClient/0.1.2")
|
require.Contains(t, b.GetCurrentUserAgent(), "MyFancyClient/0.1.2")
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
currentUserAgent := bridge.GetCurrentUserAgent()
|
currentUserAgent := bridge.GetCurrentUserAgent()
|
||||||
require.Contains(t, currentUserAgent, "MyFancyClient/0.1.2")
|
require.Contains(t, currentUserAgent, "MyFancyClient/0.1.2")
|
||||||
})
|
})
|
||||||
@ -233,22 +225,13 @@ func TestBridge_UserAgentFromUnknownClient(t *testing.T) {
|
|||||||
_, _, err := s.CreateUser(otherUser, otherPassword)
|
_, _, err := s.CreateUser(otherUser, otherPassword)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
imapWaiter := waitForIMAPServerReady(b)
|
|
||||||
defer imapWaiter.Done()
|
|
||||||
|
|
||||||
smtpWaiter := waitForSMTPServerReady(b)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
currentUserAgent := b.GetCurrentUserAgent()
|
currentUserAgent := b.GetCurrentUserAgent()
|
||||||
require.Contains(t, currentUserAgent, useragent.DefaultUserAgent)
|
require.Contains(t, currentUserAgent, useragent.DefaultUserAgent)
|
||||||
|
|
||||||
userID, err := b.LoginFull(context.Background(), username, password, nil, nil)
|
userID, err := b.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
imapWaiter.Wait()
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() { _ = imapClient.Logout() }()
|
defer func() { _ = imapClient.Logout() }()
|
||||||
@ -272,22 +255,13 @@ func TestBridge_UserAgentFromSMTPClient(t *testing.T) {
|
|||||||
_, _, err := s.CreateUser(otherUser, otherPassword)
|
_, _, err := s.CreateUser(otherUser, otherPassword)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
imapWaiter := waitForIMAPServerReady(b)
|
|
||||||
defer imapWaiter.Done()
|
|
||||||
|
|
||||||
smtpWaiter := waitForSMTPServerReady(b)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
currentUserAgent := b.GetCurrentUserAgent()
|
currentUserAgent := b.GetCurrentUserAgent()
|
||||||
require.Contains(t, currentUserAgent, useragent.DefaultUserAgent)
|
require.Contains(t, currentUserAgent, useragent.DefaultUserAgent)
|
||||||
|
|
||||||
userID, err := b.LoginFull(context.Background(), username, password, nil, nil)
|
userID, err := b.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
imapWaiter.Wait()
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
client, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(b.GetSMTPPort())))
|
client, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(b.GetSMTPPort())))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer client.Close() //nolint:errcheck
|
defer client.Close() //nolint:errcheck
|
||||||
@ -331,18 +305,9 @@ func TestBridge_UserAgentFromIMAPID(t *testing.T) {
|
|||||||
_, _, err := s.CreateUser(otherUser, otherPassword)
|
_, _, err := s.CreateUser(otherUser, otherPassword)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
imapWaiter := waitForIMAPServerReady(b)
|
|
||||||
defer imapWaiter.Done()
|
|
||||||
|
|
||||||
smtpWaiter := waitForSMTPServerReady(b)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
require.NoError(t, getErr(b.LoginFull(ctx, otherUser, otherPassword, nil, nil)))
|
require.NoError(t, getErr(b.LoginFull(ctx, otherUser, otherPassword, nil, nil)))
|
||||||
|
|
||||||
imapWaiter.Wait()
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() { _ = imapClient.Logout() }()
|
defer func() { _ = imapClient.Logout() }()
|
||||||
@ -400,13 +365,13 @@ func TestBridge_Cookies(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Start bridge and add a user so that API assigns us a session ID via cookie.
|
// Start bridge and add a user so that API assigns us a session ID via cookie.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Start bridge again and check that it uses the same session ID.
|
// Start bridge again and check that it uses the same session ID.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(_ *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -519,7 +484,7 @@ func TestBridge_ManualUpdate(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_ForceUpdate(t *testing.T) {
|
func TestBridge_ForceUpdate(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Get a stream of update events.
|
// Get a stream of update events.
|
||||||
updateCh, done := bridge.GetEvents(events.UpdateForced{})
|
updateCh, done := bridge.GetEvents(events.UpdateForced{})
|
||||||
defer done()
|
defer done()
|
||||||
@ -542,7 +507,7 @@ func TestBridge_BadVaultKey(t *testing.T) {
|
|||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
// Login a user.
|
// Login a user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
newUserID, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
newUserID, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -550,17 +515,17 @@ func TestBridge_BadVaultKey(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Start bridge with the correct vault key -- it should load the users correctly.
|
// Start bridge with the correct vault key -- it should load the users correctly.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.ElementsMatch(t, []string{userID}, bridge.GetUserIDs())
|
require.ElementsMatch(t, []string{userID}, bridge.GetUserIDs())
|
||||||
})
|
})
|
||||||
|
|
||||||
// Start bridge with a bad vault key, the vault will be wiped and bridge will show no users.
|
// Start bridge with a bad vault key, the vault will be wiped and bridge will show no users.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, []byte("bad"), func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, []byte("bad"), func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.Empty(t, bridge.GetUserIDs())
|
require.Empty(t, bridge.GetUserIDs())
|
||||||
})
|
})
|
||||||
|
|
||||||
// Start bridge with a nil vault key, the vault will be wiped and bridge will show no users.
|
// Start bridge with a nil vault key, the vault will be wiped and bridge will show no users.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, nil, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, nil, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.Empty(t, bridge.GetUserIDs())
|
require.Empty(t, bridge.GetUserIDs())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -570,7 +535,7 @@ func TestBridge_MissingGluonStore(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
var gluonDir string
|
var gluonDir string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -585,7 +550,7 @@ func TestBridge_MissingGluonStore(t *testing.T) {
|
|||||||
require.NoError(t, os.RemoveAll(gluonDir))
|
require.NoError(t, os.RemoveAll(gluonDir))
|
||||||
|
|
||||||
// Bridge starts but can't find the gluon store dir; there should be no error.
|
// Bridge starts but can't find the gluon store dir; there should be no error.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridgeWaitForServers(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(_ *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -595,7 +560,7 @@ func TestBridge_MissingGluonDatabase(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
var gluonDir string
|
var gluonDir string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -608,7 +573,7 @@ func TestBridge_MissingGluonDatabase(t *testing.T) {
|
|||||||
require.NoError(t, os.RemoveAll(gluonDir))
|
require.NoError(t, os.RemoveAll(gluonDir))
|
||||||
|
|
||||||
// Bridge starts but can't find the gluon database dir; there should be no error.
|
// Bridge starts but can't find the gluon database dir; there should be no error.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(_ *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -622,7 +587,7 @@ func TestBridge_AddressWithoutKeys(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer m.Close()
|
defer m.Close()
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Watch for sync finished event.
|
// Watch for sync finished event.
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
@ -698,7 +663,7 @@ func TestBridge_FactoryReset(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_InitGluonDirectory(t *testing.T) {
|
func TestBridge_InitGluonDirectory(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
configDir, err := b.GetGluonDataDir()
|
configDir, err := b.GetGluonDataDir()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -713,22 +678,13 @@ func TestBridge_InitGluonDirectory(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_LoginFailed(t *testing.T) {
|
func TestBridge_LoginFailed(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
imapWaiter := waitForIMAPServerReady(bridge)
|
|
||||||
defer imapWaiter.Done()
|
|
||||||
|
|
||||||
smtpWaiter := waitForSMTPServerReady(bridge)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
failCh, done := chToType[events.Event, events.IMAPLoginFailed](bridge.GetEvents(events.IMAPLoginFailed{}))
|
failCh, done := chToType[events.Event, events.IMAPLoginFailed](bridge.GetEvents(events.IMAPLoginFailed{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
_, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
_, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
imapWaiter.Wait()
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
imapClient, err := eventuallyDial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
imapClient, err := eventuallyDial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -750,18 +706,12 @@ func TestBridge_ChangeCacheDirectory(t *testing.T) {
|
|||||||
createNumMessages(ctx, t, c, addrID, labelID, 10)
|
createNumMessages(ctx, t, c, addrID, labelID, 10)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
newCacheDir := t.TempDir()
|
newCacheDir := t.TempDir()
|
||||||
currentCacheDir := b.GetGluonCacheDir()
|
currentCacheDir := b.GetGluonCacheDir()
|
||||||
configDir, err := b.GetGluonDataDir()
|
configDir, err := b.GetGluonDataDir()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
imapWaiter := waitForIMAPServerReady(b)
|
|
||||||
defer imapWaiter.Done()
|
|
||||||
|
|
||||||
smtpWaiter := waitForSMTPServerReady(b)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
// Login the user.
|
// Login the user.
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
@ -795,9 +745,6 @@ func TestBridge_ChangeCacheDirectory(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.True(t, info.State == bridge.Connected)
|
require.True(t, info.State == bridge.Connected)
|
||||||
|
|
||||||
imapWaiter.Wait()
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
client, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
client, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
|
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
|
||||||
@ -825,7 +772,7 @@ func TestBridge_ChangeAddressOrder(t *testing.T) {
|
|||||||
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
|
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Log the user in with its first address.
|
// Log the user in with its first address.
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
@ -853,7 +800,7 @@ func TestBridge_ChangeAddressOrder(t *testing.T) {
|
|||||||
require.NoError(t, c.OrderAddresses(ctx, proton.OrderAddressesReq{AddressIDs: []string{aliasID, addrID}}))
|
require.NoError(t, c.OrderAddresses(ctx, proton.OrderAddressesReq{AddressIDs: []string{aliasID, addrID}}))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// We should still see 10 messages in the inbox.
|
// We should still see 10 messages in the inbox.
|
||||||
info, err := b.GetUserInfo(userID)
|
info, err := b.GetUserInfo(userID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -950,6 +897,7 @@ func withBridgeNoMocks(
|
|||||||
mocks.Autostarter,
|
mocks.Autostarter,
|
||||||
mocks.Updater,
|
mocks.Updater,
|
||||||
v2_3_0,
|
v2_3_0,
|
||||||
|
keychain.NewTestKeychainsList(),
|
||||||
|
|
||||||
// The API stuff.
|
// The API stuff.
|
||||||
apiURL,
|
apiURL,
|
||||||
@ -961,6 +909,7 @@ func withBridgeNoMocks(
|
|||||||
mocks.CrashHandler,
|
mocks.CrashHandler,
|
||||||
mocks.Reporter,
|
mocks.Reporter,
|
||||||
testUIDValidityGenerator,
|
testUIDValidityGenerator,
|
||||||
|
mocks.Heartbeat,
|
||||||
|
|
||||||
// The logging stuff.
|
// The logging stuff.
|
||||||
os.Getenv("BRIDGE_LOG_IMAP_CLIENT") == "1",
|
os.Getenv("BRIDGE_LOG_IMAP_CLIENT") == "1",
|
||||||
@ -970,9 +919,6 @@ func withBridgeNoMocks(
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, bridge.GetErrors())
|
require.Empty(t, bridge.GetErrors())
|
||||||
|
|
||||||
// Start the Heartbeat process.
|
|
||||||
bridge.StartHeartbeat(mocks.Heartbeat)
|
|
||||||
|
|
||||||
// Wait for bridge to finish loading users.
|
// Wait for bridge to finish loading users.
|
||||||
waitForEvent(t, eventCh, events.AllUsersLoaded{})
|
waitForEvent(t, eventCh, events.AllUsersLoaded{})
|
||||||
|
|
||||||
@ -1131,3 +1077,57 @@ func waitForIMAPServerStopped(b *bridge.Bridge) *eventWaiter {
|
|||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBridge_GetUpdatedCachePath(t *testing.T) {
|
||||||
|
type TestData struct {
|
||||||
|
gluonDBPath string
|
||||||
|
gluonCachePath string
|
||||||
|
shouldChange bool
|
||||||
|
}
|
||||||
|
|
||||||
|
dataArr := []TestData{
|
||||||
|
{
|
||||||
|
gluonDBPath: "/Users/test/",
|
||||||
|
gluonCachePath: "/Users/test/gluon",
|
||||||
|
shouldChange: false,
|
||||||
|
}, {
|
||||||
|
gluonDBPath: "/Users/test/",
|
||||||
|
gluonCachePath: "/Users/tester/gluon",
|
||||||
|
shouldChange: true,
|
||||||
|
}, {
|
||||||
|
gluonDBPath: "/Users/testing/",
|
||||||
|
gluonCachePath: "/Users/test/gluon",
|
||||||
|
shouldChange: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gluonDBPath: "/Users/testing/",
|
||||||
|
gluonCachePath: "/Users/test/gluon",
|
||||||
|
shouldChange: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gluonDBPath: "/Users/testing/",
|
||||||
|
gluonCachePath: "/Volumes/test/gluon",
|
||||||
|
shouldChange: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gluonDBPath: "/Volumes/test/",
|
||||||
|
gluonCachePath: "/Users/test/gluon",
|
||||||
|
shouldChange: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gluonDBPath: "/XXX/test/",
|
||||||
|
gluonCachePath: "/Users/test/gluon",
|
||||||
|
shouldChange: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gluonDBPath: "/XXX/test/",
|
||||||
|
gluonCachePath: "/YYY/test/gluon",
|
||||||
|
shouldChange: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, el := range dataArr {
|
||||||
|
newCachePath := bridge.GetUpdatedCachePath(el.gluonDBPath, el.gluonCachePath)
|
||||||
|
require.Equal(t, el.shouldChange, newCachePath != "" && newCachePath != el.gluonCachePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -19,6 +19,7 @@ package bridge
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/ProtonMail/go-proton-api"
|
"github.com/ProtonMail/go-proton-api"
|
||||||
@ -33,63 +34,133 @@ const (
|
|||||||
DefaultMaxSessionCountForBugReport = 10
|
DefaultMaxSessionCountForBugReport = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, title, description, username, email, client string, attachLogs bool) error {
|
type ReportBugReq struct {
|
||||||
var account = username
|
OSType string
|
||||||
|
OSVersion string
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
Username string
|
||||||
|
Email string
|
||||||
|
EmailClient string
|
||||||
|
IncludeLogs bool
|
||||||
|
}
|
||||||
|
|
||||||
if info, err := bridge.QueryUserInfo(username); err == nil {
|
func (bridge *Bridge) ReportBug(ctx context.Context, report *ReportBugReq) error {
|
||||||
account = info.Username
|
if info, err := bridge.QueryUserInfo(report.Username); err == nil {
|
||||||
|
report.Username = info.Username
|
||||||
} else if userIDs := bridge.GetUserIDs(); len(userIDs) > 0 {
|
} else if userIDs := bridge.GetUserIDs(); len(userIDs) > 0 {
|
||||||
if err := bridge.vault.GetUser(userIDs[0], func(user *vault.User) {
|
if err := bridge.vault.GetUser(userIDs[0], func(user *vault.User) {
|
||||||
account = user.Username()
|
report.Username = user.Username()
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var attachment []proton.ReportBugAttachment
|
var attachments []proton.ReportBugAttachment
|
||||||
|
if report.IncludeLogs {
|
||||||
if attachLogs {
|
logs, err := bridge.CollectLogs()
|
||||||
logsPath, err := bridge.locator.ProvideLogsPath()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
attachments = append(attachments, logs)
|
||||||
buffer, err := logging.ZipLogsForBugReport(logsPath, DefaultMaxSessionCountForBugReport, DefaultMaxBugReportZipSize)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := io.ReadAll(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
attachment = append(attachment, proton.ReportBugAttachment{
|
|
||||||
Name: "logs.zip",
|
|
||||||
Filename: "logs.zip",
|
|
||||||
MIMEType: "application/zip",
|
|
||||||
Body: body,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
safe.Lock(func() {
|
var firstAtt proton.ReportBugAttachment
|
||||||
|
if len(attachments) > 0 && report.IncludeLogs {
|
||||||
|
firstAtt = attachments[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
attachmentType := proton.AttachmentTypeSync
|
||||||
|
if len(attachments) > 1 {
|
||||||
|
attachmentType = proton.AttachmentTypeAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := bridge.createTicket(ctx, report, attachmentType, firstAtt)
|
||||||
|
if err != nil || token == "" {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
safe.RLock(func() {
|
||||||
for _, user := range bridge.users {
|
for _, user := range bridge.users {
|
||||||
user.ReportBugSent()
|
user.ReportBugSent()
|
||||||
}
|
}
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
|
|
||||||
return bridge.api.ReportBug(ctx, proton.ReportBugReq{
|
// if we have a token we can append more attachment to the bugReport
|
||||||
OS: osType,
|
for i, att := range attachments {
|
||||||
OSVersion: osVersion,
|
if i == 0 && report.IncludeLogs {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := bridge.appendComment(ctx, token, att)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
Title: "[Bridge] Bug - " + title,
|
func (bridge *Bridge) CollectLogs() (proton.ReportBugAttachment, error) {
|
||||||
Description: description,
|
logsPath, err := bridge.locator.ProvideLogsPath()
|
||||||
|
if err != nil {
|
||||||
|
return proton.ReportBugAttachment{}, err
|
||||||
|
}
|
||||||
|
|
||||||
Client: client,
|
buffer, err := logging.ZipLogsForBugReport(logsPath, DefaultMaxSessionCountForBugReport, DefaultMaxBugReportZipSize)
|
||||||
|
if err != nil {
|
||||||
|
return proton.ReportBugAttachment{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return proton.ReportBugAttachment{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return proton.ReportBugAttachment{
|
||||||
|
Name: "logs.zip",
|
||||||
|
Filename: "logs.zip",
|
||||||
|
MIMEType: "application/zip",
|
||||||
|
Body: body,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) createTicket(ctx context.Context, report *ReportBugReq,
|
||||||
|
asyncAttach proton.AttachmentType, att proton.ReportBugAttachment) (string, error) {
|
||||||
|
var attachments []proton.ReportBugAttachment
|
||||||
|
attachments = append(attachments, att)
|
||||||
|
res, err := bridge.api.ReportBug(ctx, proton.ReportBugReq{
|
||||||
|
OS: report.OSType,
|
||||||
|
OSVersion: report.OSVersion,
|
||||||
|
|
||||||
|
Title: "[Bridge] Bug - " + report.Title,
|
||||||
|
Description: report.Description,
|
||||||
|
|
||||||
|
Client: report.EmailClient,
|
||||||
ClientType: proton.ClientTypeEmail,
|
ClientType: proton.ClientTypeEmail,
|
||||||
ClientVersion: constants.AppVersion(bridge.curVersion.Original()),
|
ClientVersion: constants.AppVersion(bridge.curVersion.Original()),
|
||||||
|
|
||||||
Username: account,
|
Username: report.Username,
|
||||||
Email: email,
|
Email: report.Email,
|
||||||
}, attachment...)
|
|
||||||
|
AsyncAttachments: asyncAttach,
|
||||||
|
}, attachments...)
|
||||||
|
|
||||||
|
if err != nil || asyncAttach != proton.AttachmentTypeAsync {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if asyncAttach == proton.AttachmentTypeAsync && res.Token == nil {
|
||||||
|
return "", errors.New("no token returns for AsyncAttachments")
|
||||||
|
}
|
||||||
|
|
||||||
|
return *res.Token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) appendComment(ctx context.Context, token string, att proton.ReportBugAttachment) error {
|
||||||
|
var attachments []proton.ReportBugAttachment
|
||||||
|
attachments = append(attachments, att)
|
||||||
|
return bridge.api.ReportBugAttachement(ctx, proton.ReportBugAttachmentReq{
|
||||||
|
Product: proton.ClientTypeEmail,
|
||||||
|
Body: "Comment adding attachment: " + att.Filename,
|
||||||
|
Token: token,
|
||||||
|
}, attachments...)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -22,7 +22,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (bridge *Bridge) ReportBugClicked() {
|
func (bridge *Bridge) ReportBugClicked() {
|
||||||
safe.Lock(func() {
|
safe.RLock(func() {
|
||||||
for _, user := range bridge.users {
|
for _, user := range bridge.users {
|
||||||
user.ReportBugClicked()
|
user.ReportBugClicked()
|
||||||
}
|
}
|
||||||
@ -30,17 +30,17 @@ func (bridge *Bridge) ReportBugClicked() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) AutoconfigUsed(client string) {
|
func (bridge *Bridge) AutoconfigUsed(client string) {
|
||||||
safe.Lock(func() {
|
safe.RLock(func() {
|
||||||
for _, user := range bridge.users {
|
for _, user := range bridge.users {
|
||||||
user.AutoconfigUsed(client)
|
user.AutoconfigUsed(client)
|
||||||
}
|
}
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) KBArticleOpened(article string) {
|
func (bridge *Bridge) ExternalLinkClicked(article string) {
|
||||||
safe.Lock(func() {
|
safe.RLock(func() {
|
||||||
for _, user := range bridge.users {
|
for _, user := range bridge.users {
|
||||||
user.KBArticleOpened(article)
|
user.ExternalLinkClicked(article)
|
||||||
}
|
}
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -19,6 +19,7 @@ package bridge
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/clientconfig"
|
"github.com/ProtonMail/proton-bridge/v3/internal/clientconfig"
|
||||||
@ -30,10 +31,10 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigureAppleMail configures apple mail for the given userID and address.
|
// ConfigureAppleMail configures Apple Mail for the given userID and address.
|
||||||
// If configuring apple mail for Catalina or newer, it ensures Bridge is using SSL.
|
// If configuring Apple Mail for Catalina or newer, it ensures Bridge is using SSL.
|
||||||
func (bridge *Bridge) ConfigureAppleMail(ctx context.Context, userID, address string) error {
|
func (bridge *Bridge) ConfigureAppleMail(ctx context.Context, userID, address string) error {
|
||||||
logrus.WithFields(logrus.Fields{
|
logPkg.WithFields(logrus.Fields{
|
||||||
"userID": userID,
|
"userID": userID,
|
||||||
"address": logging.Sensitive(address),
|
"address": logging.Sensitive(address),
|
||||||
}).Info("Configuring Apple Mail")
|
}).Info("Configuring Apple Mail")
|
||||||
@ -44,16 +45,28 @@ func (bridge *Bridge) ConfigureAppleMail(ctx context.Context, userID, address st
|
|||||||
return ErrNoSuchUser
|
return ErrNoSuchUser
|
||||||
}
|
}
|
||||||
|
|
||||||
if address == "" {
|
emails := user.Emails()
|
||||||
address = user.Emails()[0]
|
displayNames := user.DisplayNames()
|
||||||
|
if (len(emails) == 0) || (len(displayNames) == 0) {
|
||||||
|
return errors.New("could not retrieve user address info")
|
||||||
}
|
}
|
||||||
|
|
||||||
username := address
|
if address == "" {
|
||||||
addresses := address
|
address = emails[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var username, displayName, addresses string
|
||||||
if user.GetAddressMode() == vault.CombinedMode {
|
if user.GetAddressMode() == vault.CombinedMode {
|
||||||
username = user.Emails()[0]
|
username = address
|
||||||
addresses = strings.Join(user.Emails(), ",")
|
displayName = displayNames[username]
|
||||||
|
addresses = strings.Join(emails, ",")
|
||||||
|
} else {
|
||||||
|
username = address
|
||||||
|
addresses = address
|
||||||
|
displayName = displayNames[address]
|
||||||
|
if len(displayName) == 0 {
|
||||||
|
displayName = address
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if useragent.IsCatalinaOrNewer() && !bridge.vault.GetSMTPSSL() {
|
if useragent.IsCatalinaOrNewer() && !bridge.vault.GetSMTPSSL() {
|
||||||
@ -69,6 +82,7 @@ func (bridge *Bridge) ConfigureAppleMail(ctx context.Context, userID, address st
|
|||||||
bridge.vault.GetIMAPSSL(),
|
bridge.vault.GetIMAPSSL(),
|
||||||
bridge.vault.GetSMTPSSL(),
|
bridge.vault.GetSMTPSSL(),
|
||||||
username,
|
username,
|
||||||
|
displayName,
|
||||||
addresses,
|
addresses,
|
||||||
user.BridgePass(),
|
user.BridgePass(),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -65,7 +65,11 @@ func (bridge *Bridge) CheckClientState(ctx context.Context, checkFlags bool, pro
|
|||||||
if progressCB != nil {
|
if progressCB != nil {
|
||||||
progressCB(fmt.Sprintf("Checking state for user %v", usr.Name()))
|
progressCB(fmt.Sprintf("Checking state for user %v", usr.Name()))
|
||||||
}
|
}
|
||||||
log := logrus.WithField("user", usr.Name()).WithField("diag", "state-check")
|
log := logrus.WithFields(logrus.Fields{
|
||||||
|
"pkg": "bridge/debug",
|
||||||
|
"user": usr.Name(),
|
||||||
|
"diag": "state-check",
|
||||||
|
})
|
||||||
log.Debug("Retrieving all server metadata")
|
log.Debug("Retrieving all server metadata")
|
||||||
meta, err := usr.GetDiagnosticMetadata(ctx)
|
meta, err := usr.GetDiagnosticMetadata(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -280,7 +284,7 @@ func clientGetMessageIDs(client *goimapclient.Client, mailbox string) (map[strin
|
|||||||
|
|
||||||
internalID, ok := header.GetChecked("X-Pm-Internal-Id")
|
internalID, ok := header.GetChecked("X-Pm-Internal-Id")
|
||||||
if !ok {
|
if !ok {
|
||||||
logrus.Errorf("Message %v does not have internal id", internalID)
|
logrus.WithField("pkg", "bridge/debug").Errorf("Message %v does not have internal id", internalID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -64,9 +64,6 @@ func TestBridge_HandleDraftsSendFromOtherClient(t *testing.T) {
|
|||||||
|
|
||||||
// The initial user should be fully synced.
|
// The initial user should be fully synced.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
waiter := waitForIMAPServerReady(b)
|
|
||||||
defer waiter.Done()
|
|
||||||
|
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -74,7 +71,6 @@ func TestBridge_HandleDraftsSendFromOtherClient(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, userID, (<-syncCh).UserID)
|
require.Equal(t, userID, (<-syncCh).UserID)
|
||||||
waiter.Wait()
|
|
||||||
|
|
||||||
info, err := b.GetUserInfo(userID)
|
info, err := b.GetUserInfo(userID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -20,18 +20,100 @@ package bridge
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gluon/async"
|
||||||
"github.com/ProtonMail/gluon/reporter"
|
"github.com/ProtonMail/gluon/reporter"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/pkg/keychain"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const HeartbeatCheckInterval = time.Hour
|
const HeartbeatCheckInterval = time.Hour
|
||||||
|
|
||||||
|
type heartBeatState struct {
|
||||||
|
task *async.Group
|
||||||
|
telemetry.Heartbeat
|
||||||
|
taskLock sync.Mutex
|
||||||
|
taskStarted bool
|
||||||
|
taskInterval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHeartBeatState(ctx context.Context, panicHandler async.PanicHandler) *heartBeatState {
|
||||||
|
return &heartBeatState{
|
||||||
|
task: async.NewGroup(ctx, panicHandler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *heartBeatState) init(bridge *Bridge, manager telemetry.HeartbeatManager) {
|
||||||
|
h.Heartbeat = telemetry.NewHeartbeat(manager, 1143, 1025, bridge.GetGluonCacheDir(), bridge.keychains.GetDefaultHelper())
|
||||||
|
h.taskInterval = manager.GetHeartbeatPeriodicInterval()
|
||||||
|
h.SetRollout(bridge.GetUpdateRollout())
|
||||||
|
h.SetAutoStart(bridge.GetAutostart())
|
||||||
|
h.SetAutoUpdate(bridge.GetAutoUpdate())
|
||||||
|
h.SetBeta(bridge.GetUpdateChannel())
|
||||||
|
h.SetDoh(bridge.GetProxyAllowed())
|
||||||
|
h.SetShowAllMail(bridge.GetShowAllMail())
|
||||||
|
h.SetIMAPConnectionMode(bridge.GetIMAPSSL())
|
||||||
|
h.SetSMTPConnectionMode(bridge.GetSMTPSSL())
|
||||||
|
h.SetIMAPPort(bridge.GetIMAPPort())
|
||||||
|
h.SetSMTPPort(bridge.GetSMTPPort())
|
||||||
|
h.SetCacheLocation(bridge.GetGluonCacheDir())
|
||||||
|
if val, err := bridge.GetKeychainApp(); err != nil {
|
||||||
|
h.SetKeyChainPref(val)
|
||||||
|
} else {
|
||||||
|
h.SetKeyChainPref(bridge.keychains.GetDefaultHelper())
|
||||||
|
}
|
||||||
|
h.SetPrevVersion(bridge.GetLastVersion().String())
|
||||||
|
|
||||||
|
safe.RLock(func() {
|
||||||
|
var splitMode = false
|
||||||
|
for _, user := range bridge.users {
|
||||||
|
if user.GetAddressMode() == vault.SplitMode {
|
||||||
|
splitMode = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var nbAccount = len(bridge.users)
|
||||||
|
h.SetNbAccount(nbAccount)
|
||||||
|
h.SetSplitMode(splitMode)
|
||||||
|
|
||||||
|
// Do not try to send if there is no user yet.
|
||||||
|
if nbAccount > 0 {
|
||||||
|
defer h.start()
|
||||||
|
}
|
||||||
|
}, bridge.usersLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *heartBeatState) start() {
|
||||||
|
h.taskLock.Lock()
|
||||||
|
defer h.taskLock.Unlock()
|
||||||
|
if h.taskStarted {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.taskStarted = true
|
||||||
|
|
||||||
|
h.task.PeriodicOrTrigger(h.taskInterval, 0, func(ctx context.Context) {
|
||||||
|
logrus.WithField("pkg", "bridge/heartbeat").Debug("Checking for heartbeat")
|
||||||
|
|
||||||
|
h.TrySending(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *heartBeatState) stop() {
|
||||||
|
h.taskLock.Lock()
|
||||||
|
defer h.taskLock.Unlock()
|
||||||
|
if !h.taskStarted {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.task.CancelAndWait()
|
||||||
|
h.taskStarted = false
|
||||||
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) IsTelemetryAvailable(ctx context.Context) bool {
|
func (bridge *Bridge) IsTelemetryAvailable(ctx context.Context) bool {
|
||||||
var flag = true
|
var flag = true
|
||||||
if bridge.GetTelemetryDisabled() {
|
if bridge.GetTelemetryDisabled() {
|
||||||
@ -53,7 +135,7 @@ func (bridge *Bridge) SendHeartbeat(ctx context.Context, heartbeat *telemetry.He
|
|||||||
if err := bridge.reporter.ReportMessageWithContext("Cannot parse heartbeat data.", reporter.Context{
|
if err := bridge.reporter.ReportMessageWithContext("Cannot parse heartbeat data.", reporter.Context{
|
||||||
"error": err,
|
"error": err,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
logrus.WithError(err).Error("Failed to parse heartbeat data.")
|
logrus.WithField("pkg", "bridge/heartbeat").WithError(err).Error("Failed to parse heartbeat data.")
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -80,49 +162,6 @@ func (bridge *Bridge) SetLastHeartbeatSent(timestamp time.Time) error {
|
|||||||
return bridge.vault.SetLastHeartbeatSent(timestamp)
|
return bridge.vault.SetLastHeartbeatSent(timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) StartHeartbeat(manager telemetry.HeartbeatManager) {
|
func (bridge *Bridge) GetHeartbeatPeriodicInterval() time.Duration {
|
||||||
bridge.heartbeat = telemetry.NewHeartbeat(manager, 1143, 1025, bridge.GetGluonCacheDir(), keychain.DefaultHelper)
|
return HeartbeatCheckInterval
|
||||||
|
|
||||||
// Check for heartbeat when triggered.
|
|
||||||
bridge.goHeartbeat = bridge.tasks.PeriodicOrTrigger(HeartbeatCheckInterval, 0, func(ctx context.Context) {
|
|
||||||
logrus.Debug("Checking for heartbeat")
|
|
||||||
|
|
||||||
bridge.heartbeat.TrySending(ctx)
|
|
||||||
})
|
|
||||||
|
|
||||||
bridge.heartbeat.SetRollout(bridge.GetUpdateRollout())
|
|
||||||
bridge.heartbeat.SetAutoStart(bridge.GetAutostart())
|
|
||||||
bridge.heartbeat.SetAutoUpdate(bridge.GetAutoUpdate())
|
|
||||||
bridge.heartbeat.SetBeta(bridge.GetUpdateChannel())
|
|
||||||
bridge.heartbeat.SetDoh(bridge.GetProxyAllowed())
|
|
||||||
bridge.heartbeat.SetShowAllMail(bridge.GetShowAllMail())
|
|
||||||
bridge.heartbeat.SetIMAPConnectionMode(bridge.GetIMAPSSL())
|
|
||||||
bridge.heartbeat.SetSMTPConnectionMode(bridge.GetSMTPSSL())
|
|
||||||
bridge.heartbeat.SetIMAPPort(bridge.GetIMAPPort())
|
|
||||||
bridge.heartbeat.SetSMTPPort(bridge.GetSMTPPort())
|
|
||||||
bridge.heartbeat.SetCacheLocation(bridge.GetGluonCacheDir())
|
|
||||||
if val, err := bridge.GetKeychainApp(); err != nil {
|
|
||||||
bridge.heartbeat.SetKeyChainPref(val)
|
|
||||||
} else {
|
|
||||||
bridge.heartbeat.SetKeyChainPref(keychain.DefaultHelper)
|
|
||||||
}
|
|
||||||
bridge.heartbeat.SetPrevVersion(bridge.GetLastVersion().String())
|
|
||||||
|
|
||||||
safe.RLock(func() {
|
|
||||||
var splitMode = false
|
|
||||||
for _, user := range bridge.users {
|
|
||||||
if user.GetAddressMode() == vault.SplitMode {
|
|
||||||
splitMode = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var nbAccount = len(bridge.users)
|
|
||||||
bridge.heartbeat.SetNbAccount(nbAccount)
|
|
||||||
bridge.heartbeat.SetSplitMode(splitMode)
|
|
||||||
|
|
||||||
// Do not try to send if there is no user yet.
|
|
||||||
if nbAccount > 0 {
|
|
||||||
defer bridge.goHeartbeat()
|
|
||||||
}
|
|
||||||
}, bridge.usersLock)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -35,10 +35,12 @@ func (bridge *Bridge) restartIMAP(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
|
func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
|
||||||
|
log := logrus.WithField("pkg", "bridge/event/imap")
|
||||||
|
|
||||||
switch event := event.(type) {
|
switch event := event.(type) {
|
||||||
case imapEvents.UserAdded:
|
case imapEvents.UserAdded:
|
||||||
for labelID, count := range event.Counts {
|
for labelID, count := range event.Counts {
|
||||||
logrus.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"gluonID": event.UserID,
|
"gluonID": event.UserID,
|
||||||
"labelID": labelID,
|
"labelID": labelID,
|
||||||
"count": count,
|
"count": count,
|
||||||
@ -46,7 +48,7 @@ func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case imapEvents.IMAPID:
|
case imapEvents.IMAPID:
|
||||||
logrus.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"sessionID": event.SessionID,
|
"sessionID": event.SessionID,
|
||||||
"name": event.IMAPID.Name,
|
"name": event.IMAPID.Name,
|
||||||
"version": event.IMAPID.Version,
|
"version": event.IMAPID.Version,
|
||||||
@ -57,7 +59,7 @@ func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case imapEvents.LoginFailed:
|
case imapEvents.LoginFailed:
|
||||||
logrus.WithFields(logrus.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"sessionID": event.SessionID,
|
"sessionID": event.SessionID,
|
||||||
"username": event.Username,
|
"username": event.Username,
|
||||||
"pkg": "imap",
|
"pkg": "imap",
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
24
internal/bridge/keychain.go
Normal file
24
internal/bridge/keychain.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
import "golang.org/x/exp/maps"
|
||||||
|
|
||||||
|
func (bridge *Bridge) GetHelpersNames() []string {
|
||||||
|
return maps.Keys(bridge.keychains.GetHelpers())
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge/mocks"
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge/mocks"
|
||||||
@ -51,6 +52,7 @@ func NewMocks(tb testing.TB, version, minAuto *semver.Version) *Mocks {
|
|||||||
|
|
||||||
// this is called at start of heartbeat process.
|
// this is called at start of heartbeat process.
|
||||||
mocks.Heartbeat.EXPECT().IsTelemetryAvailable(gomock.Any()).AnyTimes()
|
mocks.Heartbeat.EXPECT().IsTelemetryAvailable(gomock.Any()).AnyTimes()
|
||||||
|
mocks.Heartbeat.EXPECT().GetHeartbeatPeriodicInterval().AnyTimes().Return(500 * time.Millisecond)
|
||||||
|
|
||||||
return mocks
|
return mocks
|
||||||
}
|
}
|
||||||
@ -154,3 +156,7 @@ func (testUpdater *TestUpdater) GetVersionInfo(_ context.Context, _ updater.Down
|
|||||||
func (testUpdater *TestUpdater) InstallUpdate(_ context.Context, _ updater.Downloader, _ updater.VersionInfo) error {
|
func (testUpdater *TestUpdater) InstallUpdate(_ context.Context, _ updater.Downloader, _ updater.VersionInfo) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (testUpdater *TestUpdater) RemoveOldUpdates() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -36,6 +36,20 @@ func (m *MockHeartbeatManager) EXPECT() *MockHeartbeatManagerMockRecorder {
|
|||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHeartbeatPeriodicInterval mocks base method.
|
||||||
|
func (m *MockHeartbeatManager) GetHeartbeatPeriodicInterval() time.Duration {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetHeartbeatPeriodicInterval")
|
||||||
|
ret0, _ := ret[0].(time.Duration)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeartbeatPeriodicInterval indicates an expected call of GetHeartbeatPeriodicInterval.
|
||||||
|
func (mr *MockHeartbeatManagerMockRecorder) GetHeartbeatPeriodicInterval() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHeartbeatPeriodicInterval", reflect.TypeOf((*MockHeartbeatManager)(nil).GetHeartbeatPeriodicInterval))
|
||||||
|
}
|
||||||
|
|
||||||
// GetLastHeartbeatSent mocks base method.
|
// GetLastHeartbeatSent mocks base method.
|
||||||
func (m *MockHeartbeatManager) GetLastHeartbeatSent() time.Time {
|
func (m *MockHeartbeatManager) GetLastHeartbeatSent() time.Time {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
97
internal/bridge/observability_test.go
Normal file
97
internal/bridge/observability_test.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
|
"github.com/ProtonMail/go-proton-api/server"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBridge_Observability(t *testing.T) {
|
||||||
|
testMetric := proton.ObservabilityMetric{
|
||||||
|
Name: "test1",
|
||||||
|
Version: 1,
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
|
throttlePeriod := time.Millisecond * 500
|
||||||
|
observability.ModifyThrottlePeriod(throttlePeriod)
|
||||||
|
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
|
require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil)))
|
||||||
|
|
||||||
|
bridge.PushObservabilityMetric(testMetric)
|
||||||
|
time.Sleep(time.Millisecond * 50) // Wait for the metric to be sent
|
||||||
|
require.Equal(t, 1, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
time.Sleep(time.Millisecond * 5) // Minor delay between each so our tests aren't flaky
|
||||||
|
bridge.PushObservabilityMetric(testMetric)
|
||||||
|
}
|
||||||
|
// We should still have only 1 metric sent as the throttleDuration has not passed
|
||||||
|
require.Equal(t, 1, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
// Wait for throttle duration to pass; we should have our remaining metrics posted
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
require.Equal(t, 11, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
// Wait for the throttle duration to reset; i.e. so we have enough time to send a request immediately
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
time.Sleep(time.Millisecond * 5)
|
||||||
|
bridge.PushObservabilityMetric(testMetric)
|
||||||
|
}
|
||||||
|
// We should only have one additional metric sent immediately
|
||||||
|
require.Equal(t, 12, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
// Wait for the others to be sent
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
require.Equal(t, 21, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
// Spam the endpoint a bit
|
||||||
|
for i := 0; i < 300; i++ {
|
||||||
|
if i < 200 {
|
||||||
|
time.Sleep(time.Millisecond * 10)
|
||||||
|
}
|
||||||
|
bridge.PushObservabilityMetric(testMetric)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we've sent all metrics
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
|
||||||
|
observabilityStats := s.GetObservabilityStatistics()
|
||||||
|
require.Equal(t, 321, len(observabilityStats.Metrics))
|
||||||
|
|
||||||
|
// Verify that each request had a throttleDuration time difference between each request
|
||||||
|
for i := 0; i < len(observabilityStats.RequestTime)-1; i++ {
|
||||||
|
tOne := observabilityStats.RequestTime[i]
|
||||||
|
tTwo := observabilityStats.RequestTime[i+1]
|
||||||
|
require.True(t, tTwo.Sub(tOne).Abs() > throttlePeriod)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -33,6 +33,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
|
smtpservice "github.com/ProtonMail/proton-bridge/v3/internal/services/smtp"
|
||||||
"github.com/emersion/go-imap"
|
"github.com/emersion/go-imap"
|
||||||
"github.com/emersion/go-sasl"
|
"github.com/emersion/go-sasl"
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
@ -45,17 +46,12 @@ func TestBridge_Send(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
smtpWaiter := waitForSMTPServerReady(bridge)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
senderUserID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
senderUserID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
recipientUserID, err := bridge.LoginFull(ctx, "recipient", password, nil, nil)
|
recipientUserID, err := bridge.LoginFull(ctx, "recipient", password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
senderInfo, err := bridge.GetUserInfo(senderUserID)
|
senderInfo, err := bridge.GetUserInfo(senderUserID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -336,6 +332,9 @@ func TestBridge_SendInvite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBridge_SendAddTextBodyPartIfNotExists(t *testing.T) {
|
func TestBridge_SendAddTextBodyPartIfNotExists(t *testing.T) {
|
||||||
|
// NOTE: Prior to GODT-2887, these tests had inline images, however after the implementation to support
|
||||||
|
// inline images new parts are injected to reference inline images without content-id set. The images
|
||||||
|
// in this test have been changed to regular attachments to keep the original checks in place.
|
||||||
const messageMultipartWithoutText = `Content-Type: multipart/mixed;
|
const messageMultipartWithoutText = `Content-Type: multipart/mixed;
|
||||||
boundary="Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84"
|
boundary="Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84"
|
||||||
Subject: A new message
|
Subject: A new message
|
||||||
@ -343,7 +342,7 @@ Date: Mon, 13 Mar 2023 16:06:16 +0100
|
|||||||
|
|
||||||
|
|
||||||
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
||||||
Content-Disposition: inline;
|
Content-Disposition: attachment;
|
||||||
filename=Cat_August_2010-4.jpeg
|
filename=Cat_August_2010-4.jpeg
|
||||||
Content-Type: image/jpeg;
|
Content-Type: image/jpeg;
|
||||||
name="Cat_August_2010-4.jpeg"
|
name="Cat_August_2010-4.jpeg"
|
||||||
@ -360,7 +359,7 @@ Subject: A new message Part2
|
|||||||
Date: Mon, 13 Mar 2023 16:06:16 +0100
|
Date: Mon, 13 Mar 2023 16:06:16 +0100
|
||||||
|
|
||||||
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
||||||
Content-Disposition: inline;
|
Content-Disposition: attachment;
|
||||||
filename=Cat_August_2010-4.jpeg
|
filename=Cat_August_2010-4.jpeg
|
||||||
Content-Type: image/jpeg;
|
Content-Type: image/jpeg;
|
||||||
name="Cat_August_2010-4.jpeg"
|
name="Cat_August_2010-4.jpeg"
|
||||||
@ -405,9 +404,6 @@ SGVsbG8gd29ybGQK
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
smtpWaiter := waitForSMTPServerReady(bridge)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
senderUserID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
senderUserID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -427,8 +423,6 @@ SGVsbG8gd29ybGQK
|
|||||||
messageMultipartWithoutTextWithTextAttachment,
|
messageMultipartWithoutTextWithTextAttachment,
|
||||||
}
|
}
|
||||||
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
for _, m := range messages {
|
for _, m := range messages {
|
||||||
// Dial the server.
|
// Dial the server.
|
||||||
client, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
client, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
||||||
@ -520,3 +514,224 @@ SGVsbG8gd29ybGQK
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBridge_SendInlineImage(t *testing.T) {
|
||||||
|
const messageInlineImageOnly = `Content-Type: multipart/mixed;
|
||||||
|
boundary="Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84"
|
||||||
|
Subject: A new message
|
||||||
|
Date: Mon, 13 Mar 2023 16:06:16 +0100
|
||||||
|
|
||||||
|
|
||||||
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
||||||
|
Content-Disposition: inline;
|
||||||
|
filename=Cat_August_2010-4.jpeg
|
||||||
|
Content-Type: image/jpeg;
|
||||||
|
name="Cat_August_2010-4.jpeg"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
SGVsbG8gd29ybGQ=
|
||||||
|
|
||||||
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84--
|
||||||
|
`
|
||||||
|
|
||||||
|
const messageInlineImageWithHTML = `Content-Type: multipart/mixed;
|
||||||
|
boundary="Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84"
|
||||||
|
Subject: A new message Part2
|
||||||
|
Date: Mon, 13 Mar 2023 16:06:16 +0100
|
||||||
|
|
||||||
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
||||||
|
Content-Type: text/html;charset=utf8
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
Hello world
|
||||||
|
|
||||||
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
||||||
|
Content-Disposition: inline;
|
||||||
|
filename=Cat_August_2010-4.jpeg
|
||||||
|
Content-Type: image/jpeg;
|
||||||
|
name="Cat_August_2010-4.jpeg"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
SGVsbG8gd29ybGQ=
|
||||||
|
|
||||||
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84--
|
||||||
|
`
|
||||||
|
|
||||||
|
const messageInlineImageWithText = `Content-Type: multipart/mixed;
|
||||||
|
boundary="Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84"
|
||||||
|
Subject: A new message Part3
|
||||||
|
Date: Mon, 13 Mar 2023 16:06:16 +0100
|
||||||
|
|
||||||
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
||||||
|
Content-Type: text/plain;charset=utf8
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
Hello world
|
||||||
|
|
||||||
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
||||||
|
Content-Disposition: inline;
|
||||||
|
filename=Cat_August_2010-4.jpeg
|
||||||
|
Content-Type: image/jpeg;
|
||||||
|
name="Cat_August_2010-4.jpeg"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
SGVsbG8gd29ybGQ=
|
||||||
|
|
||||||
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84--
|
||||||
|
`
|
||||||
|
|
||||||
|
const messageInlineImageFollowedByText = `Content-Type: multipart/mixed;
|
||||||
|
boundary="Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84"
|
||||||
|
Subject: A new message Part4
|
||||||
|
Date: Mon, 13 Mar 2023 16:06:16 +0100
|
||||||
|
|
||||||
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
||||||
|
Content-Disposition: inline;
|
||||||
|
filename=Cat_August_2010-4.jpeg
|
||||||
|
Content-Type: image/jpeg;
|
||||||
|
name="Cat_August_2010-4.jpeg"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
SGVsbG8gd29ybGQ=
|
||||||
|
|
||||||
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84
|
||||||
|
Content-Type: text/plain;charset=utf8
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
Hello world
|
||||||
|
|
||||||
|
--Apple-Mail=_E7AC06C7-4EB2-4453-8CBB-80F4412A7C84--
|
||||||
|
`
|
||||||
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
|
_, _, err := s.CreateUser("recipient", password)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
|
senderUserID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
recipientUserID, err := bridge.LoginFull(ctx, "recipient", password, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
senderInfo, err := bridge.GetUserInfo(senderUserID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
recipientInfo, err := bridge.GetUserInfo(recipientUserID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
messages := []string{
|
||||||
|
messageInlineImageOnly,
|
||||||
|
messageInlineImageWithHTML,
|
||||||
|
messageInlineImageWithText,
|
||||||
|
messageInlineImageFollowedByText,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range messages {
|
||||||
|
// Dial the server.
|
||||||
|
client, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer client.Close() //nolint:errcheck
|
||||||
|
|
||||||
|
// Upgrade to TLS.
|
||||||
|
require.NoError(t, client.StartTLS(&tls.Config{InsecureSkipVerify: true}))
|
||||||
|
|
||||||
|
// Authorize with SASL LOGIN.
|
||||||
|
require.NoError(t, client.Auth(sasl.NewLoginClient(
|
||||||
|
senderInfo.Addresses[0],
|
||||||
|
string(senderInfo.BridgePass)),
|
||||||
|
))
|
||||||
|
|
||||||
|
// Send the message.
|
||||||
|
require.NoError(t, client.SendMail(
|
||||||
|
senderInfo.Addresses[0],
|
||||||
|
[]string{recipientInfo.Addresses[0]},
|
||||||
|
strings.NewReader(m),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect the sender IMAP client.
|
||||||
|
senderIMAPClient, err := eventuallyDial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, senderIMAPClient.Login(senderInfo.Addresses[0], string(senderInfo.BridgePass)))
|
||||||
|
defer senderIMAPClient.Logout() //nolint:errcheck
|
||||||
|
|
||||||
|
// Connect the recipient IMAP client.
|
||||||
|
recipientIMAPClient, err := eventuallyDial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, recipientIMAPClient.Login(recipientInfo.Addresses[0], string(recipientInfo.BridgePass)))
|
||||||
|
defer recipientIMAPClient.Logout() //nolint:errcheck
|
||||||
|
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
messages, err := clientFetch(senderIMAPClient, `Sent`, imap.FetchBodyStructure)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if len(messages) != 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// messages may not be in order
|
||||||
|
for _, message := range messages {
|
||||||
|
require.Equal(t, 1, len(message.BodyStructure.Parts))
|
||||||
|
require.Equal(t, "multipart", message.BodyStructure.MIMEType)
|
||||||
|
require.Equal(t, "mixed", message.BodyStructure.MIMESubType)
|
||||||
|
require.Equal(t, "multipart", message.BodyStructure.Parts[0].MIMEType)
|
||||||
|
require.Equal(t, "related", message.BodyStructure.Parts[0].MIMESubType)
|
||||||
|
require.Len(t, message.BodyStructure.Parts[0].Parts, 2)
|
||||||
|
require.Equal(t, "text", message.BodyStructure.Parts[0].Parts[0].MIMEType)
|
||||||
|
require.Equal(t, "html", message.BodyStructure.Parts[0].Parts[0].MIMESubType)
|
||||||
|
require.Equal(t, "image", message.BodyStructure.Parts[0].Parts[1].MIMEType)
|
||||||
|
require.Equal(t, "jpeg", message.BodyStructure.Parts[0].Parts[1].MIMESubType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}, 10*time.Second, 100*time.Millisecond)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBridge_SendAddressDisabled(t *testing.T) {
|
||||||
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
|
recipientUserID, _, err := s.CreateUser("recipient", password)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
senderUserID, addrID, err := s.CreateUser("sender", password)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NoError(t, s.ChangeAddressAllowSend(senderUserID, addrID, false))
|
||||||
|
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
|
senderUserID, err := bridge.LoginFull(ctx, "sender", password, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = bridge.LoginFull(ctx, "recipient", password, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
recipientInfo, err := bridge.GetUserInfo(recipientUserID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
senderInfo, err := bridge.GetUserInfo(senderUserID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Dial the server.
|
||||||
|
client, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer client.Close() //nolint:errcheck
|
||||||
|
|
||||||
|
// Upgrade to TLS.
|
||||||
|
require.NoError(t, client.StartTLS(&tls.Config{InsecureSkipVerify: true}))
|
||||||
|
require.NoError(t, client.Auth(sasl.NewLoginClient(
|
||||||
|
senderInfo.Addresses[0],
|
||||||
|
string(senderInfo.BridgePass)),
|
||||||
|
))
|
||||||
|
|
||||||
|
// Send the message.
|
||||||
|
err = client.SendMail(
|
||||||
|
senderInfo.Addresses[0],
|
||||||
|
[]string{recipientInfo.Addresses[0]},
|
||||||
|
strings.NewReader("Subject: Test 1\r\n\r\nHello world!"),
|
||||||
|
)
|
||||||
|
|
||||||
|
smtpErr := smtpservice.NewErrCannotSendFromAddress(senderInfo.Addresses[0])
|
||||||
|
require.Equal(t, fmt.Sprintf("Error: %v", smtpErr.Error()), err.Error())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -36,9 +36,6 @@ import (
|
|||||||
func TestBridge_Report(t *testing.T) {
|
func TestBridge_Report(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
imapWaiter := waitForIMAPServerReady(b)
|
|
||||||
defer imapWaiter.Done()
|
|
||||||
|
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -54,8 +51,6 @@ func TestBridge_Report(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.True(t, info.State == bridge.Connected)
|
require.True(t, info.State == bridge.Connected)
|
||||||
|
|
||||||
imapWaiter.Wait()
|
|
||||||
|
|
||||||
// Dial the IMAP port.
|
// Dial the IMAP port.
|
||||||
conn, err := net.Dial("tcp", fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
conn, err := net.Dial("tcp", fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -20,6 +20,7 @@ package bridge_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/go-proton-api"
|
"github.com/ProtonMail/go-proton-api"
|
||||||
@ -27,57 +28,39 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
|
"github.com/emersion/go-smtp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServerManager_NoLoadedUsersNoServers(t *testing.T) {
|
func TestServerManager_ServersStartWithBridge(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
_, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerManager_ServersStartAfterFirstConnectedUser(t *testing.T) {
|
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
|
||||||
imapWaiter := waitForIMAPServerReady(bridge)
|
|
||||||
defer imapWaiter.Done()
|
|
||||||
|
|
||||||
smtpWaiter := waitForSMTPServerReady(bridge)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
_, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, imapClient.Logout())
|
||||||
|
|
||||||
imapWaiter.Wait()
|
smtpClient, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
||||||
smtpWaiter.Wait()
|
require.NoError(t, err)
|
||||||
|
smtpClient.Close() //nolint:errcheck
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerManager_ServersStopsAfterUserLogsOut(t *testing.T) {
|
func TestServerManager_ServersKeepsRunningfterUserLogsOut(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
imapWaiter := waitForIMAPServerReady(bridge)
|
|
||||||
defer imapWaiter.Done()
|
|
||||||
|
|
||||||
smtpWaiter := waitForSMTPServerReady(bridge)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
imapWaiter.Wait()
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
imapWaiterStopped := waitForIMAPServerStopped(bridge)
|
|
||||||
defer imapWaiterStopped.Done()
|
|
||||||
|
|
||||||
require.NoError(t, bridge.LogoutUser(ctx, userID))
|
require.NoError(t, bridge.LogoutUser(ctx, userID))
|
||||||
|
|
||||||
imapWaiterStopped.Wait()
|
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, imapClient.Logout())
|
||||||
|
|
||||||
|
smtpClient, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
smtpClient.Close() //nolint:errcheck
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -89,22 +72,13 @@ func TestServerManager_ServersDoNotStopWhenThereIsStillOneActiveUser(t *testing.
|
|||||||
_, _, err := s.CreateUser(otherUser, otherPassword)
|
_, _, err := s.CreateUser(otherUser, otherPassword)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
imapWaiter := waitForIMAPServerReady(bridge)
|
|
||||||
defer imapWaiter.Done()
|
|
||||||
|
|
||||||
smtpWaiter := waitForSMTPServerReady(bridge)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
_, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
_, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
userIDOther, err := bridge.LoginFull(ctx, otherUser, otherPassword, nil, nil)
|
userIDOther, err := bridge.LoginFull(ctx, otherUser, otherPassword, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
imapWaiter.Wait()
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
evtCh, cancel := bridge.GetEvents(events.UserDeauth{})
|
evtCh, cancel := bridge.GetEvents(events.UserDeauth{})
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@ -115,38 +89,17 @@ func TestServerManager_ServersDoNotStopWhenThereIsStillOneActiveUser(t *testing.
|
|||||||
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, imapClient.Logout())
|
require.NoError(t, imapClient.Logout())
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServerManager_ServersStartIfAtLeastOneUserIsLoggedIn(t *testing.T) {
|
smtpClient, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
||||||
otherPassword := []byte("bar")
|
|
||||||
otherUser := "foo"
|
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
|
||||||
userIDOther, _, err := s.CreateUser(otherUser, otherPassword)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
|
||||||
_, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
smtpClient.Close() //nolint:errcheck
|
||||||
_, err = bridge.LoginFull(ctx, otherUser, otherPassword, nil, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, s.RevokeUser(userIDOther))
|
|
||||||
|
|
||||||
withBridgeWaitForServers(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
|
||||||
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, imapClient.Logout())
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerManager_NetworkLossStopsServers(t *testing.T) {
|
func TestServerManager_NetworkLossStopsServers(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
imapWaiter := waitForIMAPServerReady(bridge)
|
imapWaiter := waitForIMAPServerReady(bridge)
|
||||||
defer imapWaiter.Done()
|
defer imapWaiter.Done()
|
||||||
|
|
||||||
@ -162,8 +115,13 @@ func TestServerManager_NetworkLossStopsServers(t *testing.T) {
|
|||||||
_, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
_, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
imapWaiter.Wait()
|
imapClient, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
||||||
smtpWaiter.Wait()
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, imapClient.Logout())
|
||||||
|
|
||||||
|
smtpClient, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetSMTPPort())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
smtpClient.Close() //nolint:errcheck
|
||||||
|
|
||||||
netCtl.Disable()
|
netCtl.Disable()
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -22,11 +22,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/kb"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (bridge *Bridge) GetKeychainApp() (string, error) {
|
func (bridge *Bridge) GetKeychainApp() (string, error) {
|
||||||
@ -133,7 +133,7 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error
|
|||||||
bridge.usersLock.RLock()
|
bridge.usersLock.RLock()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
logrus.Info("Restarting user event loops")
|
logPkg.Info("Restarting user event loops")
|
||||||
for _, u := range bridge.users {
|
for _, u := range bridge.users {
|
||||||
u.ResumeEventLoop()
|
u.ResumeEventLoop()
|
||||||
}
|
}
|
||||||
@ -148,20 +148,20 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error
|
|||||||
|
|
||||||
waiters := make([]waiter, 0, len(bridge.users))
|
waiters := make([]waiter, 0, len(bridge.users))
|
||||||
|
|
||||||
logrus.Info("Pausing user event loops for gluon dir change")
|
logPkg.Info("Pausing user event loops for gluon dir change")
|
||||||
for id, u := range bridge.users {
|
for id, u := range bridge.users {
|
||||||
waiters = append(waiters, waiter{w: u.PauseEventLoopWithWaiter(), id: id})
|
waiters = append(waiters, waiter{w: u.PauseEventLoopWithWaiter(), id: id})
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Info("Waiting on user event loop completion")
|
logPkg.Info("Waiting on user event loop completion")
|
||||||
for _, waiter := range waiters {
|
for _, waiter := range waiters {
|
||||||
if err := waiter.w.WaitPollFinished(ctx); err != nil {
|
if err := waiter.w.WaitPollFinished(ctx); err != nil {
|
||||||
logrus.WithError(err).Errorf("Failed to wait on event loop pause for user %v", waiter.id)
|
logPkg.WithError(err).Errorf("Failed to wait on event loop pause for user %v", waiter.id)
|
||||||
return fmt.Errorf("failed on event loop pause: %w", err)
|
return fmt.Errorf("failed on event loop pause: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Info("Changing gluon directory")
|
logPkg.Info("Changing gluon directory")
|
||||||
return bridge.serverManager.SetGluonDir(ctx, newGluonDir)
|
return bridge.serverManager.SetGluonDir(ctx, newGluonDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,9 +261,12 @@ func (bridge *Bridge) SetTelemetryDisabled(isDisabled bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// If telemetry is re-enabled locally, try to send the heartbeat.
|
// If telemetry is re-enabled locally, try to send the heartbeat.
|
||||||
if !isDisabled {
|
if isDisabled {
|
||||||
defer bridge.goHeartbeat()
|
bridge.heartbeat.stop()
|
||||||
|
} else {
|
||||||
|
bridge.heartbeat.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,6 +310,10 @@ func (bridge *Bridge) SetColorScheme(colorScheme string) error {
|
|||||||
return bridge.vault.SetColorScheme(colorScheme)
|
return bridge.vault.SetColorScheme(colorScheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) GetKnowledgeBaseSuggestions(userInput string) (kb.ArticleList, error) {
|
||||||
|
return kb.GetSuggestions(userInput)
|
||||||
|
}
|
||||||
|
|
||||||
// FactoryReset deletes all users, wipes the vault, and deletes all files.
|
// FactoryReset deletes all users, wipes the vault, and deletes all files.
|
||||||
// Note: it does not clear the keychain. The only entry in the keychain is the vault password,
|
// Note: it does not clear the keychain. The only entry in the keychain is the vault password,
|
||||||
// which we need at next startup to decrypt the vault.
|
// which we need at next startup to decrypt the vault.
|
||||||
@ -322,13 +329,13 @@ func (bridge *Bridge) FactoryReset(ctx context.Context) {
|
|||||||
// Wipe the vault.
|
// Wipe the vault.
|
||||||
gluonCacheDir, err := bridge.locator.ProvideGluonCachePath()
|
gluonCacheDir, err := bridge.locator.ProvideGluonCachePath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Error("Failed to provide gluon dir")
|
logPkg.WithError(err).Error("Failed to provide gluon dir")
|
||||||
} else if err := bridge.vault.Reset(gluonCacheDir); err != nil {
|
} else if err := bridge.vault.Reset(gluonCacheDir); err != nil {
|
||||||
logrus.WithError(err).Error("Failed to reset vault")
|
logPkg.WithError(err).Error("Failed to reset vault")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lastly, delete all files except the vault.
|
// Lastly, delete all files except the vault.
|
||||||
if err := bridge.locator.Clear(bridge.vault.Path()); err != nil {
|
if err := bridge.locator.Clear(bridge.vault.Path()); err != nil {
|
||||||
logrus.WithError(err).Error("Failed to clear data paths")
|
logPkg.WithError(err).Error("Failed to clear data paths")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -31,7 +31,7 @@ import (
|
|||||||
|
|
||||||
func TestBridge_Settings_GluonDir(t *testing.T) {
|
func TestBridge_Settings_GluonDir(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Create a user.
|
// Create a user.
|
||||||
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -57,7 +57,7 @@ func TestBridge_Settings_GluonDirWithOnGoingEvents(t *testing.T) {
|
|||||||
userID, addrID, err := s.CreateUser("imap", password)
|
userID, addrID, err := s.CreateUser("imap", password)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ func TestBridge_Settings_GluonDirWithOnGoingEvents(t *testing.T) {
|
|||||||
createNumMessages(ctx, t, c, addrID, labelID, 200)
|
createNumMessages(ctx, t, c, addrID, labelID, 200)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridgeWaitForServers(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridgeWaitForServers(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Create a new location for the Gluon data.
|
// Create a new location for the Gluon data.
|
||||||
newGluonDir := t.TempDir()
|
newGluonDir := t.TempDir()
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ func TestBridge_Settings_GluonDirWithOnGoingEvents(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Settings_IMAPPort(t *testing.T) {
|
func TestBridge_Settings_IMAPPort(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
curPort := bridge.GetIMAPPort()
|
curPort := bridge.GetIMAPPort()
|
||||||
|
|
||||||
// Set the port to 1144.
|
// Set the port to 1144.
|
||||||
@ -110,7 +110,7 @@ func TestBridge_Settings_IMAPPort(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Settings_IMAPSSL(t *testing.T) {
|
func TestBridge_Settings_IMAPSSL(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// By default, IMAP SSL is disabled.
|
// By default, IMAP SSL is disabled.
|
||||||
require.False(t, bridge.GetIMAPSSL())
|
require.False(t, bridge.GetIMAPSSL())
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ func TestBridge_Settings_IMAPSSL(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Settings_SMTPPort(t *testing.T) {
|
func TestBridge_Settings_SMTPPort(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
curPort := bridge.GetSMTPPort()
|
curPort := bridge.GetSMTPPort()
|
||||||
|
|
||||||
// Set the port to 1024.
|
// Set the port to 1024.
|
||||||
@ -142,7 +142,7 @@ func TestBridge_Settings_SMTPPort(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Settings_SMTPSSL(t *testing.T) {
|
func TestBridge_Settings_SMTPSSL(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// By default, SMTP SSL is disabled.
|
// By default, SMTP SSL is disabled.
|
||||||
require.False(t, bridge.GetSMTPSSL())
|
require.False(t, bridge.GetSMTPSSL())
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ func TestBridge_Settings_Autostart(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Settings_FirstStart(t *testing.T) {
|
func TestBridge_Settings_FirstStart(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// By default, first start is true.
|
// By default, first start is true.
|
||||||
require.True(t, bridge.GetFirstStart())
|
require.True(t, bridge.GetFirstStart())
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -37,6 +37,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
||||||
"github.com/bradenaw/juniper/iterator"
|
"github.com/bradenaw/juniper/iterator"
|
||||||
"github.com/bradenaw/juniper/stream"
|
"github.com/bradenaw/juniper/stream"
|
||||||
"github.com/bradenaw/juniper/xslices"
|
"github.com/bradenaw/juniper/xslices"
|
||||||
@ -231,7 +232,7 @@ func TestBridge_SyncWithOngoingEvents(t *testing.T) {
|
|||||||
var total uint64
|
var total uint64
|
||||||
|
|
||||||
// The initial user should be fully synced.
|
// The initial user should be fully synced.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -245,7 +246,7 @@ func TestBridge_SyncWithOngoingEvents(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Now let's remove the user and stop the network at 2/3 of the data.
|
// Now let's remove the user and stop the network at 2/3 of the data.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.NoError(t, bridge.DeleteUser(ctx, userID))
|
require.NoError(t, bridge.DeleteUser(ctx, userID))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -253,7 +254,7 @@ func TestBridge_SyncWithOngoingEvents(t *testing.T) {
|
|||||||
netCtl.SetReadLimit(2 * total / 3)
|
netCtl.SetReadLimit(2 * total / 3)
|
||||||
|
|
||||||
// Login the user; its sync should fail.
|
// Login the user; its sync should fail.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -579,6 +580,116 @@ func TestBridge_MessageCreateDuringSync(t *testing.T) {
|
|||||||
}, server.WithTLS(false))
|
}, server.WithTLS(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBridge_CorruptedVaultClearsPreviousIMAPSyncState(t *testing.T) {
|
||||||
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
|
userID, addrID, err := s.CreateUser("imap", password)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
labelID, err := s.CreateLabel(userID, "folder", "", proton.LabelTypeFolder)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
withClient(ctx, t, s, "imap", password, func(ctx context.Context, c *proton.Client) {
|
||||||
|
createNumMessages(ctx, t, c, addrID, labelID, 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
userID, err = bridge.LoginFull(context.Background(), "imap", password, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Wait for sync to finish
|
||||||
|
require.Equal(t, userID, (<-syncCh).UserID)
|
||||||
|
})
|
||||||
|
|
||||||
|
settingsPath, err := locator.ProvideSettingsPath()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
syncConfigPath, err := locator.ProvideIMAPSyncConfigPath()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
syncStatePath := imapservice.GetSyncConfigPath(syncConfigPath, userID)
|
||||||
|
// Check sync state is complete
|
||||||
|
{
|
||||||
|
state, err := imapservice.NewSyncState(syncStatePath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
syncStatus, err := state.GetSyncStatus(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, syncStatus.IsComplete())
|
||||||
|
}
|
||||||
|
|
||||||
|
// corrupt the vault
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(settingsPath, "vault.enc"), []byte("Trash!"), 0o600))
|
||||||
|
|
||||||
|
// Bridge starts but can't find the gluon database dir; there should be no error.
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
|
_, err := bridge.LoginFull(context.Background(), "imap", password, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check sync state is reset.
|
||||||
|
{
|
||||||
|
state, err := imapservice.NewSyncState(syncStatePath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
syncStatus, err := state.GetSyncStatus(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, syncStatus.IsComplete())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBridge_AddressOrderChangeDuringSyncInCombinedModeDoesNotTriggerBadEventOnNewMessage(t *testing.T) {
|
||||||
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
|
// Create a user.
|
||||||
|
userID, addrID, err := s.CreateUser("user", password)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
|
userInfoChanged, done := chToType[events.Event, events.UserChanged](bridge.GetEvents(events.UserChanged{}))
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
|
||||||
|
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 300)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := bridge.LoginFull(ctx, "user", password, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
info, err := bridge.GetUserInfo(userID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(info.Addresses))
|
||||||
|
require.Equal(t, info.Addresses[0], "user@proton.local")
|
||||||
|
|
||||||
|
addrID2, err := s.CreateAddress(userID, "foo@"+s.GetDomain(), password)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NoError(t, s.SetAddressOrder(userID, []string{addrID2, addrID}))
|
||||||
|
|
||||||
|
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
|
||||||
|
createNumMessages(ctx, t, c, addrID2, proton.InboxLabel, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Since we can't intercept events at this time, we sleep for a bit to make sure the
|
||||||
|
// new message does not get combined into the event below. This ensures the newly created
|
||||||
|
// goes through the full code flow which triggered the original bad event.
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
require.NoError(t, s.SetAddressOrder(userID, []string{addrID, addrID2}))
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case e := <-userInfoChanged:
|
||||||
|
require.Equal(t, userID, e.UserID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func withClient(ctx context.Context, t *testing.T, s *server.Server, username string, password []byte, fn func(context.Context, *proton.Client)) { //nolint:unparam
|
func withClient(ctx context.Context, t *testing.T, s *server.Server, username string, password []byte, fn func(context.Context, *proton.Client)) { //nolint:unparam
|
||||||
m := proton.New(
|
m := proton.New(
|
||||||
proton.WithHostURL(s.GetHostURL()),
|
proton.WithHostURL(s.GetHostURL()),
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -33,6 +33,8 @@ type Locator interface {
|
|||||||
GetDependencyLicensesLink() string
|
GetDependencyLicensesLink() string
|
||||||
Clear(...string) error
|
Clear(...string) error
|
||||||
ProvideIMAPSyncConfigPath() (string, error)
|
ProvideIMAPSyncConfigPath() (string, error)
|
||||||
|
ProvideUnleashCachePath() (string, error)
|
||||||
|
ProvideNotificationsCachePath() (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyController interface {
|
type ProxyController interface {
|
||||||
@ -53,4 +55,5 @@ type Autostarter interface {
|
|||||||
type Updater interface {
|
type Updater interface {
|
||||||
GetVersionInfo(context.Context, updater.Downloader, updater.Channel) (updater.VersionInfo, error)
|
GetVersionInfo(context.Context, updater.Downloader, updater.Channel) (updater.VersionInfo, error)
|
||||||
InstallUpdate(context.Context, updater.Downloader, updater.VersionInfo) error
|
InstallUpdate(context.Context, updater.Downloader, updater.VersionInfo) error
|
||||||
|
RemoveOldUpdates() error
|
||||||
}
|
}
|
||||||
|
|||||||
90
internal/bridge/unleash_test.go
Normal file
90
internal/bridge/unleash_test.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bridge_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
|
"github.com/ProtonMail/go-proton-api/server"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_UnleashService(t *testing.T) {
|
||||||
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
|
unleash.ModifyPollPeriodAndJitter(500*time.Millisecond, 0)
|
||||||
|
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
|
// Initial startup assumes there is no cached feature flags.
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-1"), false)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-2"), false)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-3"), false)
|
||||||
|
|
||||||
|
s.PushFeatureFlag("test-1")
|
||||||
|
s.PushFeatureFlag("test-2")
|
||||||
|
|
||||||
|
// Wait for poll.
|
||||||
|
time.Sleep(time.Millisecond * 700)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-1"), true)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-2"), true)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-3"), false)
|
||||||
|
|
||||||
|
s.PushFeatureFlag("test-3")
|
||||||
|
time.Sleep(time.Millisecond * 700) // Wait for poll again
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-1"), true)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-2"), true)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-3"), true)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wait for Bridge to close.
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
|
||||||
|
// Second instance should have a feature flag cache file available. Therefore, all of the flags should evaluate to true on startup.
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-1"), true)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-2"), true)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-3"), true)
|
||||||
|
|
||||||
|
s.DeleteFeatureFlags()
|
||||||
|
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-1"), true)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-2"), true)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-3"), true)
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 700)
|
||||||
|
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-1"), false)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-2"), false)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-3"), false)
|
||||||
|
|
||||||
|
s.PushFeatureFlag("test-3")
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-1"), false)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-2"), false)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-3"), false)
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 700)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-1"), false)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-2"), false)
|
||||||
|
require.Equal(t, b.GetFeatureFlagValue("test-3"), true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -139,3 +139,9 @@ func (bridge *Bridge) installUpdate(ctx context.Context, job installJob) {
|
|||||||
}
|
}
|
||||||
}, bridge.newVersionLock)
|
}, bridge.newVersionLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) RemoveOldUpdates() {
|
||||||
|
if err := bridge.updater.RemoveOldUpdates(); err != nil {
|
||||||
|
logrus.WithError(err).Error("Remove old updates fails")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/ProtonMail/gluon/reporter"
|
"github.com/ProtonMail/gluon/reporter"
|
||||||
"github.com/ProtonMail/go-proton-api"
|
"github.com/ProtonMail/go-proton-api"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/hv"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
||||||
@ -38,6 +39,8 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var logUser = logrus.WithField("pkg", "bridge/user") //nolint:gochecknoglobals
|
||||||
|
|
||||||
type UserState int
|
type UserState int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -46,6 +49,8 @@ const (
|
|||||||
Connected
|
Connected
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrFailedToUnlock = errors.New("failed to unlock user keys")
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
// UserID is the user's API ID.
|
// UserID is the user's API ID.
|
||||||
UserID string
|
UserID string
|
||||||
@ -66,10 +71,10 @@ type UserInfo struct {
|
|||||||
BridgePass []byte
|
BridgePass []byte
|
||||||
|
|
||||||
// UsedSpace is the amount of space used by the user.
|
// UsedSpace is the amount of space used by the user.
|
||||||
UsedSpace int
|
UsedSpace uint64
|
||||||
|
|
||||||
// MaxSpace is the total amount of space available to the user.
|
// MaxSpace is the total amount of space available to the user.
|
||||||
MaxSpace int
|
MaxSpace uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserIDs returns the IDs of all known users (authorized or not).
|
// GetUserIDs returns the IDs of all known users (authorized or not).
|
||||||
@ -119,23 +124,28 @@ func (bridge *Bridge) QueryUserInfo(query string) (UserInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoginAuth begins the login process. It returns an authorized client that might need 2FA.
|
// LoginAuth begins the login process. It returns an authorized client that might need 2FA.
|
||||||
func (bridge *Bridge) LoginAuth(ctx context.Context, username string, password []byte) (*proton.Client, proton.Auth, error) {
|
func (bridge *Bridge) LoginAuth(ctx context.Context, username string, password []byte, hvDetails *proton.APIHVDetails) (*proton.Client, proton.Auth, error) {
|
||||||
logrus.WithField("username", logging.Sensitive(username)).Info("Authorizing user for login")
|
logUser.WithField("username", logging.Sensitive(username)).Info("Authorizing user for login")
|
||||||
|
|
||||||
if username == "crash@bandicoot" {
|
if username == "crash@bandicoot" {
|
||||||
panic("Your wish is my command.. I crash!")
|
panic("Your wish is my command.. I crash!")
|
||||||
}
|
}
|
||||||
|
client, auth, err := bridge.api.NewClientWithLoginWithHVToken(ctx, username, password, hvDetails)
|
||||||
client, auth, err := bridge.api.NewClientWithLogin(ctx, username, password)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if hv.IsHvRequest(err) {
|
||||||
|
logUser.WithFields(logrus.Fields{"username": logging.Sensitive(username),
|
||||||
|
"loginError": err.Error()}).Info("Human Verification requested for login")
|
||||||
|
return nil, proton.Auth{}, err
|
||||||
|
}
|
||||||
|
|
||||||
return nil, proton.Auth{}, fmt.Errorf("failed to create new API client: %w", err)
|
return nil, proton.Auth{}, fmt.Errorf("failed to create new API client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok := safe.RLockRet(func() bool { return mapHas(bridge.users, auth.UserID) }, bridge.usersLock); ok {
|
if ok := safe.RLockRet(func() bool { return mapHas(bridge.users, auth.UserID) }, bridge.usersLock); ok {
|
||||||
logrus.WithField("userID", auth.UserID).Warn("User already logged in")
|
logUser.WithField("userID", auth.UserID).Warn("User already logged in")
|
||||||
|
|
||||||
if err := client.AuthDelete(ctx); err != nil {
|
if err := client.AuthDelete(ctx); err != nil {
|
||||||
logrus.WithError(err).Warn("Failed to delete auth")
|
logUser.WithError(err).Warn("Failed to delete auth")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, proton.Auth{}, ErrUserAlreadyLoggedIn
|
return nil, proton.Auth{}, ErrUserAlreadyLoggedIn
|
||||||
@ -150,18 +160,23 @@ func (bridge *Bridge) LoginUser(
|
|||||||
client *proton.Client,
|
client *proton.Client,
|
||||||
auth proton.Auth,
|
auth proton.Auth,
|
||||||
keyPass []byte,
|
keyPass []byte,
|
||||||
|
hvDetails *proton.APIHVDetails,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
logrus.WithField("userID", auth.UserID).Info("Logging in authorized user")
|
logUser.WithField("userID", auth.UserID).Info("Logging in authorized user")
|
||||||
|
|
||||||
userID, err := try.CatchVal(
|
userID, err := try.CatchVal(
|
||||||
func() (string, error) {
|
func() (string, error) {
|
||||||
return bridge.loginUser(ctx, client, auth.UID, auth.RefreshToken, keyPass)
|
return bridge.loginUser(ctx, client, auth.UID, auth.RefreshToken, keyPass, hvDetails)
|
||||||
},
|
|
||||||
func() error {
|
|
||||||
return client.AuthDelete(ctx)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Failure to unlock will allow retries, so we do not delete auth.
|
||||||
|
if !errors.Is(err, ErrFailedToUnlock) {
|
||||||
|
if deleteErr := client.AuthDelete(ctx); deleteErr != nil {
|
||||||
|
logUser.WithError(deleteErr).Error("Failed to delete auth")
|
||||||
|
}
|
||||||
|
}
|
||||||
return "", fmt.Errorf("failed to login user: %w", err)
|
return "", fmt.Errorf("failed to login user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,15 +197,16 @@ func (bridge *Bridge) LoginFull(
|
|||||||
getTOTP func() (string, error),
|
getTOTP func() (string, error),
|
||||||
getKeyPass func() ([]byte, error),
|
getKeyPass func() ([]byte, error),
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
logrus.WithField("username", logging.Sensitive(username)).Info("Performing full user login")
|
logUser.WithField("username", logging.Sensitive(username)).Info("Performing full user login")
|
||||||
|
|
||||||
client, auth, err := bridge.LoginAuth(ctx, username, password)
|
// (atanas) the following may need to be modified once HV is merged (its used only for testing; and depends on whether we will test HV related logic)
|
||||||
|
client, auth, err := bridge.LoginAuth(ctx, username, password, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to begin login process: %w", err)
|
return "", fmt.Errorf("failed to begin login process: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if auth.TwoFA.Enabled&proton.HasTOTP != 0 {
|
if auth.TwoFA.Enabled&proton.HasTOTP != 0 {
|
||||||
logrus.WithField("userID", auth.UserID).Info("Requesting TOTP")
|
logUser.WithField("userID", auth.UserID).Info("Requesting TOTP")
|
||||||
|
|
||||||
totp, err := getTOTP()
|
totp, err := getTOTP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -205,7 +221,7 @@ func (bridge *Bridge) LoginFull(
|
|||||||
var keyPass []byte
|
var keyPass []byte
|
||||||
|
|
||||||
if auth.PasswordMode == proton.TwoPasswordMode {
|
if auth.PasswordMode == proton.TwoPasswordMode {
|
||||||
logrus.WithField("userID", auth.UserID).Info("Requesting mailbox password")
|
logUser.WithField("userID", auth.UserID).Info("Requesting mailbox password")
|
||||||
|
|
||||||
userKeyPass, err := getKeyPass()
|
userKeyPass, err := getKeyPass()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -217,12 +233,21 @@ func (bridge *Bridge) LoginFull(
|
|||||||
keyPass = password
|
keyPass = password
|
||||||
}
|
}
|
||||||
|
|
||||||
return bridge.LoginUser(ctx, client, auth, keyPass)
|
userID, err := bridge.LoginUser(ctx, client, auth, keyPass, nil)
|
||||||
|
if err != nil {
|
||||||
|
if deleteErr := client.AuthDelete(ctx); deleteErr != nil {
|
||||||
|
logUser.WithError(err).Error("Failed to delete auth")
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return userID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogoutUser logs out the given user.
|
// LogoutUser logs out the given user.
|
||||||
func (bridge *Bridge) LogoutUser(ctx context.Context, userID string) error {
|
func (bridge *Bridge) LogoutUser(ctx context.Context, userID string) error {
|
||||||
logrus.WithField("userID", userID).Info("Logging out user")
|
logUser.WithField("userID", userID).Info("Logging out user")
|
||||||
|
|
||||||
return safe.LockRet(func() error {
|
return safe.LockRet(func() error {
|
||||||
user, ok := bridge.users[userID]
|
user, ok := bridge.users[userID]
|
||||||
@ -242,7 +267,7 @@ func (bridge *Bridge) LogoutUser(ctx context.Context, userID string) error {
|
|||||||
|
|
||||||
// DeleteUser deletes the given user.
|
// DeleteUser deletes the given user.
|
||||||
func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
|
func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
|
||||||
logrus.WithField("userID", userID).Info("Deleting user")
|
logUser.WithField("userID", userID).Info("Deleting user")
|
||||||
|
|
||||||
syncConfigDir, err := bridge.locator.ProvideIMAPSyncConfigPath()
|
syncConfigDir, err := bridge.locator.ProvideIMAPSyncConfigPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -263,7 +288,7 @@ func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := bridge.vault.DeleteUser(userID); err != nil {
|
if err := bridge.vault.DeleteUser(userID); err != nil {
|
||||||
logrus.WithError(err).Error("Failed to delete vault user")
|
logUser.WithError(err).Error("Failed to delete vault user")
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.publish(events.UserDeleted{
|
bridge.publish(events.UserDeleted{
|
||||||
@ -276,7 +301,7 @@ func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
|
|||||||
|
|
||||||
// SetAddressMode sets the address mode for the given user.
|
// SetAddressMode sets the address mode for the given user.
|
||||||
func (bridge *Bridge) SetAddressMode(ctx context.Context, userID string, mode vault.AddressMode) error {
|
func (bridge *Bridge) SetAddressMode(ctx context.Context, userID string, mode vault.AddressMode) error {
|
||||||
logrus.WithField("userID", userID).WithField("mode", mode).Info("Setting address mode")
|
logUser.WithField("userID", userID).WithField("mode", mode).Info("Setting address mode")
|
||||||
|
|
||||||
return safe.RLockRet(func() error {
|
return safe.RLockRet(func() error {
|
||||||
user, ok := bridge.users[userID]
|
user, ok := bridge.users[userID]
|
||||||
@ -312,9 +337,9 @@ func (bridge *Bridge) SetAddressMode(ctx context.Context, userID string, mode va
|
|||||||
|
|
||||||
// SendBadEventUserFeedback passes the feedback to the given user.
|
// SendBadEventUserFeedback passes the feedback to the given user.
|
||||||
func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string, doResync bool) error {
|
func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string, doResync bool) error {
|
||||||
logrus.WithField("userID", userID).WithField("doResync", doResync).Info("Passing bad event feedback to user")
|
logUser.WithField("userID", userID).WithField("doResync", doResync).Info("Passing bad event feedback to user")
|
||||||
|
|
||||||
return safe.LockRet(func() error {
|
return safe.RLockRet(func() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
user, ok := bridge.users[userID]
|
user, ok := bridge.users[userID]
|
||||||
@ -323,7 +348,7 @@ func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string,
|
|||||||
"Failed to handle event: feedback failed: no such user",
|
"Failed to handle event: feedback failed: no such user",
|
||||||
reporter.Context{"user_id": userID},
|
reporter.Context{"user_id": userID},
|
||||||
); rerr != nil {
|
); rerr != nil {
|
||||||
logrus.WithError(rerr).Error("Failed to report feedback failure")
|
logUser.WithError(rerr).Error("Failed to report feedback failure")
|
||||||
}
|
}
|
||||||
|
|
||||||
return ErrNoSuchUser
|
return ErrNoSuchUser
|
||||||
@ -334,7 +359,7 @@ func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string,
|
|||||||
"Failed to handle event: feedback resync",
|
"Failed to handle event: feedback resync",
|
||||||
reporter.Context{"user_id": userID},
|
reporter.Context{"user_id": userID},
|
||||||
); rerr != nil {
|
); rerr != nil {
|
||||||
logrus.WithError(rerr).Error("Failed to report feedback failure")
|
logUser.WithError(rerr).Error("Failed to report feedback failure")
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.BadEventFeedbackResync(ctx)
|
return user.BadEventFeedbackResync(ctx)
|
||||||
@ -344,7 +369,7 @@ func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string,
|
|||||||
"Failed to handle event: feedback logout",
|
"Failed to handle event: feedback logout",
|
||||||
reporter.Context{"user_id": userID},
|
reporter.Context{"user_id": userID},
|
||||||
); rerr != nil {
|
); rerr != nil {
|
||||||
logrus.WithError(rerr).Error("Failed to report feedback failure")
|
logUser.WithError(rerr).Error("Failed to report feedback failure")
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.logoutUser(ctx, user, true, false, false)
|
bridge.logoutUser(ctx, user, true, false, false)
|
||||||
@ -357,8 +382,8 @@ func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string,
|
|||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) loginUser(ctx context.Context, client *proton.Client, authUID, authRef string, keyPass []byte) (string, error) {
|
func (bridge *Bridge) loginUser(ctx context.Context, client *proton.Client, authUID, authRef string, keyPass []byte, hvDetails *proton.APIHVDetails) (string, error) {
|
||||||
apiUser, err := client.GetUser(ctx)
|
apiUser, err := client.GetUserWithHV(ctx, hvDetails)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get API user: %w", err)
|
return "", fmt.Errorf("failed to get API user: %w", err)
|
||||||
}
|
}
|
||||||
@ -374,9 +399,9 @@ func (bridge *Bridge) loginUser(ctx context.Context, client *proton.Client, auth
|
|||||||
}
|
}
|
||||||
|
|
||||||
if userKR, err := apiUser.Keys.Unlock(saltedKeyPass, nil); err != nil {
|
if userKR, err := apiUser.Keys.Unlock(saltedKeyPass, nil); err != nil {
|
||||||
return "", fmt.Errorf("failed to unlock user keys: %w", err)
|
return "", fmt.Errorf("%w: %w", ErrFailedToUnlock, err)
|
||||||
} else if userKR.CountDecryptionEntities() == 0 {
|
} else if userKR.CountDecryptionEntities() == 0 {
|
||||||
return "", fmt.Errorf("failed to unlock user keys")
|
return "", ErrFailedToUnlock
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bridge.addUser(ctx, client, apiUser, authUID, authRef, saltedKeyPass, true); err != nil {
|
if err := bridge.addUser(ctx, client, apiUser, authUID, authRef, saltedKeyPass, true); err != nil {
|
||||||
@ -388,11 +413,11 @@ func (bridge *Bridge) loginUser(ctx context.Context, client *proton.Client, auth
|
|||||||
|
|
||||||
// loadUsers tries to load each user in the vault that isn't already loaded.
|
// loadUsers tries to load each user in the vault that isn't already loaded.
|
||||||
func (bridge *Bridge) loadUsers(ctx context.Context) error {
|
func (bridge *Bridge) loadUsers(ctx context.Context) error {
|
||||||
logrus.WithField("count", len(bridge.vault.GetUserIDs())).Info("Loading users")
|
logUser.WithField("count", len(bridge.vault.GetUserIDs())).Info("Loading users")
|
||||||
defer logrus.Info("Finished loading users")
|
defer logUser.Info("Finished loading users")
|
||||||
|
|
||||||
return bridge.vault.ForUser(runtime.NumCPU(), func(user *vault.User) error {
|
return bridge.vault.ForUser(runtime.NumCPU(), func(user *vault.User) error {
|
||||||
log := logrus.WithField("userID", user.UserID())
|
log := logUser.WithField("userID", user.UserID())
|
||||||
|
|
||||||
if user.AuthUID() == "" {
|
if user.AuthUID() == "" {
|
||||||
log.Info("User is not connected (skipping)")
|
log.Info("User is not connected (skipping)")
|
||||||
@ -436,7 +461,7 @@ func (bridge *Bridge) loadUser(ctx context.Context, user *vault.User) error {
|
|||||||
if apiErr := new(proton.APIError); errors.As(err, &apiErr) && (apiErr.Code == proton.AuthRefreshTokenInvalid) {
|
if apiErr := new(proton.APIError); errors.As(err, &apiErr) && (apiErr.Code == proton.AuthRefreshTokenInvalid) {
|
||||||
// The session cannot be refreshed, we sign out the user by clearing his auth secrets.
|
// The session cannot be refreshed, we sign out the user by clearing his auth secrets.
|
||||||
if err := user.Clear(); err != nil {
|
if err := user.Clear(); err != nil {
|
||||||
logrus.WithError(err).Warn("Failed to clear user secrets")
|
logUser.WithError(err).Warn("Failed to clear user secrets")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,26 +504,26 @@ func (bridge *Bridge) addUser(
|
|||||||
return fmt.Errorf("failed to add vault user: %w", err)
|
return fmt.Errorf("failed to add vault user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bridge.addUserWithVault(ctx, client, apiUser, vaultUser); err != nil {
|
if err := bridge.addUserWithVault(ctx, client, apiUser, vaultUser, isNew); err != nil {
|
||||||
if _, ok := err.(*resty.ResponseError); ok || isLogin {
|
if _, ok := err.(*resty.ResponseError); ok || isLogin {
|
||||||
logrus.WithError(err).Error("Failed to add user, clearing its secrets from vault")
|
logUser.WithError(err).Error("Failed to add user, clearing its secrets from vault")
|
||||||
|
|
||||||
if err := vaultUser.Clear(); err != nil {
|
if err := vaultUser.Clear(); err != nil {
|
||||||
logrus.WithError(err).Error("Failed to clear user secrets")
|
logUser.WithError(err).Error("Failed to clear user secrets")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logrus.WithError(err).Error("Failed to add user")
|
logUser.WithError(err).Error("Failed to add user")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := vaultUser.Close(); err != nil {
|
if err := vaultUser.Close(); err != nil {
|
||||||
logrus.WithError(err).Error("Failed to close vault user")
|
logUser.WithError(err).Error("Failed to close vault user")
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNew {
|
if isNew {
|
||||||
logrus.Warn("Deleting newly added vault user")
|
logUser.Warn("Deleting newly added vault user")
|
||||||
|
|
||||||
if err := bridge.vault.DeleteUser(apiUser.ID); err != nil {
|
if err := bridge.vault.DeleteUser(apiUser.ID); err != nil {
|
||||||
logrus.WithError(err).Error("Failed to delete vault user")
|
logUser.WithError(err).Error("Failed to delete vault user")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -514,6 +539,7 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
client *proton.Client,
|
client *proton.Client,
|
||||||
apiUser proton.User,
|
apiUser proton.User,
|
||||||
vault *vault.User,
|
vault *vault.User,
|
||||||
|
isNew bool,
|
||||||
) error {
|
) error {
|
||||||
statsPath, err := bridge.locator.ProvideStatsPath()
|
statsPath, err := bridge.locator.ProvideStatsPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -540,7 +566,12 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
bridge.serverManager,
|
bridge.serverManager,
|
||||||
&bridgeEventSubscription{b: bridge},
|
&bridgeEventSubscription{b: bridge},
|
||||||
bridge.syncService,
|
bridge.syncService,
|
||||||
|
bridge.observabilityService,
|
||||||
syncSettingsPath,
|
syncSettingsPath,
|
||||||
|
isNew,
|
||||||
|
bridge.notificationStore,
|
||||||
|
bridge.unleashService.GetFlagValue,
|
||||||
|
bridge.observabilityService.AddMetric,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create user: %w", err)
|
return fmt.Errorf("failed to create user: %w", err)
|
||||||
@ -550,7 +581,7 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
// For example, if the user's addresses change, we need to update them in gluon.
|
// For example, if the user's addresses change, we need to update them in gluon.
|
||||||
bridge.tasks.Once(func(ctx context.Context) {
|
bridge.tasks.Once(func(ctx context.Context) {
|
||||||
async.RangeContext(ctx, user.GetEventCh(), func(event events.Event) {
|
async.RangeContext(ctx, user.GetEventCh(), func(event events.Event) {
|
||||||
logrus.WithFields(logrus.Fields{
|
logUser.WithFields(logrus.Fields{
|
||||||
"userID": apiUser.ID,
|
"userID": apiUser.ID,
|
||||||
"event": event,
|
"event": event,
|
||||||
}).Debug("Received user event")
|
}).Debug("Received user event")
|
||||||
@ -577,7 +608,9 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
|
|
||||||
// As we need at least one user to send heartbeat, try to send it.
|
// As we need at least one user to send heartbeat, try to send it.
|
||||||
defer bridge.goHeartbeat()
|
bridge.heartbeat.start()
|
||||||
|
|
||||||
|
user.PublishEvent(ctx, events.UserLoadedCheckResync{UserID: user.ID()})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -601,14 +634,14 @@ func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI,
|
|||||||
user.SendConfigStatusAbort(ctx, withTelemetry)
|
user.SendConfigStatusAbort(ctx, withTelemetry)
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
logUser.WithFields(logrus.Fields{
|
||||||
"userID": user.ID(),
|
"userID": user.ID(),
|
||||||
"withAPI": withAPI,
|
"withAPI": withAPI,
|
||||||
"withData": withData,
|
"withData": withData,
|
||||||
}).Debug("Logging out user")
|
}).Debug("Logging out user")
|
||||||
|
|
||||||
if err := user.Logout(ctx, withAPI); err != nil {
|
if err := user.Logout(ctx, withAPI); err != nil {
|
||||||
logrus.WithError(err).Error("Failed to logout user")
|
logUser.WithError(err).Error("Failed to logout user")
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.heartbeat.SetNbAccount(len(bridge.users))
|
bridge.heartbeat.SetNbAccount(len(bridge.users))
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -62,7 +62,7 @@ func TestBridge_User_RefreshEvent(t *testing.T) {
|
|||||||
messageIDs = createNumMessages(ctx, t, c, addrID, labelID, 10)
|
messageIDs = createNumMessages(ctx, t, c, addrID, labelID, 10)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ func TestBridge_User_RefreshEvent(t *testing.T) {
|
|||||||
|
|
||||||
require.NoError(t, s.RefreshUser(userID, proton.RefreshMail))
|
require.NoError(t, s.RefreshUser(userID, proton.RefreshMail))
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
syncCh, closeCh := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, closeCh := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
|
|
||||||
require.Equal(t, userID, (<-syncCh).UserID)
|
require.Equal(t, userID, (<-syncCh).UserID)
|
||||||
@ -82,7 +82,7 @@ func TestBridge_User_RefreshEvent(t *testing.T) {
|
|||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
|
withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) {
|
||||||
createNumMessages(ctx, t, c, addrID, labelID, 10)
|
createNumMessages(ctx, t, c, addrID, labelID, 10)
|
||||||
})
|
})
|
||||||
@ -139,9 +139,6 @@ func test_badMessage_badEvent(userFeedback func(t *testing.T, ctx context.Contex
|
|||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
smtpWaiter := waitForSMTPServerReady(bridge)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
|
|
||||||
var messageIDs []string
|
var messageIDs []string
|
||||||
@ -177,8 +174,6 @@ func test_badMessage_badEvent(userFeedback func(t *testing.T, ctx context.Contex
|
|||||||
|
|
||||||
userFeedback(t, ctx, bridge, badUserID)
|
userFeedback(t, ctx, bridge, badUserID)
|
||||||
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -196,10 +191,7 @@ func TestBridge_User_BadMessage_NoBadEvent(t *testing.T) {
|
|||||||
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
|
createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
smtpWaiter := waitForSMTPServerReady(bridge)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
|
|
||||||
var messageIDs []string
|
var messageIDs []string
|
||||||
@ -223,7 +215,6 @@ func TestBridge_User_BadMessage_NoBadEvent(t *testing.T) {
|
|||||||
require.NoError(t, c.DeleteMessage(ctx, messageIDs...))
|
require.NoError(t, c.DeleteMessage(ctx, messageIDs...))
|
||||||
})
|
})
|
||||||
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -377,7 +368,7 @@ func TestBridge_User_Network_NoBadEvents(t *testing.T) {
|
|||||||
_, addrID, err := s.CreateUser("user", password)
|
_, addrID, err := s.CreateUser("user", password)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
|
|
||||||
// Create 10 more messages for the user, generating events.
|
// Create 10 more messages for the user, generating events.
|
||||||
@ -463,7 +454,7 @@ func TestBridge_User_UpdateDraft(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Initially sync the user.
|
// Initially sync the user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -496,7 +487,7 @@ func TestBridge_User_UpdateDraft(t *testing.T) {
|
|||||||
require.Empty(t, draft.ReplyTos)
|
require.Empty(t, draft.ReplyTos)
|
||||||
|
|
||||||
// Process those events
|
// Process those events
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -522,7 +513,7 @@ func TestBridge_User_UpdateDraftAndCreateOtherMessage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Initially sync the user.
|
// Initially sync the user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -554,7 +545,7 @@ func TestBridge_User_UpdateDraftAndCreateOtherMessage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Process those events
|
// Process those events
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -582,7 +573,7 @@ func TestBridge_User_UpdateDraftAndCreateOtherMessage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Process those events.
|
// Process those events.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -590,7 +581,7 @@ func TestBridge_User_UpdateDraftAndCreateOtherMessage(t *testing.T) {
|
|||||||
require.NoError(t, c.MarkMessagesUnread(ctx, res[0].MessageID))
|
require.NoError(t, c.MarkMessagesUnread(ctx, res[0].MessageID))
|
||||||
|
|
||||||
// Process those events.
|
// Process those events.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -604,7 +595,7 @@ func TestBridge_User_SendDraftRemoveDraftFlag(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Initially sync the user.
|
// Initially sync the user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userLoginAndSync(ctx, t, bridge, "user", password)
|
userLoginAndSync(ctx, t, bridge, "user", password)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -637,7 +628,7 @@ func TestBridge_User_SendDraftRemoveDraftFlag(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Process those events
|
// Process those events
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
|
|
||||||
info, err := bridge.QueryUserInfo("user")
|
info, err := bridge.QueryUserInfo("user")
|
||||||
@ -676,7 +667,7 @@ func TestBridge_User_SendDraftRemoveDraftFlag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process those events; the draft will move to the sent folder and lose the draft flag.
|
// Process those events; the draft will move to the sent folder and lose the draft flag.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userContinueEventProcess(ctx, t, s, bridge)
|
userContinueEventProcess(ctx, t, s, bridge)
|
||||||
|
|
||||||
info, err := bridge.QueryUserInfo("user")
|
info, err := bridge.QueryUserInfo("user")
|
||||||
@ -706,7 +697,7 @@ func TestBridge_User_DisableEnableAddress(t *testing.T) {
|
|||||||
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password)
|
aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.NoError(t, getErr(bridge.LoginFull(ctx, "user", password, nil, nil)))
|
require.NoError(t, getErr(bridge.LoginFull(ctx, "user", password, nil, nil)))
|
||||||
|
|
||||||
// Initially we should list the address.
|
// Initially we should list the address.
|
||||||
@ -720,7 +711,7 @@ func TestBridge_User_DisableEnableAddress(t *testing.T) {
|
|||||||
require.NoError(t, c.DisableAddress(ctx, aliasID))
|
require.NoError(t, c.DisableAddress(ctx, aliasID))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Eventually we shouldn't list the address.
|
// Eventually we shouldn't list the address.
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
info, err := bridge.QueryUserInfo("user")
|
info, err := bridge.QueryUserInfo("user")
|
||||||
@ -735,7 +726,7 @@ func TestBridge_User_DisableEnableAddress(t *testing.T) {
|
|||||||
require.NoError(t, c.EnableAddress(ctx, aliasID))
|
require.NoError(t, c.EnableAddress(ctx, aliasID))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Eventually we should list the address.
|
// Eventually we should list the address.
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
info, err := bridge.QueryUserInfo("user")
|
info, err := bridge.QueryUserInfo("user")
|
||||||
@ -762,7 +753,7 @@ func TestBridge_User_CreateDisabledAddress(t *testing.T) {
|
|||||||
require.NoError(t, c.DisableAddress(ctx, aliasID))
|
require.NoError(t, c.DisableAddress(ctx, aliasID))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.NoError(t, getErr(bridge.LoginFull(ctx, "user", password, nil, nil)))
|
require.NoError(t, getErr(bridge.LoginFull(ctx, "user", password, nil, nil)))
|
||||||
|
|
||||||
// Initially we shouldn't list the address.
|
// Initially we shouldn't list the address.
|
||||||
@ -775,21 +766,12 @@ func TestBridge_User_CreateDisabledAddress(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_User_HandleParentLabelRename(t *testing.T) {
|
func TestBridge_User_HandleParentLabelRename(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
imapWaiter := waitForIMAPServerReady(bridge)
|
|
||||||
defer imapWaiter.Done()
|
|
||||||
|
|
||||||
smtpWaiter := waitForSMTPServerReady(bridge)
|
|
||||||
defer smtpWaiter.Done()
|
|
||||||
|
|
||||||
require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil)))
|
require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil)))
|
||||||
|
|
||||||
info, err := bridge.QueryUserInfo(username)
|
info, err := bridge.QueryUserInfo(username)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
imapWaiter.Wait()
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
cli, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
cli, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, cli.Login(info.Addresses[0], string(info.BridgePass)))
|
require.NoError(t, cli.Login(info.Addresses[0], string(info.BridgePass)))
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -36,6 +36,9 @@ func (bridge *Bridge) handleUserEvent(ctx context.Context, user *user.User, even
|
|||||||
case events.UserBadEvent:
|
case events.UserBadEvent:
|
||||||
bridge.handleUserBadEvent(ctx, user, event)
|
bridge.handleUserBadEvent(ctx, user, event)
|
||||||
|
|
||||||
|
case events.UserLoadedCheckResync:
|
||||||
|
user.VerifyResyncAndExecute()
|
||||||
|
|
||||||
case events.UncategorizedEventError:
|
case events.UncategorizedEventError:
|
||||||
bridge.handleUncategorizedErrorEvent(event)
|
bridge.handleUncategorizedErrorEvent(event)
|
||||||
}
|
}
|
||||||
@ -49,7 +52,7 @@ func (bridge *Bridge) handleUserDeauth(ctx context.Context, user *user.User) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) handleUserBadEvent(ctx context.Context, user *user.User, event events.UserBadEvent) {
|
func (bridge *Bridge) handleUserBadEvent(ctx context.Context, user *user.User, event events.UserBadEvent) {
|
||||||
safe.Lock(func() {
|
safe.RLock(func() {
|
||||||
if rerr := bridge.reporter.ReportMessageWithContext("Failed to handle event", reporter.Context{
|
if rerr := bridge.reporter.ReportMessageWithContext("Failed to handle event", reporter.Context{
|
||||||
"user_id": user.ID(),
|
"user_id": user.ID(),
|
||||||
"old_event_id": event.OldEventID,
|
"old_event_id": event.OldEventID,
|
||||||
@ -58,7 +61,7 @@ func (bridge *Bridge) handleUserBadEvent(ctx context.Context, user *user.User, e
|
|||||||
"error": event.Error,
|
"error": event.Error,
|
||||||
"error_type": internal.ErrCauseType(event.Error),
|
"error_type": internal.ErrCauseType(event.Error),
|
||||||
}); rerr != nil {
|
}); rerr != nil {
|
||||||
logrus.WithError(rerr).Error("Failed to report failed event handling")
|
logrus.WithField("pkg", "bridge/event").WithError(rerr).Error("Failed to report failed event handling")
|
||||||
}
|
}
|
||||||
|
|
||||||
user.OnBadEvent(ctx)
|
user.OnBadEvent(ctx)
|
||||||
@ -70,6 +73,6 @@ func (bridge *Bridge) handleUncategorizedErrorEvent(event events.UncategorizedEv
|
|||||||
"error_type": internal.ErrCauseType(event.Error),
|
"error_type": internal.ErrCauseType(event.Error),
|
||||||
"error": event.Error,
|
"error": event.Error,
|
||||||
}); rerr != nil {
|
}); rerr != nil {
|
||||||
logrus.WithError(rerr).Error("Failed to report failed event handling")
|
logrus.WithField("pkg", "bridge/event").WithError(rerr).Error("Failed to report failed event handling")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -35,12 +35,12 @@ import (
|
|||||||
|
|
||||||
func TestBridge_WithoutUsers(t *testing.T) {
|
func TestBridge_WithoutUsers(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.Empty(t, bridge.GetUserIDs())
|
require.Empty(t, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.Empty(t, bridge.GetUserIDs())
|
require.Empty(t, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
})
|
})
|
||||||
@ -49,7 +49,7 @@ func TestBridge_WithoutUsers(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_Login(t *testing.T) {
|
func TestBridge_Login(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -69,7 +69,7 @@ func TestBridge_Login_DropConn(t *testing.T) {
|
|||||||
defer func() { _ = dropListener.Close() }()
|
defer func() { _ = dropListener.Close() }()
|
||||||
|
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -96,7 +96,7 @@ func TestBridge_Login_DropConn(t *testing.T) {
|
|||||||
return 0, false
|
return 0, false
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// The user is eventually connected.
|
// The user is eventually connected.
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
return len(bridge.GetUserIDs()) == 1 && len(getConnectedUserIDs(t, bridge)) == 1
|
return len(bridge.GetUserIDs()) == 1 && len(getConnectedUserIDs(t, bridge)) == 1
|
||||||
@ -107,7 +107,7 @@ func TestBridge_Login_DropConn(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_LoginTwice(t *testing.T) {
|
func TestBridge_LoginTwice(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -125,7 +125,7 @@ func TestBridge_LoginTwice(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_LoginLogoutLogin(t *testing.T) {
|
func TestBridge_LoginLogoutLogin(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ func TestBridge_LoginLogoutLogin(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_LoginDeleteLogin(t *testing.T) {
|
func TestBridge_LoginDeleteLogin(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ func TestBridge_LoginDeleteLogin(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_LoginDeauthLogin(t *testing.T) {
|
func TestBridge_LoginDeauthLogin(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -215,7 +215,7 @@ func TestBridge_LoginDeauthRestartLogin(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -235,7 +235,7 @@ func TestBridge_LoginDeauthRestartLogin(t *testing.T) {
|
|||||||
require.IsType(t, events.UserDeauth{}, <-eventCh)
|
require.IsType(t, events.UserDeauth{}, <-eventCh)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// The user should be disconnected at startup.
|
// The user should be disconnected at startup.
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
@ -257,7 +257,7 @@ func TestBridge_LoginExpireLogin(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
s.SetAuthLife(authLife)
|
s.SetAuthLife(authLife)
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user. Its auth will only be valid for a short time.
|
// Login the user. Its auth will only be valid for a short time.
|
||||||
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ func TestBridge_FailToLoad(t *testing.T) {
|
|||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
// Login the user.
|
// Login the user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -283,7 +283,7 @@ func TestBridge_FailToLoad(t *testing.T) {
|
|||||||
require.NoError(t, s.RevokeUser(userID))
|
require.NoError(t, s.RevokeUser(userID))
|
||||||
|
|
||||||
// When bridge starts, the user will not be logged in.
|
// When bridge starts, the user will not be logged in.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
})
|
})
|
||||||
@ -295,7 +295,7 @@ func TestBridge_LoadWithoutInternet(t *testing.T) {
|
|||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
// Login the user.
|
// Login the user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -303,7 +303,7 @@ func TestBridge_LoadWithoutInternet(t *testing.T) {
|
|||||||
netCtl.Disable()
|
netCtl.Disable()
|
||||||
|
|
||||||
// Start bridge without internet.
|
// Start bridge without internet.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Initially, users are not connected.
|
// Initially, users are not connected.
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
@ -325,11 +325,11 @@ func TestBridge_LoginRestart(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
||||||
})
|
})
|
||||||
@ -340,7 +340,7 @@ func TestBridge_LoginLogoutRestart(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -348,7 +348,7 @@ func TestBridge_LoginLogoutRestart(t *testing.T) {
|
|||||||
require.NoError(t, bridge.LogoutUser(ctx, userID))
|
require.NoError(t, bridge.LogoutUser(ctx, userID))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// The user is still disconnected.
|
// The user is still disconnected.
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
@ -360,7 +360,7 @@ func TestBridge_LoginDeleteRestart(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -368,7 +368,7 @@ func TestBridge_LoginDeleteRestart(t *testing.T) {
|
|||||||
require.NoError(t, bridge.DeleteUser(ctx, userID))
|
require.NoError(t, bridge.DeleteUser(ctx, userID))
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// The user is still gone.
|
// The user is still gone.
|
||||||
require.Empty(t, bridge.GetUserIDs())
|
require.Empty(t, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
@ -384,7 +384,7 @@ func TestBridge_FailLoginRecover(t *testing.T) {
|
|||||||
|
|
||||||
// Log the user in, wait for it to sync, then log it out.
|
// Log the user in, wait for it to sync, then log it out.
|
||||||
// (We don't want to count message sync data in the test.)
|
// (We don't want to count message sync data in the test.)
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -396,7 +396,7 @@ func TestBridge_FailLoginRecover(t *testing.T) {
|
|||||||
var total uint64
|
var total uint64
|
||||||
|
|
||||||
// Now that the user is synced, we can measure exactly how much data is needed during login.
|
// Now that the user is synced, we can measure exactly how much data is needed during login.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
total = countBytesRead(netCtl, func() {
|
total = countBytesRead(netCtl, func() {
|
||||||
must(bridge.LoginFull(ctx, username, password, nil, nil))
|
must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
})
|
})
|
||||||
@ -405,7 +405,7 @@ func TestBridge_FailLoginRecover(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Now simulate failing to login.
|
// Now simulate failing to login.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Simulate a partial read.
|
// Simulate a partial read.
|
||||||
netCtl.SetReadLimit(i * total / 10)
|
netCtl.SetReadLimit(i * total / 10)
|
||||||
|
|
||||||
@ -421,7 +421,7 @@ func TestBridge_FailLoginRecover(t *testing.T) {
|
|||||||
netCtl.SetReadLimit(0)
|
netCtl.SetReadLimit(0)
|
||||||
|
|
||||||
// We should now be able to log the user in.
|
// We should now be able to log the user in.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil)))
|
require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil)))
|
||||||
|
|
||||||
// The user should be there, now connected.
|
// The user should be there, now connected.
|
||||||
@ -441,7 +441,7 @@ func TestBridge_FailLoadRecover(t *testing.T) {
|
|||||||
|
|
||||||
// Log the user in and wait for it to sync.
|
// Log the user in and wait for it to sync.
|
||||||
// (We don't want to count message sync data in the test.)
|
// (We don't want to count message sync data in the test.)
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -451,7 +451,7 @@ func TestBridge_FailLoadRecover(t *testing.T) {
|
|||||||
|
|
||||||
// See how much data it takes to load the user at startup.
|
// See how much data it takes to load the user at startup.
|
||||||
total := countBytesRead(netCtl, func() {
|
total := countBytesRead(netCtl, func() {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(_ *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -460,7 +460,7 @@ func TestBridge_FailLoadRecover(t *testing.T) {
|
|||||||
netCtl.SetReadLimit(i * total / 10)
|
netCtl.SetReadLimit(i * total / 10)
|
||||||
|
|
||||||
// We should fail to load the user; it should be listed but disconnected.
|
// We should fail to load the user; it should be listed but disconnected.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
})
|
})
|
||||||
@ -469,7 +469,7 @@ func TestBridge_FailLoadRecover(t *testing.T) {
|
|||||||
netCtl.SetReadLimit(0)
|
netCtl.SetReadLimit(0)
|
||||||
|
|
||||||
// We should now be able to load the user.
|
// We should now be able to load the user.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
||||||
})
|
})
|
||||||
@ -484,7 +484,7 @@ func TestBridge_BridgePass(t *testing.T) {
|
|||||||
|
|
||||||
var pass []byte
|
var pass []byte
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -501,7 +501,7 @@ func TestBridge_BridgePass(t *testing.T) {
|
|||||||
require.Equal(t, pass, pass)
|
require.Equal(t, pass, pass)
|
||||||
})
|
})
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// The bridge should load the user.
|
// The bridge should load the user.
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
||||||
@ -514,7 +514,7 @@ func TestBridge_BridgePass(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_AddressMode(t *testing.T) {
|
func TestBridge_AddressMode(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -552,7 +552,7 @@ func TestBridge_AddressMode(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_LoginLogoutRepeated(t *testing.T) {
|
func TestBridge_LoginLogoutRepeated(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
// Log the user in.
|
// Log the user in.
|
||||||
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID := must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
@ -568,7 +568,7 @@ func TestBridge_LogoutOffline(t *testing.T) {
|
|||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
var userID string
|
var userID string
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
userID = must(bridge.LoginFull(ctx, username, password, nil, nil))
|
||||||
|
|
||||||
@ -590,7 +590,7 @@ func TestBridge_LogoutOffline(t *testing.T) {
|
|||||||
// Go back online.
|
// Go back online.
|
||||||
netCtl.Enable()
|
netCtl.Enable()
|
||||||
|
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// The user is still disconnected.
|
// The user is still disconnected.
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
||||||
require.Empty(t, getConnectedUserIDs(t, bridge))
|
require.Empty(t, getConnectedUserIDs(t, bridge))
|
||||||
@ -600,7 +600,7 @@ func TestBridge_LogoutOffline(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_DeleteDisconnected(t *testing.T) {
|
func TestBridge_DeleteDisconnected(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -628,7 +628,7 @@ func TestBridge_DeleteDisconnected(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_DeleteOffline(t *testing.T) {
|
func TestBridge_DeleteOffline(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -652,7 +652,7 @@ func TestBridge_DeleteOffline(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_UserInfo_Alias(t *testing.T) {
|
func TestBridge_UserInfo_Alias(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Create a new user.
|
// Create a new user.
|
||||||
userID, _, err := s.CreateUser("primary", []byte("password"))
|
userID, _, err := s.CreateUser("primary", []byte("password"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -675,7 +675,7 @@ func TestBridge_UserInfo_Alias(t *testing.T) {
|
|||||||
|
|
||||||
func TestBridge_User_Refresh(t *testing.T) {
|
func TestBridge_User_Refresh(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) {
|
||||||
// Get a channel of sync started events.
|
// Get a channel of sync started events.
|
||||||
syncStartCh, done := chToType[events.Event, events.SyncStarted](bridge.GetEvents(events.SyncStarted{}))
|
syncStartCh, done := chToType[events.Event, events.SyncStarted](bridge.GetEvents(events.SyncStarted{}))
|
||||||
defer done()
|
defer done()
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -356,6 +356,10 @@ func removeCertTrustCGo(buffer *C.char, size C.ulonglong) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func osSupportCertInstall() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// installCert installs a certificate in the keychain. The certificate is added to the keychain and it is set as trusted.
|
// 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.
|
// 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 {
|
func installCert(certPEM []byte) error {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -28,6 +28,7 @@ import (
|
|||||||
func TestCertInKeychain(t *testing.T) {
|
func TestCertInKeychain(t *testing.T) {
|
||||||
// no trust settings change is performed, so this test will not trigger an OS security prompt.
|
// no trust settings change is performed, so this test will not trigger an OS security prompt.
|
||||||
certPEM := generatePEMCertificate(t)
|
certPEM := generatePEMCertificate(t)
|
||||||
|
require.True(t, osSupportCertInstall())
|
||||||
require.False(t, isCertInKeychain(certPEM))
|
require.False(t, isCertInKeychain(certPEM))
|
||||||
require.NoError(t, addCertToKeychain(certPEM))
|
require.NoError(t, addCertToKeychain(certPEM))
|
||||||
require.True(t, isCertInKeychain(certPEM))
|
require.True(t, isCertInKeychain(certPEM))
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
package certs
|
package certs
|
||||||
|
|
||||||
|
func osSupportCertInstall() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func installCert([]byte) error {
|
func installCert([]byte) error {
|
||||||
return nil // Linux doesn't have a root cert store.
|
return nil // Linux doesn't have a root cert store.
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
package certs
|
package certs
|
||||||
|
|
||||||
|
func osSupportCertInstall() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func installCert([]byte) error {
|
func installCert([]byte) error {
|
||||||
return nil // NOTE(GODT-986): Install certs to root cert store?
|
return nil // NOTE(GODT-986): Install certs to root cert store?
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -37,6 +37,10 @@ func NewInstaller() *Installer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (installer *Installer) OSSupportCertInstall() bool {
|
||||||
|
return osSupportCertInstall()
|
||||||
|
}
|
||||||
|
|
||||||
func (installer *Installer) InstallCert(certPEM []byte) error {
|
func (installer *Installer) InstallCert(certPEM []byte) error {
|
||||||
installer.log.Info("Installing the Bridge TLS certificate in the OS keychain")
|
installer.log.Info("Installing the Bridge TLS certificate in the OS keychain")
|
||||||
|
|
||||||
@ -64,3 +68,15 @@ func (installer *Installer) UninstallCert(certPEM []byte) error {
|
|||||||
func (installer *Installer) IsCertInstalled(certPEM []byte) bool {
|
func (installer *Installer) IsCertInstalled(certPEM []byte) bool {
|
||||||
return isCertInstalled(certPEM)
|
return isCertInstalled(certPEM)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogCertInstallStatus reports the current status of the certificate installation in the log.
|
||||||
|
// If certificate installation is not supported on the platform, this function does nothing.
|
||||||
|
func (installer *Installer) LogCertInstallStatus(certPEM []byte) {
|
||||||
|
if installer.OSSupportCertInstall() {
|
||||||
|
if installer.IsCertInstalled(certPEM) {
|
||||||
|
installer.log.Info("The Bridge TLS certificate is installed in the OS keychain")
|
||||||
|
} else {
|
||||||
|
installer.log.Info("The Bridge TLS certificate is not installed in the OS keychain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -21,6 +21,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||||
@ -39,10 +40,10 @@ func (c *AppleMail) Configure(
|
|||||||
hostname string,
|
hostname string,
|
||||||
imapPort, smtpPort int,
|
imapPort, smtpPort int,
|
||||||
imapSSL, smtpSSL bool,
|
imapSSL, smtpSSL bool,
|
||||||
username, addresses string,
|
username, displayName, addresses string,
|
||||||
password []byte,
|
password []byte,
|
||||||
) error {
|
) error {
|
||||||
mc := prepareMobileConfig(hostname, imapPort, smtpPort, imapSSL, smtpSSL, username, addresses, password)
|
mc := prepareMobileConfig(hostname, imapPort, smtpPort, imapSSL, smtpSSL, username, displayName, addresses, password)
|
||||||
|
|
||||||
confPath, err := saveConfigTemporarily(mc)
|
confPath, err := saveConfigTemporarily(mc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -66,28 +67,28 @@ func prepareMobileConfig(
|
|||||||
hostname string,
|
hostname string,
|
||||||
imapPort, smtpPort int,
|
imapPort, smtpPort int,
|
||||||
imapSSL, smtpSSL bool,
|
imapSSL, smtpSSL bool,
|
||||||
username, addresses string,
|
username, displayName, addresses string,
|
||||||
password []byte,
|
password []byte,
|
||||||
) *mobileconfig.Config {
|
) *mobileconfig.Config {
|
||||||
return &mobileconfig.Config{
|
return &mobileconfig.Config{
|
||||||
DisplayName: username,
|
DisplayName: escapeXMLString(username),
|
||||||
EmailAddress: addresses,
|
EmailAddress: escapeXMLString(addresses),
|
||||||
AccountName: username,
|
AccountName: escapeXMLString(displayName),
|
||||||
AccountDescription: username,
|
AccountDescription: escapeXMLString(username),
|
||||||
Identifier: "protonmail " + username + strconv.FormatInt(time.Now().Unix(), 10),
|
Identifier: escapeXMLString("protonmail " + username + strconv.FormatInt(time.Now().Unix(), 10)),
|
||||||
IMAP: &mobileconfig.IMAP{
|
IMAP: &mobileconfig.IMAP{
|
||||||
Hostname: hostname,
|
Hostname: escapeXMLString(hostname),
|
||||||
Port: imapPort,
|
Port: imapPort,
|
||||||
TLS: imapSSL,
|
TLS: imapSSL,
|
||||||
Username: username,
|
Username: escapeXMLString(username),
|
||||||
Password: string(password),
|
Password: escapeXMLString(string(password)),
|
||||||
},
|
},
|
||||||
SMTP: &mobileconfig.SMTP{
|
SMTP: &mobileconfig.SMTP{
|
||||||
Hostname: hostname,
|
Hostname: escapeXMLString(hostname),
|
||||||
Port: smtpPort,
|
Port: smtpPort,
|
||||||
TLS: smtpSSL,
|
TLS: smtpSSL,
|
||||||
Username: username,
|
Username: escapeXMLString(username),
|
||||||
Password: string(password),
|
Password: escapeXMLString(string(password)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,3 +122,13 @@ func saveConfigTemporarily(mc *mobileconfig.Config) (fname string, err error) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// escapeXMLString replace all occurrences of the 5 characters `&`, `<`, `>`, `"` and `'` by their respective escaped version as per the XML spec.
|
||||||
|
// https://www.w3.org/TR/xml/#syntax
|
||||||
|
func escapeXMLString(input string) string {
|
||||||
|
result := strings.ReplaceAll(input, `&`, `&`)
|
||||||
|
result = strings.ReplaceAll(result, `<`, `<`)
|
||||||
|
result = strings.ReplaceAll(result, `>`, `>`)
|
||||||
|
result = strings.ReplaceAll(result, `"`, `"`)
|
||||||
|
return strings.ReplaceAll(result, `'`, `'`)
|
||||||
|
}
|
||||||
|
|||||||
38
internal/clientconfig/applemail_test.go
Normal file
38
internal/clientconfig/applemail_test.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package clientconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEscapeXMLString(t *testing.T) {
|
||||||
|
require.Equal(t, escapeXMLString(`abc&&''""<<>>def`), `abc&&''""<<>>def`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test requires human interaction (user configuration profile installation prompt). It is for debugging purpose and is disabled by default.
|
||||||
|
func _TestInstallCert(t *testing.T) { //nolint:unused
|
||||||
|
require.NoError(
|
||||||
|
t,
|
||||||
|
(&AppleMail{}).Configure(`127.0.0.1`, 1143, 1025, true, false, `user&>>`, `<<abc&&'"def>>`, `user&a`, []byte(`ir8R9vhdNXyB7isWzhyEkQ`)),
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -95,6 +95,13 @@ func (status *ConfigurationStatus) IsPending() bool {
|
|||||||
return !status.Data.DataV1.PendingSince.IsZero()
|
return !status.Data.DataV1.PendingSince.IsZero()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (status *ConfigurationStatus) isPendingSinceMin() int {
|
||||||
|
if min := int(time.Since(status.Data.DataV1.PendingSince).Minutes()); min > 0 {
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (status *ConfigurationStatus) IsFromFailure() bool {
|
func (status *ConfigurationStatus) IsFromFailure() bool {
|
||||||
status.DataLock.RLock()
|
status.DataLock.RLock()
|
||||||
defer status.DataLock.RUnlock()
|
defer status.DataLock.RUnlock()
|
||||||
@ -128,7 +135,7 @@ func (status *ConfigurationStatus) ApplyProgress() error {
|
|||||||
return status.Save()
|
return status.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (status *ConfigurationStatus) RecordLinkClicked(link uint) error {
|
func (status *ConfigurationStatus) RecordLinkClicked(link uint64) error {
|
||||||
status.DataLock.Lock()
|
status.DataLock.Lock()
|
||||||
defer status.DataLock.Unlock()
|
defer status.DataLock.Unlock()
|
||||||
|
|
||||||
@ -191,11 +198,11 @@ func (data *ConfigurationStatusData) init() {
|
|||||||
data.DataV1.FailureDetails = ""
|
data.DataV1.FailureDetails = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (data *ConfigurationStatusData) setClickedLink(pos uint) {
|
func (data *ConfigurationStatusData) setClickedLink(pos uint64) {
|
||||||
data.DataV1.ClickedLink |= 1 << pos
|
data.DataV1.ClickedLink |= 1 << pos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (data *ConfigurationStatusData) hasLinkClicked(pos uint) bool {
|
func (data *ConfigurationStatusData) hasLinkClicked(pos uint64) bool {
|
||||||
val := data.DataV1.ClickedLink & (1 << pos)
|
val := data.DataV1.ClickedLink & (1 << pos)
|
||||||
return val > 0
|
return val > 0
|
||||||
}
|
}
|
||||||
@ -204,7 +211,7 @@ func (data *ConfigurationStatusData) clickedLinkToString() string {
|
|||||||
var str = ""
|
var str = ""
|
||||||
var first = true
|
var first = true
|
||||||
for i := 0; i < 64; i++ {
|
for i := 0; i < 64; i++ {
|
||||||
if data.hasLinkClicked(uint(i)) {
|
if data.hasLinkClicked(uint64(i)) {
|
||||||
if !first {
|
if !first {
|
||||||
str += ","
|
str += ","
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -19,7 +19,6 @@ package configstatus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigAbortValues struct {
|
type ConfigAbortValues struct {
|
||||||
@ -41,17 +40,20 @@ type ConfigAbortData struct {
|
|||||||
|
|
||||||
type ConfigAbortBuilder struct{}
|
type ConfigAbortBuilder struct{}
|
||||||
|
|
||||||
func (*ConfigAbortBuilder) New(data *ConfigurationStatusData) ConfigAbortData {
|
func (*ConfigAbortBuilder) New(config *ConfigurationStatus) ConfigAbortData {
|
||||||
|
config.DataLock.RLock()
|
||||||
|
defer config.DataLock.RUnlock()
|
||||||
|
|
||||||
return ConfigAbortData{
|
return ConfigAbortData{
|
||||||
MeasurementGroup: "bridge.any.configuration",
|
MeasurementGroup: "bridge.any.configuration",
|
||||||
Event: "bridge_config_abort",
|
Event: "bridge_config_abort",
|
||||||
Values: ConfigSuccessValues{
|
Values: ConfigSuccessValues{
|
||||||
Duration: int(time.Since(data.DataV1.PendingSince).Minutes()),
|
Duration: config.isPendingSinceMin(),
|
||||||
},
|
},
|
||||||
Dimensions: ConfigSuccessDimensions{
|
Dimensions: ConfigSuccessDimensions{
|
||||||
ReportClick: strconv.FormatBool(data.DataV1.ReportClick),
|
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
||||||
ReportSent: strconv.FormatBool(data.DataV1.ReportSent),
|
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
||||||
ClickedLink: data.clickedLinkToString(),
|
ClickedLink: config.Data.clickedLinkToString(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -33,7 +33,7 @@ func TestConfigurationAbort_default(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var builder = configstatus.ConfigAbortBuilder{}
|
var builder = configstatus.ConfigAbortBuilder{}
|
||||||
req := builder.New(config.Data)
|
req := builder.New(config)
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
require.Equal(t, "bridge_config_abort", req.Event)
|
require.Equal(t, "bridge_config_abort", req.Event)
|
||||||
@ -64,7 +64,7 @@ func TestConfigurationAbort_fed(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var builder = configstatus.ConfigAbortBuilder{}
|
var builder = configstatus.ConfigAbortBuilder{}
|
||||||
req := builder.New(config.Data)
|
req := builder.New(config)
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
require.Equal(t, "bridge_config_abort", req.Event)
|
require.Equal(t, "bridge_config_abort", req.Event)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -33,13 +33,16 @@ type ConfigProgressData struct {
|
|||||||
|
|
||||||
type ConfigProgressBuilder struct{}
|
type ConfigProgressBuilder struct{}
|
||||||
|
|
||||||
func (*ConfigProgressBuilder) New(data *ConfigurationStatusData) ConfigProgressData {
|
func (*ConfigProgressBuilder) New(config *ConfigurationStatus) ConfigProgressData {
|
||||||
|
config.DataLock.RLock()
|
||||||
|
defer config.DataLock.RUnlock()
|
||||||
|
|
||||||
return ConfigProgressData{
|
return ConfigProgressData{
|
||||||
MeasurementGroup: "bridge.any.configuration",
|
MeasurementGroup: "bridge.any.configuration",
|
||||||
Event: "bridge_config_progress",
|
Event: "bridge_config_progress",
|
||||||
Values: ConfigProgressValues{
|
Values: ConfigProgressValues{
|
||||||
NbDay: numberOfDay(time.Now(), data.DataV1.PendingSince),
|
NbDay: numberOfDay(time.Now(), config.Data.DataV1.PendingSince),
|
||||||
NbDaySinceLast: numberOfDay(time.Now(), data.DataV1.LastProgress),
|
NbDaySinceLast: numberOfDay(time.Now(), config.Data.DataV1.LastProgress),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,10 +52,7 @@ func numberOfDay(now, prev time.Time) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if now.Year() > prev.Year() {
|
if now.Year() > prev.Year() {
|
||||||
if now.YearDay() > prev.YearDay() {
|
return (365 * (now.Year() - prev.Year())) + now.YearDay() - prev.YearDay()
|
||||||
return 365 + (now.YearDay() - prev.YearDay())
|
|
||||||
}
|
|
||||||
return (prev.YearDay() + now.YearDay()) - 365
|
|
||||||
} else if now.YearDay() > prev.YearDay() {
|
} else if now.YearDay() > prev.YearDay() {
|
||||||
return now.YearDay() - prev.YearDay()
|
return now.YearDay() - prev.YearDay()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -33,7 +33,7 @@ func TestConfigurationProgress_default(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var builder = configstatus.ConfigProgressBuilder{}
|
var builder = configstatus.ConfigProgressBuilder{}
|
||||||
req := builder.New(config.Data)
|
req := builder.New(config)
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
require.Equal(t, "bridge_config_progress", req.Event)
|
require.Equal(t, "bridge_config_progress", req.Event)
|
||||||
@ -62,10 +62,39 @@ func TestConfigurationProgress_fed(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var builder = configstatus.ConfigProgressBuilder{}
|
var builder = configstatus.ConfigProgressBuilder{}
|
||||||
req := builder.New(config.Data)
|
req := builder.New(config)
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
require.Equal(t, "bridge_config_progress", req.Event)
|
require.Equal(t, "bridge_config_progress", req.Event)
|
||||||
require.Equal(t, 5, req.Values.NbDay)
|
require.Equal(t, 5, req.Values.NbDay)
|
||||||
require.Equal(t, 2, req.Values.NbDaySinceLast)
|
require.Equal(t, 2, req.Values.NbDaySinceLast)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigurationProgress_fed_year_change(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "dummy.json")
|
||||||
|
var data = configstatus.ConfigurationStatusData{
|
||||||
|
Metadata: configstatus.Metadata{Version: "1.0.0"},
|
||||||
|
DataV1: configstatus.DataV1{
|
||||||
|
PendingSince: time.Now().AddDate(-1, 0, -5),
|
||||||
|
LastProgress: time.Now().AddDate(0, 0, -2),
|
||||||
|
Autoconf: "Mr TBird",
|
||||||
|
ClickedLink: 42,
|
||||||
|
ReportSent: false,
|
||||||
|
ReportClick: true,
|
||||||
|
FailureDetails: "Not an error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
||||||
|
|
||||||
|
config, err := configstatus.LoadConfigurationStatus(file)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var builder = configstatus.ConfigProgressBuilder{}
|
||||||
|
req := builder.New(config)
|
||||||
|
|
||||||
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
|
require.Equal(t, "bridge_config_progress", req.Event)
|
||||||
|
require.True(t, (req.Values.NbDay == 370) || (req.Values.NbDay == 371)) // leap year is accounted for in the simplest manner.
|
||||||
|
require.Equal(t, 2, req.Values.NbDaySinceLast)
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -19,7 +19,6 @@ package configstatus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigRecoveryValues struct {
|
type ConfigRecoveryValues struct {
|
||||||
@ -43,19 +42,22 @@ type ConfigRecoveryData struct {
|
|||||||
|
|
||||||
type ConfigRecoveryBuilder struct{}
|
type ConfigRecoveryBuilder struct{}
|
||||||
|
|
||||||
func (*ConfigRecoveryBuilder) New(data *ConfigurationStatusData) ConfigRecoveryData {
|
func (*ConfigRecoveryBuilder) New(config *ConfigurationStatus) ConfigRecoveryData {
|
||||||
|
config.DataLock.RLock()
|
||||||
|
defer config.DataLock.RUnlock()
|
||||||
|
|
||||||
return ConfigRecoveryData{
|
return ConfigRecoveryData{
|
||||||
MeasurementGroup: "bridge.any.configuration",
|
MeasurementGroup: "bridge.any.configuration",
|
||||||
Event: "bridge_config_recovery",
|
Event: "bridge_config_recovery",
|
||||||
Values: ConfigRecoveryValues{
|
Values: ConfigRecoveryValues{
|
||||||
Duration: int(time.Since(data.DataV1.PendingSince).Minutes()),
|
Duration: config.isPendingSinceMin(),
|
||||||
},
|
},
|
||||||
Dimensions: ConfigRecoveryDimensions{
|
Dimensions: ConfigRecoveryDimensions{
|
||||||
Autoconf: data.DataV1.Autoconf,
|
Autoconf: config.Data.DataV1.Autoconf,
|
||||||
ReportClick: strconv.FormatBool(data.DataV1.ReportClick),
|
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
||||||
ReportSent: strconv.FormatBool(data.DataV1.ReportSent),
|
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
||||||
ClickedLink: data.clickedLinkToString(),
|
ClickedLink: config.Data.clickedLinkToString(),
|
||||||
FailureDetails: data.DataV1.FailureDetails,
|
FailureDetails: config.Data.DataV1.FailureDetails,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -33,7 +33,7 @@ func TestConfigurationRecovery_default(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var builder = configstatus.ConfigRecoveryBuilder{}
|
var builder = configstatus.ConfigRecoveryBuilder{}
|
||||||
req := builder.New(config.Data)
|
req := builder.New(config)
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
require.Equal(t, "bridge_config_recovery", req.Event)
|
require.Equal(t, "bridge_config_recovery", req.Event)
|
||||||
@ -66,7 +66,7 @@ func TestConfigurationRecovery_fed(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var builder = configstatus.ConfigRecoveryBuilder{}
|
var builder = configstatus.ConfigRecoveryBuilder{}
|
||||||
req := builder.New(config.Data)
|
req := builder.New(config)
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
require.Equal(t, "bridge_config_recovery", req.Event)
|
require.Equal(t, "bridge_config_recovery", req.Event)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -19,7 +19,6 @@ package configstatus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigSuccessValues struct {
|
type ConfigSuccessValues struct {
|
||||||
@ -42,18 +41,21 @@ type ConfigSuccessData struct {
|
|||||||
|
|
||||||
type ConfigSuccessBuilder struct{}
|
type ConfigSuccessBuilder struct{}
|
||||||
|
|
||||||
func (*ConfigSuccessBuilder) New(data *ConfigurationStatusData) ConfigSuccessData {
|
func (*ConfigSuccessBuilder) New(config *ConfigurationStatus) ConfigSuccessData {
|
||||||
|
config.DataLock.RLock()
|
||||||
|
defer config.DataLock.RUnlock()
|
||||||
|
|
||||||
return ConfigSuccessData{
|
return ConfigSuccessData{
|
||||||
MeasurementGroup: "bridge.any.configuration",
|
MeasurementGroup: "bridge.any.configuration",
|
||||||
Event: "bridge_config_success",
|
Event: "bridge_config_success",
|
||||||
Values: ConfigSuccessValues{
|
Values: ConfigSuccessValues{
|
||||||
Duration: int(time.Since(data.DataV1.PendingSince).Minutes()),
|
Duration: config.isPendingSinceMin(),
|
||||||
},
|
},
|
||||||
Dimensions: ConfigSuccessDimensions{
|
Dimensions: ConfigSuccessDimensions{
|
||||||
Autoconf: data.DataV1.Autoconf,
|
Autoconf: config.Data.DataV1.Autoconf,
|
||||||
ReportClick: strconv.FormatBool(data.DataV1.ReportClick),
|
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
||||||
ReportSent: strconv.FormatBool(data.DataV1.ReportSent),
|
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
||||||
ClickedLink: data.clickedLinkToString(),
|
ClickedLink: config.Data.clickedLinkToString(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
@ -33,7 +33,7 @@ func TestConfigurationSuccess_default(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var builder = configstatus.ConfigSuccessBuilder{}
|
var builder = configstatus.ConfigSuccessBuilder{}
|
||||||
req := builder.New(config.Data)
|
req := builder.New(config)
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
require.Equal(t, "bridge_config_success", req.Event)
|
require.Equal(t, "bridge_config_success", req.Event)
|
||||||
@ -65,7 +65,7 @@ func TestConfigurationSuccess_fed(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var builder = configstatus.ConfigSuccessBuilder{}
|
var builder = configstatus.ConfigSuccessBuilder{}
|
||||||
req := builder.New(config.Data)
|
req := builder.New(config)
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
||||||
require.Equal(t, "bridge_config_success", req.Event)
|
require.Equal(t, "bridge_config_success", req.Event)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.Bridge.
|
// This file is part of Proton Mail Bridge.Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
// Copyright (c) 2024 Proton AG
|
||||||
//
|
//
|
||||||
// This file is part of Proton Mail Bridge.
|
// This file is part of Proton Mail Bridge.
|
||||||
//
|
//
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user