mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
Compare commits
244 Commits
v3.8.2
...
5e136df557
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e136df557 | |||
| 9c5b5c2ac3 | |||
| 0e6df4ce73 | |||
| a4772ee4e0 | |||
| ef779a23c1 | |||
| 4f4a2c3fd8 | |||
| 120a7b3626 | |||
| 7cf3b6fb7b | |||
| dd2448f35a | |||
| 917cf3fd51 | |||
| 98c3a01e08 | |||
| ecb74dbb29 | |||
| fa9836e9cf | |||
| 29a10bc4df | |||
| 6ad1e89919 | |||
| 55a0f27ca9 | |||
| c8291c2d35 | |||
| 4d8d00a62f | |||
| f0c76b1114 | |||
| cd69a712e1 | |||
| 21029825c9 | |||
| 3f78f4d672 | |||
| 5fbe94c559 | |||
| 80d556343e | |||
| 612d1054db | |||
| acf2fc32c4 | |||
| af01c63298 | |||
| 2e98d64f94 | |||
| cdcdd45bcf | |||
| 61ca604ace | |||
| b3e2a91f56 | |||
| 7d9753e2da | |||
| f1aef383b7 | |||
| 6647231278 | |||
| 531368da86 | |||
| a8caec560e | |||
| 0c21925939 | |||
| 19a445e73a | |||
| 96e0070ed2 | |||
| 516ff5206d | |||
| 4d2b328589 | |||
| 810be2d423 | |||
| e3d0334b6f | |||
| fb523e5573 | |||
| cb8d1a2389 | |||
| 84f0a6722a | |||
| c3a495facd | |||
| 9cdc40ca05 | |||
| 7021b1c2ea | |||
| 607d9df8a9 | |||
| 93396145dc | |||
| 7457fb06d2 | |||
| bee2642aec | |||
| b481ce2203 | |||
| 040d887aae | |||
| 3710dff0cd | |||
| df78e29234 | |||
| 6105f32c75 | |||
| f5bc6ad1f0 | |||
| e8a95e26f6 | |||
| ebe54ca92e | |||
| ff7e45f395 | |||
| 79c63f5785 | |||
| 3ca9e625f5 | |||
| 5b874657cb | |||
| da76784290 | |||
| bfe67f3005 | |||
| 99e6f00aaa | |||
| 43cbedafb8 | |||
| ac9ab8ab32 | |||
| f04350c046 | |||
| ed1b65731a | |||
| d12928b31c | |||
| 1ea06a95b7 | |||
| e290cd308b | |||
| 3d53bf7477 | |||
| 84c0b907d7 | |||
| b30455b641 | |||
| db9902e70b | |||
| f1f63c1d03 | |||
| 81a3c2aba8 | |||
| bbfc9beb04 | |||
| c4dba09ee6 | |||
| a5435eb1da | |||
| 54c56efdfa | |||
| fc64dbec59 | |||
| 5d3f084a2b | |||
| 606d6c0e3e | |||
| 9fbb6b4ca5 | |||
| 0d33cc5000 | |||
| ed5adb18fb | |||
| 85a91c5572 | |||
| 56d4bfbb71 | |||
| 48a75b0dd7 | |||
| 8688277ee6 | |||
| 63eb67760e | |||
| cffab028b2 | |||
| 8ea712b052 | |||
| ff0615167b | |||
| e2b361b9a6 | |||
| 1c6bbf1fae | |||
| e7713fa785 | |||
| b84663dd7a | |||
| 28ae54b5ca | |||
| 00aff40160 | |||
| cd8db6fd1c | |||
| a5e0f85a58 | |||
| ab289e6e01 | |||
| a28dc9f2f3 | |||
| 6cbe51138a | |||
| 8a859082cd | |||
| 1d972835ff | |||
| 8469e0a661 | |||
| 82607efe1c | |||
| 961dc9435f | |||
| 6ea970bf97 | |||
| a05b90e803 | |||
| 239ad8b946 | |||
| b574ccb6ea | |||
| 2569e83e51 | |||
| 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 | |||
| f34a7ff0ed | |||
| d2fbbc3e25 | |||
| 1c7c342e19 | |||
| da069a0155 | |||
| 8e49c84a12 | |||
| 754d80d097 | |||
| 63e272e270 | |||
| 54859a34b2 | |||
| 9b1feed68b | |||
| c9b6cc162b | |||
| bf3c90b8e9 | |||
| 8d63fb2301 | |||
| 7953306cc8 | |||
| 37352d44d2 | |||
| 2a1aeb208d | |||
| 94fbe260e4 | |||
| 6d4937222e | |||
| e33bad7bf1 | |||
| 70fdc91aff | |||
| bde8e45b37 | |||
| 6cb2d944d0 | |||
| cf0f59afc0 | |||
| 65d8fbbf31 | |||
| d919c0accf | |||
| 0ca07066db | |||
| 384fa4eb4b | |||
| 0c6e4ffa35 | |||
| 7fa1948c21 | |||
| 413ab1fc1e | |||
| 9894cf9744 | |||
| 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 | |||
| f84067de3e | |||
| f885bfbcf4 | |||
| f3aac09ecb | |||
| 38d692ebfb | |||
| 1acc7eb7db | |||
| 248fbf5e33 | |||
| 8b12a454ea | |||
| 310fcffc7b | |||
| 318ad16378 | |||
| 8be4246f7e | |||
| e580f89106 | |||
| 01043e033e | |||
| 94b44b383a | |||
| a3b8fabb26 | |||
| 275b30e518 | |||
| bf244e5c86 | |||
| cf9651bb94 | |||
| ba65ffdbc7 | |||
| 4b95ef4d82 | |||
| 951c7c27fb | |||
| e7423a9519 | |||
| d3582fa981 | |||
| 80c852a5b2 | |||
| 51498e3e37 | |||
| b7ef6e1486 | |||
| 0d03f84711 | |||
| 949666724d | |||
| bbe19bf960 | |||
| bfe25e3a46 | |||
| 236c958703 | |||
| e6b312b437 | |||
| 384154c767 | |||
| 45d2e9ea63 | |||
| 86e8a566c7 | |||
| a80fd92018 | |||
| 71063ac5ee |
30
.gitea/workflows/ci.yml
Normal file
30
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
name: nogui build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
jobs:
|
||||||
|
try:
|
||||||
|
name: try & test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.23.3'
|
||||||
|
|
||||||
|
- name: Install build-essentials
|
||||||
|
run: apt update -y && apt install build-essential libsecret-1-dev -y
|
||||||
|
|
||||||
|
- name: build nogui
|
||||||
|
run: make build-nogui
|
||||||
|
|
||||||
|
- uses: christopherhx/gitea-upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: nogui.zip
|
||||||
|
path: |
|
||||||
|
proton-bridge
|
||||||
|
bridge
|
||||||
41
.github/ISSUE_TEMPLATE/general-issue-template.md
vendored
41
.github/ISSUE_TEMPLATE/general-issue-template.md
vendored
@ -1,41 +0,0 @@
|
|||||||
---
|
|
||||||
name: General issue template
|
|
||||||
about: Template for detailed report of issues
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Issue tracker is ONLY used for reporting bugs with technical details. "It doesn't work" or new features should be discussed with our customer support. Please use bug report function in Bridge or contact bridge@protonmail.ch.
|
|
||||||
<!--- Provide a general summary of the issue in the Title above -->
|
|
||||||
|
|
||||||
## Expected Behavior
|
|
||||||
<!--- Tell us what should happen -->
|
|
||||||
|
|
||||||
## Current Behavior
|
|
||||||
<!--- Tell us what happens instead of the expected behavior -->
|
|
||||||
|
|
||||||
## Possible Solution
|
|
||||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
|
||||||
|
|
||||||
## Steps to Reproduce
|
|
||||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
|
||||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
4.
|
|
||||||
|
|
||||||
## Version Information
|
|
||||||
<!--- Which version of the app(s) were you using when you experienced this issue? -->
|
|
||||||
|
|
||||||
## Context (Environment)
|
|
||||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
|
||||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
|
||||||
|
|
||||||
## Detailed Description
|
|
||||||
<!--- Provide a detailed description of the change or addition you are proposing -->
|
|
||||||
|
|
||||||
## Possible Implementation
|
|
||||||
<!--- Not obligatory, but suggest an idea for implementing addition or change -->
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -7,6 +7,7 @@
|
|||||||
*~
|
*~
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
.vs
|
||||||
|
|
||||||
# Test files
|
# Test files
|
||||||
godog.test
|
godog.test
|
||||||
@ -35,6 +36,8 @@ cmd/Import-Export/deploy
|
|||||||
proton-bridge
|
proton-bridge
|
||||||
cmd/Desktop-Bridge/*.exe
|
cmd/Desktop-Bridge/*.exe
|
||||||
cmd/launcher/*.exe
|
cmd/launcher/*.exe
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
|
||||||
# Jetbrains (CLion, Golang) cmake build dirs
|
# Jetbrains (CLion, Golang) cmake build dirs
|
||||||
cmake-build-*/
|
cmake-build-*/
|
||||||
|
|||||||
@ -16,8 +16,6 @@
|
|||||||
# 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:
|
default:
|
||||||
tags:
|
tags:
|
||||||
- shared-small
|
- shared-small
|
||||||
@ -27,16 +25,26 @@ variables:
|
|||||||
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
|
||||||
|
|
||||||
include:
|
include:
|
||||||
|
- local: ci/setup.yml
|
||||||
- local: ci/rules.yml
|
- local: ci/rules.yml
|
||||||
- local: ci/env.yml
|
- local: ci/env.yml
|
||||||
- local: ci/test.yml
|
- local: ci/test.yml
|
||||||
|
- local: ci/report.yml
|
||||||
- local: ci/build.yml
|
- local: ci/build.yml
|
||||||
|
- component: gitlab.protontech.ch/proton/devops/cicd-components/kits/devsecops/go@~latest
|
||||||
|
inputs:
|
||||||
|
stage: analyse
|
||||||
|
|
||||||
|
|||||||
1
.gitlab/CODEOWNERS
Normal file
1
.gitlab/CODEOWNERS
Normal file
@ -0,0 +1 @@
|
|||||||
|
* @go/bridge-ppl/devs
|
||||||
@ -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.
|
||||||
@ -87,6 +88,7 @@ linters:
|
|||||||
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
||||||
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
|
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
|
||||||
- exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false]
|
- exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false]
|
||||||
|
- copyloopvar # detects places where loop variables are copied.
|
||||||
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
|
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
|
||||||
- godot # Check if comments end in a period [fast: true, auto-fix: true]
|
- godot # Check if comments end in a period [fast: true, auto-fix: true]
|
||||||
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: false]
|
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: false]
|
||||||
|
|||||||
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)
|
||||||
|
|||||||
@ -1,10 +1,78 @@
|
|||||||
# Contribution Policy
|
# Contributing guidelines
|
||||||
|
|
||||||
|
The following document describes how to contribute to the project. In this context, contribution does not only mean code contribution but also reporting issues, requesting new features, or just asking for help.
|
||||||
|
|
||||||
|
## Reporting issues
|
||||||
|
|
||||||
|
In case you experience issues while using the application, our request is to contact Proton customer support directly.
|
||||||
|
|
||||||
|
The benefits of using Proton customer support are
|
||||||
|
|
||||||
|
- Available 24/7/365.
|
||||||
|
- Provides priority support based on subscription type.
|
||||||
|
- Will escalate the issue to the developers every time it becomes too technical or they do not know the answer to a question.
|
||||||
|
- Easier to detect systematic issues by connecting similar reports.
|
||||||
|
- Possible to quickly derive frequency of an issue.
|
||||||
|
- Can assist you to transfer sensitive information safely to us.
|
||||||
|
|
||||||
|
To speed up the communication with customer support, consider the following:
|
||||||
|
|
||||||
|
- Whenever is possible, use the in-app bug report feature. It provides an application specific guide compared to using the generic report form on web.
|
||||||
|
- Whenever is possible, proactively attach logs to your report. Reporting an issue from the application can help you in that.
|
||||||
|
- Check whether your system is officially supported by Proton, including the source of the installer. We cannot provide help when the application is packaged by a third party or when the application is used on systems that we do not prepare to support.
|
||||||
|
- If your report is a feature request, see the Feature request section. In case it is an issue related to application security, see the Security vulnerabilities section.
|
||||||
|
|
||||||
|
In the past, we used GitHub issue tracker for more technical issues in parallel to Proton customer support, but we run into limitations with this approach:
|
||||||
|
|
||||||
|
- Monitoring GitHub issue tracker took development time as it was managed by the development team.
|
||||||
|
- It made issue frequency tracking challenging because we did not have a single point of entry for issues.
|
||||||
|
- Users were confused what technical issue means, and used the GitHub issue tracker for feature requests, or non-technical discussions.
|
||||||
|
- Users sometimes shared sensitive data through the GitHub issue tracker.
|
||||||
|
|
||||||
|
For the above reasons, we do not use GitHub issue tracker anymore but ask you to contact our customer support in case you run into a problem.
|
||||||
|
|
||||||
|
### Security vulnerabilities
|
||||||
|
|
||||||
|
Proton runs a bug bounty program for security vulnerabilities. They differ from normal bug reports in the following ways:
|
||||||
|
|
||||||
|
- These reports go directly to our security team.
|
||||||
|
- They expect deeper explanation of the issue.
|
||||||
|
- Depending on the finding, they may be financially rewarded.
|
||||||
|
|
||||||
|
More information about the program can be found [here](https://proton.me/security/bug-bounty).
|
||||||
|
|
||||||
|
## Feature requests
|
||||||
|
|
||||||
|
What someone considers as a bug is sometimes a feature, and sometimes, a missing feature is considered as a bug. Instead of reporting feature requests as bugs, we setup a UserVoice page to allow our users to share their preferences. UserVoice also makes it possible to vote on other feature requests, making the community preference public.
|
||||||
|
|
||||||
|
Our product team frequently monitors UserVoice, and the features listed there are taken into account in our planning.
|
||||||
|
|
||||||
|
Examples for UserVoice requests:
|
||||||
|
|
||||||
|
- Extending the officially supported environments (e.g., operating systems, clients, or computer architectures).
|
||||||
|
- Requesting new features.
|
||||||
|
- Integration with non-Proton services.
|
||||||
|
|
||||||
|
UserVoice is available [here](https://protonmail.uservoice.com/).
|
||||||
|
|
||||||
|
## Asking for help
|
||||||
|
|
||||||
|
The best ways to get answer for generic questions or to get help with setting up the system is to interact with our active community on [Reddit](https://reddit.com/r/ProtonMail/) or to contact customer support.
|
||||||
|
|
||||||
|
## Code contribution
|
||||||
|
|
||||||
|
We are grateful if you can contribute directly with code. In that case there is nothing else to do than to open a pull request.
|
||||||
|
|
||||||
|
The following is worthwhile noting
|
||||||
|
|
||||||
|
- The project is primarily developed on an internal repository, and the one on GitHub is only a mirror of it. For that reason, the merge request will not be merged on GitHub but added to the project internally. We are keeping the original author in the change set to respect the contribution.
|
||||||
|
- The application is used on numerous platforms and by many third party clients. To have higher chance your change to be accepted, consider all supported dependencies.
|
||||||
|
- Give detailed description of the issue, preferably with test steps to reproduce the original issue, and to verify the fix. It is even better if you also extend the automated tests.
|
||||||
|
|
||||||
|
### Contribution policy
|
||||||
|
|
||||||
By making a contribution to this project:
|
By making a contribution to this project:
|
||||||
|
|
||||||
1. I assign any and all copyright related to the contribution to Proton AG;
|
1. You assign any and all copyright related to the contribution to Proton AG;
|
||||||
2. I certify that the contribution was created in whole by me;
|
2. You certify that the contribution was created in whole by you;
|
||||||
3. I understand and agree that this project and the contribution are public
|
3. You understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information you submit with it) is maintained indefinitely and may be redistributed with this project or the open source license(s) involved.
|
||||||
and that a record of the contribution (including all personal information I
|
|
||||||
submit with it) is maintained indefinitely and may be redistributed with
|
|
||||||
this project or the open source license(s) involved.
|
|
||||||
|
|||||||
@ -63,11 +63,15 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [goleak](https://go.uber.org/goleak) available under [license](https://pkg.go.dev/go.uber.org/goleak?tab=licenses)
|
* [goleak](https://go.uber.org/goleak) available under [license](https://pkg.go.dev/go.uber.org/goleak?tab=licenses)
|
||||||
* [exp](https://golang.org/x/exp) available under [license](https://cs.opensource.google/go/x/exp/+/master:LICENSE)
|
* [exp](https://golang.org/x/exp) available under [license](https://cs.opensource.google/go/x/exp/+/master:LICENSE)
|
||||||
* [net](https://golang.org/x/net) available under [license](https://cs.opensource.google/go/x/net/+/master:LICENSE)
|
* [net](https://golang.org/x/net) available under [license](https://cs.opensource.google/go/x/net/+/master:LICENSE)
|
||||||
|
* [oauth2](https://golang.org/x/oauth2) available under [license](https://cs.opensource.google/go/x/oauth2/+/master:LICENSE)
|
||||||
* [sys](https://golang.org/x/sys) available under [license](https://cs.opensource.google/go/x/sys/+/master:LICENSE)
|
* [sys](https://golang.org/x/sys) available under [license](https://cs.opensource.google/go/x/sys/+/master:LICENSE)
|
||||||
* [text](https://golang.org/x/text) available under [license](https://cs.opensource.google/go/x/text/+/master:LICENSE)
|
* [text](https://golang.org/x/text) available under [license](https://cs.opensource.google/go/x/text/+/master:LICENSE)
|
||||||
|
* [api](https://google.golang.org/api) available under [license](https://pkg.go.dev/google.golang.org/api?tab=licenses)
|
||||||
* [grpc](https://google.golang.org/grpc) available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE)
|
* [grpc](https://google.golang.org/grpc) available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE)
|
||||||
* [protobuf](https://google.golang.org/protobuf) available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE)
|
* [protobuf](https://google.golang.org/protobuf) available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE)
|
||||||
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
|
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
|
||||||
|
* [compute](https://cloud.google.com/go/compute) available under [license](https://pkg.go.dev/cloud.google.com/go/compute?tab=licenses)
|
||||||
|
* [metadata](https://cloud.google.com/go/compute/metadata) available under [license](https://pkg.go.dev/cloud.google.com/go/compute/metadata?tab=licenses)
|
||||||
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
|
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
|
||||||
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
|
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
|
||||||
* [go-mime](https://github.com/ProtonMail/go-mime) available under [license](https://github.com/ProtonMail/go-mime/blob/master/LICENSE)
|
* [go-mime](https://github.com/ProtonMail/go-mime) available under [license](https://github.com/ProtonMail/go-mime/blob/master/LICENSE)
|
||||||
@ -95,8 +99,11 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [validator](https://github.com/go-playground/validator/v10) available under [license](https://github.com/go-playground/validator/v10/blob/master/LICENSE)
|
* [validator](https://github.com/go-playground/validator/v10) available under [license](https://github.com/go-playground/validator/v10/blob/master/LICENSE)
|
||||||
* [go-json](https://github.com/goccy/go-json) available under [license](https://github.com/goccy/go-json/blob/master/LICENSE)
|
* [go-json](https://github.com/goccy/go-json) available under [license](https://github.com/goccy/go-json/blob/master/LICENSE)
|
||||||
* [uuid](https://github.com/gofrs/uuid) available under [license](https://github.com/gofrs/uuid/blob/master/LICENSE)
|
* [uuid](https://github.com/gofrs/uuid) available under [license](https://github.com/gofrs/uuid/blob/master/LICENSE)
|
||||||
|
* [groupcache](https://github.com/golang/groupcache) available under [license](https://github.com/golang/groupcache/blob/master/LICENSE)
|
||||||
* [protobuf](https://github.com/golang/protobuf) available under [license](https://github.com/golang/protobuf/blob/master/LICENSE)
|
* [protobuf](https://github.com/golang/protobuf) available under [license](https://github.com/golang/protobuf/blob/master/LICENSE)
|
||||||
* [pprof](https://github.com/google/pprof) available under [license](https://github.com/google/pprof/blob/master/LICENSE)
|
* [pprof](https://github.com/google/pprof) available under [license](https://github.com/google/pprof/blob/master/LICENSE)
|
||||||
|
* [enterprise-certificate-proxy](https://github.com/googleapis/enterprise-certificate-proxy) available under [license](https://github.com/googleapis/enterprise-certificate-proxy/blob/master/LICENSE)
|
||||||
|
* [gax-go](https://github.com/googleapis/gax-go/v2) available under [license](https://github.com/googleapis/gax-go/v2/blob/master/LICENSE)
|
||||||
* [errwrap](https://github.com/hashicorp/errwrap) available under [license](https://github.com/hashicorp/errwrap/blob/master/LICENSE)
|
* [errwrap](https://github.com/hashicorp/errwrap) available under [license](https://github.com/hashicorp/errwrap/blob/master/LICENSE)
|
||||||
* [go-immutable-radix](https://github.com/hashicorp/go-immutable-radix) available under [license](https://github.com/hashicorp/go-immutable-radix/blob/master/LICENSE)
|
* [go-immutable-radix](https://github.com/hashicorp/go-immutable-radix) available under [license](https://github.com/hashicorp/go-immutable-radix/blob/master/LICENSE)
|
||||||
* [go-memdb](https://github.com/hashicorp/go-memdb) available under [license](https://github.com/hashicorp/go-memdb/blob/master/LICENSE)
|
* [go-memdb](https://github.com/hashicorp/go-memdb) available under [license](https://github.com/hashicorp/go-memdb/blob/master/LICENSE)
|
||||||
@ -124,15 +131,16 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [codec](https://github.com/ugorji/go/codec) available under [license](https://github.com/ugorji/go/codec/blob/master/LICENSE)
|
* [codec](https://github.com/ugorji/go/codec) available under [license](https://github.com/ugorji/go/codec/blob/master/LICENSE)
|
||||||
* [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE)
|
* [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE)
|
||||||
* [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE)
|
* [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE)
|
||||||
* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json)
|
* [go-ordered-json](https://gitlab.com/c0b/go-ordered-json) available under [license](https://gitlab.com/c0b/go-ordered-json/blob/master/LICENSE)
|
||||||
|
* [go.opencensus.io](https://pkg.go.dev/go.opencensus.io?tab=licenses) available under [license](https://pkg.go.dev/go.opencensus.io?tab=licenses)
|
||||||
* [arch](https://golang.org/x/arch) available under [license](https://cs.opensource.google/go/x/arch/+/master:LICENSE)
|
* [arch](https://golang.org/x/arch) available under [license](https://cs.opensource.google/go/x/arch/+/master:LICENSE)
|
||||||
* [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE)
|
* [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE)
|
||||||
* [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE)
|
* [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE)
|
||||||
* [sync](https://golang.org/x/sync) available under [license](https://cs.opensource.google/go/x/sync/+/master:LICENSE)
|
* [sync](https://golang.org/x/sync) available under [license](https://cs.opensource.google/go/x/sync/+/master:LICENSE)
|
||||||
* [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)
|
||||||
|
* [appengine](https://google.golang.org/appengine) available under [license](https://pkg.go.dev/google.golang.org/appengine?tab=licenses)
|
||||||
* [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) 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)
|
* [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)
|
* [resty](https://github.com/LBeernaertProton/resty/v2) available under [license](https://github.com/LBeernaertProton/resty/v2/blob/master/LICENSE)
|
||||||
|
|||||||
163
Changelog.md
163
Changelog.md
@ -3,6 +3,169 @@
|
|||||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
|
|
||||||
|
## Erasmus Bridge 3.15.1
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* BRIDGE-281: Disable keychain test on macOS.
|
||||||
|
|
||||||
|
|
||||||
|
## Erasmus Bridge 3.15.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* BRIDGE-238: Added host information to sentry events; new sentry event for keychain issues.
|
||||||
|
* BRIDGE-236: Added SMTP observability metrics.
|
||||||
|
* BRIDGE-217: Added missing parameter to the CLI help command.
|
||||||
|
* BRIDGE-234: Add accessibility name in QML for UI automation.
|
||||||
|
* BRIDGE-232: Test: Add Home Menu Bridge UI e2e automation tests.
|
||||||
|
* BRIDGE-220: Test: Add Bridge E2E UI login/logout tests for Windows.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* BRIDGE-228: Removed sentry events.
|
||||||
|
* BRIDGE-218: Observability adapter; gluon observability metrics and tests.
|
||||||
|
* BRIDGE-215: Tweak wording on macOS profile install page.
|
||||||
|
* BRIDGE-131: Test: Integration tests for messages from Proton <-> Gmail.
|
||||||
|
* BRIDGE-142: Bridge icon can be removed from the menu bar on macOS.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* BRIDGE-240: Fix for running against Qt 6.8 (contribution of GitHub user Cimbali).
|
||||||
|
* BRIDGE-231: Fix reversed header order in messages.
|
||||||
|
* BRIDGE-235: Fix compilation of Bridge GUI Tester on Windows.
|
||||||
|
* BRIDGE-120: Use appropriate address key when importing / saving draft.
|
||||||
|
|
||||||
|
|
||||||
|
## Dragon Bridge 3.14.0
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* BRIDGE-207: Failure to download or verify an update now fails silently.
|
||||||
|
* BRIDGE-204: Removed redundant Sentry events.
|
||||||
|
* BRIDGE-150: Observability service modification.
|
||||||
|
* BRIDGE-210: Reduced log level of cache events so they won't be printed to stdout.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* BRIDGE-106: Fixed import of multipart-related messages.
|
||||||
|
* BRIDGE-108: Fixed GetInitials when empty username is passed.
|
||||||
|
|
||||||
|
|
||||||
|
## 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
|
## Xikou Bridge 3.8.2
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
25
Makefile
25
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.8.2+git
|
BRIDGE_APP_VERSION?=3.15.1+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.61.0"
|
||||||
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 \
|
||||||
|
|||||||
16
README.md
16
README.md
@ -1,7 +1,7 @@
|
|||||||
# Proton Mail Bridge and Import Export app
|
# Proton Mail Bridge
|
||||||
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 application.
|
||||||
For a detailed build information see [BUILDS](./BUILDS.md).
|
For a detailed build information see [BUILDS](./BUILDS.md).
|
||||||
The license can be found in [LICENSE](./LICENSE) file, for more licensing information see [COPYING_NOTES](./COPYING_NOTES.md).
|
The license can be found in [LICENSE](./LICENSE) file, for more licensing information see [COPYING_NOTES](./COPYING_NOTES.md).
|
||||||
For contribution policy see [CONTRIBUTING](./CONTRIBUTING.md).
|
For contribution policy see [CONTRIBUTING](./CONTRIBUTING.md).
|
||||||
@ -13,7 +13,7 @@ Proton Mail Bridge for e-mail clients.
|
|||||||
When launched, Bridge will initialize local IMAP/SMTP servers and render
|
When launched, Bridge will initialize local IMAP/SMTP servers and render
|
||||||
its GUI.
|
its GUI.
|
||||||
|
|
||||||
To configure an e-mail client, firstly log in using your Proton Mail credentials.
|
To configure an e-mail client, first log in using your Proton Mail credentials.
|
||||||
Open your e-mail client and add a new account using the settings which are
|
Open your e-mail client and add a new account using the settings which are
|
||||||
located in the Bridge GUI. The client will only be able to sync with
|
located in the Bridge GUI. The client will only be able to sync with
|
||||||
your Proton Mail account when the Bridge is running, thus the option
|
your Proton Mail account when the Bridge is running, thus the option
|
||||||
@ -24,10 +24,10 @@ background.
|
|||||||
|
|
||||||
More details [on the public website](https://proton.me/mail/bridge).
|
More details [on the public website](https://proton.me/mail/bridge).
|
||||||
|
|
||||||
## Launchers
|
## Launcher
|
||||||
Launchers are binaries used to run the Proton Mail Bridge or Import-Export apps.
|
The launcher is a binary used to run the Proton Mail Bridge.
|
||||||
|
|
||||||
Official distributions of the Proton Mail Bridge and Import-Export apps contain
|
The Official distribution of the Proton Mail Bridge application contains
|
||||||
both a launcher and the app itself. The launcher is installed in a protected
|
both a launcher and the app itself. The launcher is installed in a protected
|
||||||
area of the system (i.e. an area accessible only with admin privileges) and is
|
area of the system (i.e. an area accessible only with admin privileges) and is
|
||||||
used to run the app. The launcher ensures that nobody tampered with the app's
|
used to run the app. The launcher ensures that nobody tampered with the app's
|
||||||
@ -37,7 +37,7 @@ feature enables the app to securely update itself automatically without asking
|
|||||||
the user for a password.
|
the user for a password.
|
||||||
|
|
||||||
## Keychain
|
## Keychain
|
||||||
You need to have a keychain in order to run the Proton Mail Bridge. On Mac or
|
You need to have a keychain in order to run Proton Mail Bridge. On Mac or
|
||||||
Windows, Bridge uses native credential managers. On Linux, use `secret-service` freedesktop.org API
|
Windows, Bridge uses native credential managers. On Linux, use `secret-service` freedesktop.org API
|
||||||
(e.g. [Gnome keyring](https://wiki.gnome.org/Projects/GnomeKeyring/))
|
(e.g. [Gnome keyring](https://wiki.gnome.org/Projects/GnomeKeyring/))
|
||||||
or
|
or
|
||||||
|
|||||||
11
ci/build.yml
11
ci/build.yml
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
.script-build:
|
.script-build:
|
||||||
@ -7,9 +6,14 @@
|
|||||||
extends:
|
extends:
|
||||||
- .rules-branch-and-MR-manual
|
- .rules-branch-and-MR-manual
|
||||||
script:
|
script:
|
||||||
|
- which go && go version
|
||||||
|
- which gcc && gcc --version
|
||||||
|
- which qmake && qmake --version
|
||||||
|
- git rev-parse --short=10 HEAD
|
||||||
- make build
|
- make build
|
||||||
- git diff && git diff-index --quiet HEAD
|
- git diff && git diff-index --quiet HEAD
|
||||||
- make vault-editor
|
- make vault-editor
|
||||||
|
- make bridge-rollout
|
||||||
artifacts:
|
artifacts:
|
||||||
expire_in: 1 day
|
expire_in: 1 day
|
||||||
when: always
|
when: always
|
||||||
@ -17,7 +21,7 @@
|
|||||||
paths:
|
paths:
|
||||||
- bridge_*.tgz
|
- bridge_*.tgz
|
||||||
- vault-editor
|
- vault-editor
|
||||||
|
- bridge-rollout
|
||||||
build-linux:
|
build-linux:
|
||||||
extends:
|
extends:
|
||||||
- .script-build
|
- .script-build
|
||||||
@ -65,5 +69,4 @@ trigger-qa-installer:
|
|||||||
SRC_HASH: $CI_COMMIT_SHA
|
SRC_HASH: $CI_COMMIT_SHA
|
||||||
trigger:
|
trigger:
|
||||||
project: "jcuth/bridge-release"
|
project: "jcuth/bridge-release"
|
||||||
branch: release/xikou
|
branch: master
|
||||||
|
|
||||||
|
|||||||
73
ci/env.yml
73
ci/env.yml
@ -2,46 +2,45 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
.env-windows:
|
.env-windows:
|
||||||
|
extends:
|
||||||
|
- .image-windows-virt-build
|
||||||
before_script:
|
before_script:
|
||||||
- export BRIDGE_SYNC_FORCE_MINIMUM_SPEC=1
|
- !reference [.before-script-windows-virt-build, before_script]
|
||||||
- export GOROOT=/c/Go1.20/
|
- !reference [.before-script-git-config, before_script]
|
||||||
- export PATH=$GOROOT/bin:$PATH
|
- mkdir -p .cache/bin
|
||||||
- export GOARCH=amd64
|
- export PATH=$(pwd)/.cache/bin:$PATH
|
||||||
- export GOPATH=~/go1.20
|
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
||||||
- export GO111MODULE=on
|
variables:
|
||||||
- export PATH="${GOPATH}/bin:${PATH}"
|
GOARCH: amd64
|
||||||
- export MSYSTEM=
|
BRIDGE_SYNC_FORCE_MINIMUM_SPEC: 1
|
||||||
- export QT6DIR=/c/grrrQt/6.4.3/msvc2019_64
|
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
|
||||||
- export PATH=$PATH:${QT6DIR}/bin
|
cache:
|
||||||
- export PATH="/c/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin:$PATH"
|
key: windows-vcpkg-go-0
|
||||||
- $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove"
|
paths:
|
||||||
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
- .cache
|
||||||
- git config --global safe.directory '*'
|
when: 'always'
|
||||||
- git status --porcelain
|
|
||||||
cache: {}
|
|
||||||
tags:
|
|
||||||
- windows-bridge
|
|
||||||
|
|
||||||
.env-darwin:
|
.env-darwin:
|
||||||
|
extends:
|
||||||
|
- .image-darwin-build
|
||||||
before_script:
|
before_script:
|
||||||
- export BRIDGE_SYNC_FORCE_MINIMUM_SPEC=1
|
- !reference [.before-script-darwin-tart-build, before_script]
|
||||||
- export PATH=/usr/local/bin:$PATH
|
- !reference [.before-script-git-config, before_script]
|
||||||
- export PATH=/usr/local/opt/git/bin:$PATH
|
- mkdir -p .cache/bin
|
||||||
- export PATH=/usr/local/opt/make/libexec/gnubin:$PATH
|
- export PATH=$(pwd)/.cache/bin:$PATH
|
||||||
- export PATH=/usr/local/opt/gnu-sed/libexec/gnubin:$PATH
|
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
||||||
- export GOROOT=~/local/opt/go@1.20
|
variables:
|
||||||
- export PATH="${GOROOT}/bin:$PATH"
|
BRIDGE_SYNC_FORCE_MINIMUM_SPEC: 1
|
||||||
- export GOPATH=~/go1.20
|
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
|
||||||
- export PATH="${GOPATH}/bin:$PATH"
|
cache:
|
||||||
- export QT6DIR=/opt/Qt/6.4.3/macos
|
key: darwin-go-and-vcpkg
|
||||||
- export PATH="${QT6DIR}/bin:$PATH"
|
paths:
|
||||||
- uname -a
|
- .cache
|
||||||
cache: {}
|
when: 'always'
|
||||||
tags:
|
|
||||||
- macos-m1-bridge
|
|
||||||
|
|
||||||
.env-linux-build:
|
.env-linux-build:
|
||||||
image: gitlab.protontech.ch:4567/go/bridge-internal:build-go1.20-qt6.4.3
|
extends:
|
||||||
|
- .image-linux-build
|
||||||
variables:
|
variables:
|
||||||
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
|
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
|
||||||
cache:
|
cache:
|
||||||
@ -50,13 +49,11 @@
|
|||||||
- .cache
|
- .cache
|
||||||
when: 'always'
|
when: 'always'
|
||||||
before_script:
|
before_script:
|
||||||
- mkdir -p .cache/bin
|
|
||||||
- export BRIDGE_SYNC_FORCE_MINIMUM_SPEC=1
|
- 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 PATH=$(pwd)/.cache/bin:$PATH
|
||||||
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
- 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:
|
tags:
|
||||||
- shared-large
|
- 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"
|
||||||
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'
|
||||||
|
|
||||||
53
ci/test.yml
53
ci/test.yml
@ -4,15 +4,17 @@
|
|||||||
lint:
|
lint:
|
||||||
stage: test
|
stage: test
|
||||||
extends:
|
extends:
|
||||||
|
- .image-linux-test
|
||||||
- .rules-branch-manual-br-tag-and-MR-and-devel-always
|
- .rules-branch-manual-br-tag-and-MR-and-devel-always
|
||||||
script:
|
script:
|
||||||
- make lint
|
- make lint
|
||||||
tags:
|
tags:
|
||||||
- shared-medium
|
- shared-medium
|
||||||
|
|
||||||
bug-report-preview:
|
lint-bug-report-preview:
|
||||||
stage: test
|
stage: test
|
||||||
extends:
|
extends:
|
||||||
|
- .image-linux-test
|
||||||
- .rules-branch-and-MR-manual
|
- .rules-branch-and-MR-manual
|
||||||
script:
|
script:
|
||||||
- make lint-bug-report-preview
|
- make lint-bug-report-preview
|
||||||
@ -24,20 +26,36 @@ bug-report-preview:
|
|||||||
extends:
|
extends:
|
||||||
- .rules-branch-manual-MR-and-devel-always
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
script:
|
script:
|
||||||
|
- which go && go version
|
||||||
|
- which gcc && gcc --version
|
||||||
- make test
|
- make test
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- coverage/**
|
- coverage/**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
test-linux:
|
test-linux:
|
||||||
extends:
|
extends:
|
||||||
|
- .image-linux-test
|
||||||
- .script-test
|
- .script-test
|
||||||
tags:
|
tags:
|
||||||
- shared-large
|
- shared-large
|
||||||
|
|
||||||
|
test-windows:
|
||||||
|
extends:
|
||||||
|
- .env-windows
|
||||||
|
- .script-test
|
||||||
|
|
||||||
|
test-darwin:
|
||||||
|
extends:
|
||||||
|
- .env-darwin
|
||||||
|
- .script-test
|
||||||
|
|
||||||
fuzz-linux:
|
fuzz-linux:
|
||||||
stage: test
|
stage: test
|
||||||
extends:
|
extends:
|
||||||
|
- .image-linux-test
|
||||||
- .rules-branch-manual-MR-and-devel-always
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
script:
|
script:
|
||||||
- make fuzz
|
- make fuzz
|
||||||
@ -55,14 +73,26 @@ test-integration:
|
|||||||
extends:
|
extends:
|
||||||
- test-linux
|
- test-linux
|
||||||
script:
|
script:
|
||||||
- make test-integration
|
- 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:
|
test-integration-race:
|
||||||
extends:
|
extends:
|
||||||
- test-integration
|
- test-integration
|
||||||
- .rules-branch-and-MR-manual
|
- .rules-branch-and-MR-manual
|
||||||
script:
|
script:
|
||||||
- make test-integration-race
|
- make test-integration-race | tee -a integration-race-job.log
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
paths:
|
||||||
|
- integration-race-job.log
|
||||||
|
|
||||||
|
|
||||||
test-integration-nightly:
|
test-integration-nightly:
|
||||||
extends:
|
extends:
|
||||||
@ -72,24 +102,19 @@ test-integration-nightly:
|
|||||||
- test-integration
|
- test-integration
|
||||||
script:
|
script:
|
||||||
- make test-integration-nightly | tee -a nightly-job.log
|
- make test-integration-nightly | tee -a nightly-job.log
|
||||||
|
after_script:
|
||||||
|
- |
|
||||||
|
grep "Error: " nightly-job.log
|
||||||
artifacts:
|
artifacts:
|
||||||
when: always
|
when: always
|
||||||
paths:
|
paths:
|
||||||
|
- tests/result/feature-tests.xml
|
||||||
- nightly-job.log
|
- nightly-job.log
|
||||||
|
|
||||||
test-windows:
|
|
||||||
extends:
|
|
||||||
- .env-windows
|
|
||||||
- .script-test
|
|
||||||
|
|
||||||
test-darwin:
|
|
||||||
extends:
|
|
||||||
- .env-darwin
|
|
||||||
- .script-test
|
|
||||||
|
|
||||||
test-coverage:
|
test-coverage:
|
||||||
stage: test
|
stage: test
|
||||||
extends:
|
extends:
|
||||||
|
- .image-linux-test
|
||||||
- .rules-branch-manual-scheduled-and-test-branch-always
|
- .rules-branch-manual-scheduled-and-test-branch-always
|
||||||
script:
|
script:
|
||||||
- ./utils/coverage.sh
|
- ./utils/coverage.sh
|
||||||
@ -114,12 +139,12 @@ test-coverage:
|
|||||||
|
|
||||||
go-vuln-check:
|
go-vuln-check:
|
||||||
extends:
|
extends:
|
||||||
|
- .image-linux-test
|
||||||
- .rules-branch-manual-MR-and-devel-always
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
stage: test
|
stage: test
|
||||||
tags:
|
tags:
|
||||||
- shared-medium
|
- shared-medium
|
||||||
script:
|
script:
|
||||||
- apt-get -y install jq
|
|
||||||
- ./utils/govulncheck.sh
|
- ./utils/govulncheck.sh
|
||||||
artifacts:
|
artifacts:
|
||||||
when: always
|
when: always
|
||||||
|
|||||||
@ -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,8 +19,15 @@ 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"
|
||||||
)
|
)
|
||||||
@ -43,5 +50,72 @@ import (
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
_ = app.New().Run(xslices.Filter(os.Args, func(arg string) bool { return !strings.Contains(arg, "-psn_") }))
|
appErr := app.New().Run(xslices.Filter(os.Args, func(arg string) bool { return !strings.Contains(arg, "-psn_") }))
|
||||||
|
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
46
go.mod
46
go.mod
@ -1,13 +1,15 @@
|
|||||||
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.20231206152152-caaf10897f9e
|
github.com/ProtonMail/gluon v0.17.1-0.20241121121545-aa1cfd19b4b2
|
||||||
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.20231130083229-e8aa47d7a366
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.7.4-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
|
||||||
@ -15,7 +17,7 @@ require (
|
|||||||
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.11.2-0.20231129083954-35e55cd2a542
|
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
|
||||||
@ -39,20 +41,24 @@ require (
|
|||||||
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.17.0
|
golang.org/x/net v0.24.0
|
||||||
golang.org/x/sys v0.13.0
|
golang.org/x/oauth2 v0.7.0
|
||||||
golang.org/x/text v0.13.0
|
golang.org/x/sys v0.19.0
|
||||||
|
golang.org/x/text v0.14.0
|
||||||
|
google.golang.org/api v0.114.0
|
||||||
google.golang.org/grpc v1.56.3
|
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 (
|
||||||
|
cloud.google.com/go/compute v1.19.1 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
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-20230717121622-edf196117233 // 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
|
||||||
@ -62,11 +68,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
|
||||||
@ -80,8 +86,11 @@ 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/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // 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/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.7.1 // 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
|
||||||
github.com/hashicorp/go-memdb v1.3.3 // indirect
|
github.com/hashicorp/go-memdb v1.3.3 // indirect
|
||||||
@ -93,14 +102,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,19 +119,20 @@ require (
|
|||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
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
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.14.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/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // 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.20240919135104-3bc88e6a9423
|
||||||
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7
|
|
||||||
github.com/emersion/go-smtp => github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865
|
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/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-20230517073537-fc1740a83768
|
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77
|
||||||
)
|
)
|
||||||
|
|||||||
133
go.sum
133
go.sum
@ -5,9 +5,16 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A
|
|||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
|
||||||
|
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||||
|
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
|
||||||
|
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
|
||||||
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=
|
||||||
@ -22,25 +29,24 @@ github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a/go.mod h
|
|||||||
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 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.20241121121545-aa1cfd19b4b2 h1:iZjKvjb6VkGb52ZaBBiXC1MGYJN4C/S97JfppdzpMHQ=
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
github.com/ProtonMail/gluon v0.17.1-0.20241121121545-aa1cfd19b4b2/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||||
github.com/ProtonMail/gluon v0.17.1-0.20231206152152-caaf10897f9e h1:kHmSOTxynSip1WJvwZTFOGJPVfI42e/I8bDzDjLK7aM=
|
|
||||||
github.com/ProtonMail/gluon v0.17.1-0.20231206152152-caaf10897f9e/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-20230717121622-edf196117233 h1:bdoKdh0f66/lrgVfYlxw0aqISY/KOqXmFJyGt7rGmnc=
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233 h1:bdoKdh0f66/lrgVfYlxw0aqISY/KOqXmFJyGt7rGmnc=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
github.com/ProtonMail/go-crypto v0.0.0-20230717121622-edf196117233/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||||
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.20240919135104-3bc88e6a9423 h1:p8nBDxvRnvDOyrcePKkPpErWGhDoTqpX8a1c54CcSu0=
|
||||||
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.20240919135104-3bc88e6a9423/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.20231130083229-e8aa47d7a366 h1:W9P5GdDnuGkB3tbzKnXmUrTjIs6zk/K+4lpPTWzsoRE=
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47 h1:a+3dOyIxJEslN5HxyICM8flY9lnCyJupXNcv6fUaivA=
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20231130083229-e8aa47d7a366/go.mod h1:t+hb0BfkmZ9fpvzVRpHC7limoowym6ln/j0XL9a8DDw=
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20240918100656-b4860af56d47/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 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
|
||||||
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
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=
|
||||||
@ -75,6 +81,7 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N
|
|||||||
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=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
@ -88,8 +95,10 @@ 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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
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=
|
||||||
@ -107,20 +116,25 @@ 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/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||||
|
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 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 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 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 h1:IFTm6NBbfSgZCaeEzorQhH4T7ZERl4j+1u7oXWzmJcM=
|
||||||
github.com/elastic/go-sysinfo v1.11.2-0.20231129083954-35e55cd2a542/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ=
|
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=
|
||||||
@ -136,6 +150,10 @@ github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwo
|
|||||||
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=
|
||||||
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
github.com/emersion/go-vcard v0.0.0-20230331202150-f3d26859ccd3/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
@ -157,6 +175,7 @@ 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/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-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
@ -164,6 +183,7 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
|||||||
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=
|
||||||
@ -182,9 +202,12 @@ github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx
|
|||||||
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 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/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/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/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=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
@ -193,6 +216,13 @@ github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+Licev
|
|||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/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.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.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
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.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
@ -200,6 +230,10 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
|
|||||||
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=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
@ -210,10 +244,15 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI
|
|||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
@ -283,9 +322,11 @@ 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/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
|
||||||
@ -303,8 +344,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=
|
||||||
@ -331,7 +372,9 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
|
|||||||
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 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 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=
|
||||||
@ -341,6 +384,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=
|
||||||
@ -354,12 +398,13 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
|||||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
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=
|
||||||
@ -367,6 +412,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=
|
||||||
@ -408,8 +454,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=
|
||||||
@ -433,6 +480,8 @@ gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a/go.mod h1:NREv
|
|||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||||
@ -447,11 +496,13 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
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.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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
|
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=
|
||||||
@ -494,6 +545,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
|||||||
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-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-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
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=
|
||||||
@ -505,11 +557,14 @@ 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/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
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=
|
||||||
|
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
|
||||||
|
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -519,8 +574,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
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=
|
||||||
@ -540,13 +595,13 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
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-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-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-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=
|
||||||
@ -560,8 +615,9 @@ 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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
|
||||||
golang.org/x/sys v0.13.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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
@ -582,8 +638,9 @@ 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/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 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
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 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
@ -598,6 +655,7 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
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=
|
||||||
@ -622,10 +680,14 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E
|
|||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE=
|
||||||
|
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
@ -635,17 +697,31 @@ 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-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
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.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
||||||
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
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=
|
||||||
@ -669,6 +745,7 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||||
|
|||||||
@ -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"
|
||||||
@ -43,6 +44,7 @@ import (
|
|||||||
"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/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"
|
||||||
@ -73,6 +75,13 @@ const (
|
|||||||
|
|
||||||
flagLogIMAP = "log-imap"
|
flagLogIMAP = "log-imap"
|
||||||
flagLogSMTP = "log-smtp"
|
flagLogSMTP = "log-smtp"
|
||||||
|
|
||||||
|
flagEnableKeychainTest = "enable-keychain-test"
|
||||||
|
flagDisableKeychainTest = "disable-keychain-test"
|
||||||
|
|
||||||
|
flagSoftwareRenderer = "software-renderer"
|
||||||
|
flagSetSoftwareRenderer = "set-software-renderer"
|
||||||
|
flagSetHardwareRenderer = "set-hardware-renderer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hidden flags.
|
// Hidden flags.
|
||||||
@ -80,8 +89,7 @@ const (
|
|||||||
flagLauncher = "launcher"
|
flagLauncher = "launcher"
|
||||||
flagNoWindow = "no-window"
|
flagNoWindow = "no-window"
|
||||||
flagParentPID = "parent-pid"
|
flagParentPID = "parent-pid"
|
||||||
flagSoftwareRenderer = "software-renderer"
|
FlagSessionID = "session-id"
|
||||||
flagSessionID = "session-id"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -89,6 +97,23 @@ const (
|
|||||||
appShortName = "bridge"
|
appShortName = "bridge"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// the two flags below have been deprecated by BRIDGE-281. We however keep them so that bridge does not error if they are passed on startup.
|
||||||
|
var cliFlagEnableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
|
||||||
|
Name: flagEnableKeychainTest,
|
||||||
|
Usage: "This flag is deprecated and does nothing",
|
||||||
|
Value: false,
|
||||||
|
DisableDefaultText: true,
|
||||||
|
Hidden: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var cliFlagDisableKeychainTest = &cli.BoolFlag{ //nolint:gochecknoglobals
|
||||||
|
Name: flagDisableKeychainTest,
|
||||||
|
Usage: "This flag is deprecated and does nothing",
|
||||||
|
Value: false,
|
||||||
|
DisableDefaultText: true,
|
||||||
|
Hidden: true,
|
||||||
|
}
|
||||||
|
|
||||||
func New() *cli.App {
|
func New() *cli.App {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
|
|
||||||
@ -138,6 +163,24 @@ func New() *cli.App {
|
|||||||
Name: flagLogSMTP,
|
Name: flagLogSMTP,
|
||||||
Usage: "Enable logging of SMTP communications (may contain decrypted data!)",
|
Usage: "Enable logging of SMTP communications (may contain decrypted data!)",
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagSoftwareRenderer, // This flag is ignored by bridge, but should be passed to launcher in case of restart, so it need to be accepted by the CLI parser.
|
||||||
|
Usage: "Use software rendering of the GUI for the current execution of the application",
|
||||||
|
Value: false,
|
||||||
|
DisableDefaultText: true,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagSetSoftwareRenderer, // This flag is ignored by bridge, we just want it to be shown in the help (BRIDGE-217).
|
||||||
|
Usage: "Toggle software rendering of the GUI for the current and future executions of the application",
|
||||||
|
Value: false,
|
||||||
|
DisableDefaultText: true,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagSetHardwareRenderer, // This flag is ignored by bridge, we just want it to be shown in the help (BRIDGE-217).
|
||||||
|
Usage: "Toggle hardware rendering of the GUI for the current and future executions of the application",
|
||||||
|
Value: false,
|
||||||
|
DisableDefaultText: true,
|
||||||
|
},
|
||||||
|
|
||||||
// Hidden flags
|
// Hidden flags
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
@ -156,18 +199,26 @@ func New() *cli.App {
|
|||||||
Hidden: true,
|
Hidden: true,
|
||||||
Value: -1,
|
Value: -1,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: flagSoftwareRenderer, // This flag is ignored by bridge, but should be passed to launcher in case of restart, so it need to be accepted by the CLI parser.
|
|
||||||
Usage: "GUI is using software renderer",
|
|
||||||
Hidden: true,
|
|
||||||
Value: false,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: flagSessionID,
|
Name: FlagSessionID,
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We override the default help value because we want "Show" to be capitalized
|
||||||
|
cli.HelpFlag = &cli.BoolFlag{
|
||||||
|
Name: "help",
|
||||||
|
Aliases: []string{"h"},
|
||||||
|
Usage: "Show help",
|
||||||
|
DisableDefaultText: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if onMacOS() {
|
||||||
|
// The two flags below were introduced for BRIDGE-116, and are available only on macOS.
|
||||||
|
// They have been later removed fro BRIDGE-281.
|
||||||
|
app.Flags = append(app.Flags, cliFlagEnableKeychainTest, cliFlagDisableKeychainTest)
|
||||||
|
}
|
||||||
|
|
||||||
app.Action = run
|
app.Action = run
|
||||||
|
|
||||||
return app
|
return app
|
||||||
@ -236,9 +287,9 @@ func run(c *cli.Context) error {
|
|||||||
|
|
||||||
return withSingleInstance(settings, locations.GetLockFile(), version, func() error {
|
return withSingleInstance(settings, locations.GetLockFile(), version, func() error {
|
||||||
// Look for available keychains
|
// Look for available keychains
|
||||||
return WithKeychainList(func(keychains *keychain.List) error {
|
return WithKeychainList(crashHandler, func(keychains *keychain.List) error {
|
||||||
// Unlock the encrypted vault.
|
// Unlock the encrypted vault.
|
||||||
return WithVault(locations, keychains, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
|
return WithVault(reporter, locations, keychains, crashHandler, func(v *vault.Vault, insecure, corrupt bool) error {
|
||||||
if !v.Migrated() {
|
if !v.Migrated() {
|
||||||
// Migrate old settings into the vault.
|
// Migrate old settings into the vault.
|
||||||
if err := migrateOldSettings(v); err != nil {
|
if err := migrateOldSettings(v); err != nil {
|
||||||
@ -344,7 +395,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,
|
||||||
@ -371,6 +422,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,9 +551,10 @@ func withCookieJar(vault *vault.Vault, fn func(http.CookieJar) error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithKeychainList init the list of usable keychains.
|
// WithKeychainList init the list of usable keychains.
|
||||||
func WithKeychainList(fn func(*keychain.List) error) error {
|
func WithKeychainList(panicHandler async.PanicHandler, fn func(*keychain.List) error) error {
|
||||||
logrus.Debug("Creating keychain list")
|
logrus.Debug("Creating keychain list")
|
||||||
defer logrus.Debug("Keychain list stop")
|
defer logrus.Debug("Keychain list stop")
|
||||||
|
defer async.HandlePanic(panicHandler)
|
||||||
return fn(keychain.NewList())
|
return fn(keychain.NewList())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,3 +575,7 @@ func setDeviceCookies(jar *cookies.Jar) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func onMacOS() bool {
|
||||||
|
return runtime.GOOS == "darwin"
|
||||||
|
}
|
||||||
|
|||||||
@ -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.
|
||||||
//
|
//
|
||||||
@ -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
|
||||||
|
|||||||
@ -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.
|
||||||
//
|
//
|
||||||
@ -25,17 +25,18 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
|
"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/sentry"
|
||||||
"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/keychain"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WithVault(locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler, fn func(*vault.Vault, bool, bool) error) error {
|
func WithVault(reporter *sentry.Reporter, 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, keychains, panicHandler)
|
encVault, insecure, corrupt, err := newVault(reporter, 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)
|
||||||
}
|
}
|
||||||
@ -57,7 +58,7 @@ func WithVault(locations *locations.Locations, keychains *keychain.List, panicHa
|
|||||||
return fn(encVault, insecure, corrupt != nil)
|
return fn(encVault, insecure, corrupt != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVault(locations *locations.Locations, keychains *keychain.List, panicHandler async.PanicHandler) (*vault.Vault, bool, error, error) {
|
func newVault(reporter *sentry.Reporter, 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, nil, fmt.Errorf("could not get vault dir: %w", err)
|
return nil, false, nil, fmt.Errorf("could not get vault dir: %w", err)
|
||||||
@ -71,6 +72,16 @@ func newVault(locations *locations.Locations, keychains *keychain.List, panicHan
|
|||||||
)
|
)
|
||||||
|
|
||||||
if key, err := loadVaultKey(vaultDir, keychains); err != nil {
|
if key, err := loadVaultKey(vaultDir, keychains); err != nil {
|
||||||
|
if reporter != nil {
|
||||||
|
if rerr := reporter.ReportMessageWithContext("Could not load/create vault key", map[string]any{
|
||||||
|
"keychainDefaultHelper": keychains.GetDefaultHelper(),
|
||||||
|
"keychainUsableHelpersLength": len(keychains.GetHelpers()),
|
||||||
|
"error": err.Error(),
|
||||||
|
}); rerr != nil {
|
||||||
|
logrus.WithError(err).Info("Failed to report keychain issue to Sentry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logrus.WithError(err).Error("Could not load/create vault key")
|
logrus.WithError(err).Error("Could not load/create vault key")
|
||||||
insecure = true
|
insecure = true
|
||||||
|
|
||||||
@ -104,14 +115,15 @@ func loadVaultKey(vaultDir string, keychains *keychain.List) ([]byte, error) {
|
|||||||
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,8 +45,11 @@ 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/ProtonMail/proton-bridge/v3/pkg/keychain"
|
||||||
@ -51,6 +58,8 @@ import (
|
|||||||
"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
|
||||||
@ -130,8 +139,19 @@ type Bridge struct {
|
|||||||
|
|
||||||
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
|
||||||
@ -245,6 +265,10 @@ 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)
|
||||||
|
|
||||||
|
observabilityService := observability.NewService(ctx, panicHandler)
|
||||||
|
|
||||||
bridge := &Bridge{
|
bridge := &Bridge{
|
||||||
vault: vault,
|
vault: vault,
|
||||||
|
|
||||||
@ -284,7 +308,13 @@ func newBridge(
|
|||||||
lastVersion: lastVersion,
|
lastVersion: lastVersion,
|
||||||
|
|
||||||
tasks: tasks,
|
tasks: tasks,
|
||||||
syncService: syncservice.NewService(reporter, panicHandler),
|
syncService: syncservice.NewService(panicHandler, observabilityService),
|
||||||
|
|
||||||
|
unleashService: unleashService,
|
||||||
|
|
||||||
|
observabilityService: observabilityService,
|
||||||
|
|
||||||
|
notificationStore: notifications.NewStore(locator.ProvideNotificationsCachePath),
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.serverManager = imapsmtpserver.NewService(context.Background(),
|
bridge.serverManager = imapsmtpserver.NewService(context.Background(),
|
||||||
@ -295,8 +325,12 @@ func newBridge(
|
|||||||
reporter,
|
reporter,
|
||||||
uidValidityGenerator,
|
uidValidityGenerator,
|
||||||
&bridgeIMAPSMTPTelemetry{b: bridge},
|
&bridgeIMAPSMTPTelemetry{b: bridge},
|
||||||
|
observabilityService,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
@ -309,6 +343,10 @@ func newBridge(
|
|||||||
|
|
||||||
bridge.syncService.Run()
|
bridge.syncService.Run()
|
||||||
|
|
||||||
|
bridge.unleashService.Run()
|
||||||
|
|
||||||
|
bridge.observabilityService.Run(bridge)
|
||||||
|
|
||||||
return bridge, nil
|
return bridge, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,7 +360,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:
|
||||||
@ -337,7 +375,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{})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -350,7 +388,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
|
||||||
@ -359,7 +397,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{})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -367,7 +405,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{})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -375,7 +413,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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -383,7 +421,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)
|
||||||
}
|
}
|
||||||
@ -396,7 +434,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 {
|
||||||
@ -434,7 +472,10 @@ 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.
|
// Stop heart beat before closing users.
|
||||||
bridge.heartbeat.stop()
|
bridge.heartbeat.stop()
|
||||||
@ -448,7 +489,7 @@ 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()
|
bridge.syncService.Close()
|
||||||
@ -459,6 +500,9 @@ func (bridge *Bridge) Close(ctx context.Context) {
|
|||||||
// 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()
|
||||||
@ -474,12 +518,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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -512,13 +556,13 @@ func (bridge *Bridge) remWatcher(watcher *watcher.Watcher[events.Event]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) onStatusUp(_ context.Context) {
|
func (bridge *Bridge) onStatusUp(_ context.Context) {
|
||||||
logrus.Info("Handling API status up")
|
logPkg.Info("Handling API status up")
|
||||||
|
|
||||||
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")
|
||||||
|
|
||||||
for backoff := time.Second; ; backoff = min(backoff*2, 30*time.Second) {
|
for backoff := time.Second; ; backoff = min(backoff*2, 30*time.Second) {
|
||||||
select {
|
select {
|
||||||
@ -526,10 +570,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
|
||||||
}
|
}
|
||||||
@ -537,6 +581,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 {
|
||||||
@ -549,10 +636,97 @@ func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(a, b time.Duration) time.Duration {
|
func (bridge *Bridge) HasAPIConnection() bool {
|
||||||
if a < b {
|
return bridge.api.GetStatus() == proton.StatusUp
|
||||||
return a
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
return b
|
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.AddMetrics(metric)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) PushDistinctObservabilityMetrics(errType observability.DistinctionErrorTypeEnum, metrics ...proton.ObservabilityMetric) {
|
||||||
|
bridge.observabilityService.AddDistinctMetrics(errType, metrics...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) ModifyObservabilityHeartbeatInterval(duration time.Duration) {
|
||||||
|
bridge.observabilityService.ModifyHeartbeatInterval(duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) ReportMessageWithContext(message string, messageCtx reporter.Context) {
|
||||||
|
if err := bridge.reporter.ReportMessageWithContext(message, messageCtx); err != nil {
|
||||||
|
logPkg.WithFields(logrus.Fields{
|
||||||
|
"err": err,
|
||||||
|
"sentryMessage": message,
|
||||||
|
"messageCtx": messageCtx,
|
||||||
|
}).Info("Error occurred when sending Report to Sentry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUsers is only used for testing purposes.
|
||||||
|
func (bridge *Bridge) GetUsers() map[string]*user.User {
|
||||||
|
return 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.
|
||||||
//
|
//
|
||||||
@ -76,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()
|
||||||
@ -125,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()
|
||||||
@ -156,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")
|
||||||
|
|
||||||
@ -183,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() }()
|
||||||
@ -220,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")
|
||||||
})
|
})
|
||||||
@ -234,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() }()
|
||||||
@ -273,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
|
||||||
@ -332,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() }()
|
||||||
@ -401,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) {
|
||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -520,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()
|
||||||
@ -543,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)
|
||||||
|
|
||||||
@ -551,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())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -571,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)
|
||||||
|
|
||||||
@ -586,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.
|
||||||
withBridgeWaitForServers(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) {
|
||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -596,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)
|
||||||
|
|
||||||
@ -609,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) {
|
||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -623,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()
|
||||||
@ -699,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)
|
||||||
|
|
||||||
@ -714,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)
|
||||||
|
|
||||||
@ -751,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()
|
||||||
@ -796,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)))
|
||||||
@ -826,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()
|
||||||
@ -854,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)
|
||||||
@ -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.
|
||||||
//
|
//
|
||||||
@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/ProtonMail/go-proton-api"
|
"github.com/ProtonMail/go-proton-api"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"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/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -80,12 +79,6 @@ func (bridge *Bridge) ReportBug(ctx context.Context, report *ReportBugReq) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
safe.RLock(func() {
|
|
||||||
for _, user := range bridge.users {
|
|
||||||
user.ReportBugSent()
|
|
||||||
}
|
|
||||||
}, bridge.usersLock)
|
|
||||||
|
|
||||||
// if we have a token we can append more attachment to the bugReport
|
// if we have a token we can append more attachment to the bugReport
|
||||||
for i, att := range attachments {
|
for i, att := range attachments {
|
||||||
if i == 0 && report.IncludeLogs {
|
if i == 0 && report.IncludeLogs {
|
||||||
|
|||||||
@ -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.
|
||||||
//
|
//
|
||||||
@ -34,7 +34,7 @@ import (
|
|||||||
// 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")
|
||||||
|
|||||||
@ -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.
|
||||||
//
|
//
|
||||||
@ -73,15 +73,15 @@ func (h *heartBeatState) init(bridge *Bridge, manager telemetry.HeartbeatManager
|
|||||||
for _, user := range bridge.users {
|
for _, user := range bridge.users {
|
||||||
if user.GetAddressMode() == vault.SplitMode {
|
if user.GetAddressMode() == vault.SplitMode {
|
||||||
splitMode = true
|
splitMode = true
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
h.SetUserPlan(user.GetUserPlanName())
|
||||||
}
|
}
|
||||||
var nbAccount = len(bridge.users)
|
var numberConnectedAccounts = len(bridge.users)
|
||||||
h.SetNbAccount(nbAccount)
|
h.SetNumberConnectedAccounts(numberConnectedAccounts)
|
||||||
h.SetSplitMode(splitMode)
|
h.SetSplitMode(splitMode)
|
||||||
|
|
||||||
// Do not try to send if there is no user yet.
|
// Do not try to send if there is no user yet.
|
||||||
if nbAccount > 0 {
|
if numberConnectedAccounts > 0 {
|
||||||
defer h.start()
|
defer h.start()
|
||||||
}
|
}
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
@ -97,7 +97,7 @@ func (h *heartBeatState) start() {
|
|||||||
h.taskStarted = true
|
h.taskStarted = true
|
||||||
|
|
||||||
h.task.PeriodicOrTrigger(h.taskInterval, 0, func(ctx context.Context) {
|
h.task.PeriodicOrTrigger(h.taskInterval, 0, func(ctx context.Context) {
|
||||||
logrus.Debug("Checking for heartbeat")
|
logrus.WithField("pkg", "bridge/heartbeat").Debug("Checking for heartbeat")
|
||||||
|
|
||||||
h.TrySending(ctx)
|
h.TrySending(ctx)
|
||||||
})
|
})
|
||||||
@ -135,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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,7 +17,9 @@
|
|||||||
|
|
||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import "github.com/sirupsen/logrus"
|
import (
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
func (bridge *Bridge) GetCurrentUserAgent() string {
|
func (bridge *Bridge) GetCurrentUserAgent() string {
|
||||||
return bridge.identifier.GetUserAgent()
|
return bridge.identifier.GetUserAgent()
|
||||||
@ -30,6 +32,8 @@ func (bridge *Bridge) SetCurrentPlatform(platform string) {
|
|||||||
func (bridge *Bridge) setUserAgent(name, version string) {
|
func (bridge *Bridge) setUserAgent(name, version string) {
|
||||||
currentUserAgent := bridge.identifier.GetClientString()
|
currentUserAgent := bridge.identifier.GetClientString()
|
||||||
|
|
||||||
|
bridge.heartbeat.SetContactedByAppleNotes(name)
|
||||||
|
|
||||||
bridge.identifier.SetClient(name, version)
|
bridge.identifier.SetClient(name, version)
|
||||||
|
|
||||||
newUserAgent := bridge.identifier.GetClientString()
|
newUserAgent := bridge.identifier.GetClientString()
|
||||||
@ -54,6 +58,7 @@ func (b *bridgeUserAgentUpdater) HasClient() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *bridgeUserAgentUpdater) SetClient(name, version string) {
|
func (b *bridgeUserAgentUpdater) SetClient(name, version string) {
|
||||||
|
b.heartbeat.SetContactedByAppleNotes(name)
|
||||||
b.identifier.SetClient(name, version)
|
b.identifier.SetClient(name, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 (
|
|||||||
imapEvents "github.com/ProtonMail/gluon/events"
|
imapEvents "github.com/ProtonMail/gluon/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
"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/unleash"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
"github.com/ProtonMail/proton-bridge/v3/internal/useragent"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -35,10 +36,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 +49,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 +60,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",
|
||||||
@ -91,6 +94,10 @@ func (b *bridgeIMAPSettings) LogServer() bool {
|
|||||||
return b.b.logIMAPServer
|
return b.b.logIMAPServer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *bridgeIMAPSettings) DisableIMAPAuthenticate() bool {
|
||||||
|
return b.b.unleashService.GetFlagValue(unleash.IMAPAuthenticateCommandDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *bridgeIMAPSettings) Port() int {
|
func (b *bridgeIMAPSettings) Port() int {
|
||||||
return b.b.vault.GetIMAPPort()
|
return b.b.vault.GetIMAPPort()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -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
internal/bridge/mocks/observability_mocks.go
Normal file
49
internal/bridge/mocks/observability_mocks.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/go-proton-api"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockObservabilitySender struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockObservabilitySenderRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockObservabilitySenderRecorder struct {
|
||||||
|
mock *MockObservabilitySender
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockObservabilitySender(ctrl *gomock.Controller) *MockObservabilitySender {
|
||||||
|
mock := &MockObservabilitySender{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockObservabilitySenderRecorder{mock: mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockObservabilitySender) EXPECT() *MockObservabilitySenderRecorder { return m.recorder }
|
||||||
|
|
||||||
|
func (m *MockObservabilitySender) AddDistinctMetrics(errType observability.DistinctionErrorTypeEnum, _ ...proton.ObservabilityMetric) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "AddDistinctMetrics", errType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockObservabilitySender) AddMetrics(metrics ...proton.ObservabilityMetric) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "AddMetrics", metrics)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr *MockObservabilitySenderRecorder) AddDistinctMetrics(errType observability.DistinctionErrorTypeEnum, _ ...proton.ObservabilityMetric) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock,
|
||||||
|
"AddDistinctMetrics",
|
||||||
|
reflect.TypeOf((*MockObservabilitySender)(nil).AddDistinctMetrics),
|
||||||
|
errType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr *MockObservabilitySenderRecorder) AddMetrics(metrics ...proton.ObservabilityMetric) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMetrics", reflect.TypeOf((*MockObservabilitySender)(nil).AddMetrics), metrics)
|
||||||
|
}
|
||||||
164
internal/bridge/observability_test.go
Normal file
164
internal/bridge/observability_test.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBridge_Observability_Heartbeat(t *testing.T) {
|
||||||
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
|
throttlePeriod := time.Millisecond * 300
|
||||||
|
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.ModifyObservabilityHeartbeatInterval(throttlePeriod)
|
||||||
|
|
||||||
|
require.Equal(t, 0, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
time.Sleep(time.Millisecond * 150)
|
||||||
|
require.Equal(t, 0, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
require.Equal(t, 1, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
time.Sleep(time.Millisecond * 350)
|
||||||
|
require.Equal(t, 2, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
time.Sleep(time.Millisecond * 350)
|
||||||
|
require.Equal(t, 3, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBridge_Observability_UserMetric(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) {
|
||||||
|
userMetricPeriod := time.Millisecond * 200
|
||||||
|
heartbeatPeriod := time.Second * 10
|
||||||
|
throttlePeriod := time.Millisecond * 100
|
||||||
|
observability.ModifyUserMetricInterval(userMetricPeriod)
|
||||||
|
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.ModifyObservabilityHeartbeatInterval(heartbeatPeriod)
|
||||||
|
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
require.Equal(t, 0, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
bridge.PushDistinctObservabilityMetrics(observability.SyncError, testMetric)
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
// We're expecting two observability metrics to be sent, the actual metric + the user metric.
|
||||||
|
require.Equal(t, 2, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
bridge.PushDistinctObservabilityMetrics(observability.SyncError, testMetric)
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
// We're expecting only a single metric to be sent, since the user metric update has been sent already within the predefined period.
|
||||||
|
require.Equal(t, 3, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
bridge.PushDistinctObservabilityMetrics(observability.SyncError, testMetric)
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
// Two metric updates should be sent again.
|
||||||
|
require.Equal(t, 5, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
|
||||||
|
bridge.PushDistinctObservabilityMetrics(observability.SyncError, testMetric)
|
||||||
|
time.Sleep(throttlePeriod)
|
||||||
|
// Only a single one should be sent.
|
||||||
|
require.Equal(t, 6, len(s.GetObservabilityStatistics().Metrics))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -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.
|
||||||
//
|
//
|
||||||
@ -46,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)
|
||||||
|
|
||||||
@ -409,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)
|
||||||
|
|
||||||
@ -431,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())))
|
||||||
@ -617,9 +607,6 @@ Hello world
|
|||||||
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)
|
||||||
|
|
||||||
@ -639,8 +626,6 @@ Hello world
|
|||||||
messageInlineImageFollowedByText,
|
messageInlineImageFollowedByText,
|
||||||
}
|
}
|
||||||
|
|
||||||
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())))
|
||||||
@ -714,17 +699,12 @@ func TestBridge_SendAddressDisabled(t *testing.T) {
|
|||||||
require.NoError(t, s.ChangeAddressAllowSend(senderUserID, addrID, false))
|
require.NoError(t, s.ChangeAddressAllowSend(senderUserID, addrID, false))
|
||||||
|
|
||||||
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, "sender", password, nil, nil)
|
senderUserID, err := bridge.LoginFull(ctx, "sender", password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = bridge.LoginFull(ctx, "recipient", password, nil, nil)
|
_, err = bridge.LoginFull(ctx, "recipient", password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
smtpWaiter.Wait()
|
|
||||||
|
|
||||||
recipientInfo, err := bridge.GetUserInfo(recipientUserID)
|
recipientInfo, err := bridge.GetUserInfo(recipientUserID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -750,7 +730,7 @@ func TestBridge_SendAddressDisabled(t *testing.T) {
|
|||||||
strings.NewReader("Subject: Test 1\r\n\r\nHello world!"),
|
strings.NewReader("Subject: Test 1\r\n\r\nHello world!"),
|
||||||
)
|
)
|
||||||
|
|
||||||
smtpErr := smtpservice.NewErrCanNotSendOnAddress(senderInfo.Addresses[0])
|
smtpErr := smtpservice.NewErrCannotSendFromAddress(senderInfo.Addresses[0])
|
||||||
require.Equal(t, fmt.Sprintf("Error: %v", smtpErr.Error()), err.Error())
|
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.
|
||||||
//
|
//
|
||||||
@ -29,16 +29,12 @@ 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/golang/mock/gomock"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
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, _ *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,19 +50,11 @@ 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)
|
||||||
defer func() { require.NoError(t, conn.Close()) }()
|
defer func() { require.NoError(t, conn.Close()) }()
|
||||||
|
|
||||||
// Sending garbage to the IMAP port should cause the bridge to report it.
|
|
||||||
mocks.Reporter.EXPECT().ReportMessageWithContext(
|
|
||||||
gomock.Eq("Failed to parse IMAP command"),
|
|
||||||
gomock.Any(),
|
|
||||||
).Return(nil)
|
|
||||||
|
|
||||||
// Read lines from the IMAP port.
|
// Read lines from the IMAP port.
|
||||||
lineCh := liner.New(conn).Lines(func() error { return nil })
|
lineCh := liner.New(conn).Lines(func() 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.
|
||||||
//
|
//
|
||||||
@ -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)
|
require.NoError(t, err)
|
||||||
|
smtpClient.Close() //nolint:errcheck
|
||||||
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)
|
|
||||||
|
|
||||||
_, 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.
|
||||||
//
|
//
|
||||||
@ -27,7 +27,6 @@ import (
|
|||||||
"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) {
|
||||||
@ -134,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()
|
||||||
}
|
}
|
||||||
@ -149,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,24 +318,23 @@ func (bridge *Bridge) GetKnowledgeBaseSuggestions(userInput string) (kb.ArticleL
|
|||||||
// 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.
|
||||||
func (bridge *Bridge) FactoryReset(ctx context.Context) {
|
func (bridge *Bridge) FactoryReset(ctx context.Context) {
|
||||||
useTelemetry := !bridge.GetTelemetryDisabled()
|
|
||||||
// Delete all the users.
|
// Delete all the users.
|
||||||
safe.Lock(func() {
|
safe.Lock(func() {
|
||||||
for _, user := range bridge.users {
|
for _, user := range bridge.users {
|
||||||
bridge.logoutUser(ctx, user, true, true, useTelemetry)
|
bridge.logoutUser(ctx, user, true, true)
|
||||||
}
|
}
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
|
|
||||||
// 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.
|
||||||
//
|
//
|
||||||
@ -232,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()
|
||||||
|
|
||||||
@ -246,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))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -254,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()
|
||||||
|
|
||||||
@ -592,7 +592,7 @@ func TestBridge_CorruptedVaultClearsPreviousIMAPSyncState(t *testing.T) {
|
|||||||
createNumMessages(ctx, t, c, addrID, labelID, 100)
|
createNumMessages(ctx, t, c, addrID, labelID, 100)
|
||||||
})
|
})
|
||||||
|
|
||||||
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) {
|
||||||
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()
|
||||||
|
|
||||||
@ -625,7 +625,7 @@ func TestBridge_CorruptedVaultClearsPreviousIMAPSyncState(t *testing.T) {
|
|||||||
require.NoError(t, os.WriteFile(filepath.Join(settingsPath, "vault.enc"), []byte("Trash!"), 0o600))
|
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.
|
// 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, _ *bridge.Mocks) {
|
||||||
_, err := bridge.LoginFull(context.Background(), "imap", password, nil, nil)
|
_, err := bridge.LoginFull(context.Background(), "imap", password, nil, nil)
|
||||||
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.
|
||||||
//
|
//
|
||||||
@ -28,11 +28,12 @@ type Locator interface {
|
|||||||
ProvideLogsPath() (string, error)
|
ProvideLogsPath() (string, error)
|
||||||
ProvideGluonCachePath() (string, error)
|
ProvideGluonCachePath() (string, error)
|
||||||
ProvideGluonDataPath() (string, error)
|
ProvideGluonDataPath() (string, error)
|
||||||
ProvideStatsPath() (string, error)
|
|
||||||
GetLicenseFilePath() string
|
GetLicenseFilePath() string
|
||||||
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 {
|
||||||
|
|||||||
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.
|
||||||
//
|
//
|
||||||
@ -21,6 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gluon/reporter"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
|
||||||
@ -115,6 +116,17 @@ func (bridge *Bridge) installUpdate(ctx context.Context, job installJob) {
|
|||||||
err := bridge.updater.InstallUpdate(ctx, bridge.api, job.version)
|
err := bridge.updater.InstallUpdate(ctx, bridge.api, job.version)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
case errors.Is(err, updater.ErrDownloadVerify):
|
||||||
|
// BRIDGE-207: if download or verification fails, we do not want to trigger a manual update. We report in the log and to Sentry
|
||||||
|
// and we fail silently.
|
||||||
|
log.WithError(err).Error("The update could not be installed, but we will fail silently")
|
||||||
|
if reporterErr := bridge.reporter.ReportMessageWithContext(
|
||||||
|
"Cannot download or verify update",
|
||||||
|
reporter.Context{"error": err},
|
||||||
|
); reporterErr != nil {
|
||||||
|
log.WithError(reporterErr).Error("Failed to report update error")
|
||||||
|
}
|
||||||
|
|
||||||
case errors.Is(err, updater.ErrUpdateAlreadyInstalled):
|
case errors.Is(err, updater.ErrUpdateAlreadyInstalled):
|
||||||
log.Info("The update was already installed")
|
log.Info("The update was already installed")
|
||||||
|
|
||||||
|
|||||||
@ -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,16 +28,20 @@ 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"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/try"
|
"github.com/ProtonMail/proton-bridge/v3/internal/try"
|
||||||
|
"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/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"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 (
|
||||||
@ -121,23 +125,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
|
||||||
@ -152,12 +161,13 @@ 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)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -165,7 +175,7 @@ func (bridge *Bridge) LoginUser(
|
|||||||
// Failure to unlock will allow retries, so we do not delete auth.
|
// Failure to unlock will allow retries, so we do not delete auth.
|
||||||
if !errors.Is(err, ErrFailedToUnlock) {
|
if !errors.Is(err, ErrFailedToUnlock) {
|
||||||
if deleteErr := client.AuthDelete(ctx); deleteErr != nil {
|
if deleteErr := client.AuthDelete(ctx); deleteErr != nil {
|
||||||
logrus.WithError(deleteErr).Error("Failed to delete auth")
|
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)
|
||||||
@ -188,15 +198,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 {
|
||||||
@ -211,7 +222,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 {
|
||||||
@ -223,10 +234,10 @@ func (bridge *Bridge) LoginFull(
|
|||||||
keyPass = password
|
keyPass = password
|
||||||
}
|
}
|
||||||
|
|
||||||
userID, err := bridge.LoginUser(ctx, client, auth, keyPass)
|
userID, err := bridge.LoginUser(ctx, client, auth, keyPass, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if deleteErr := client.AuthDelete(ctx); deleteErr != nil {
|
if deleteErr := client.AuthDelete(ctx); deleteErr != nil {
|
||||||
logrus.WithError(err).Error("Failed to delete auth")
|
logUser.WithError(err).Error("Failed to delete auth")
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", err
|
return "", err
|
||||||
@ -237,7 +248,7 @@ func (bridge *Bridge) LoginFull(
|
|||||||
|
|
||||||
// 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]
|
||||||
@ -245,7 +256,7 @@ func (bridge *Bridge) LogoutUser(ctx context.Context, userID string) error {
|
|||||||
return ErrNoSuchUser
|
return ErrNoSuchUser
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.logoutUser(ctx, user, true, false, false)
|
bridge.logoutUser(ctx, user, true, false)
|
||||||
|
|
||||||
bridge.publish(events.UserLoggedOut{
|
bridge.publish(events.UserLoggedOut{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
@ -257,7 +268,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 {
|
||||||
@ -270,7 +281,7 @@ func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user, ok := bridge.users[userID]; ok {
|
if user, ok := bridge.users[userID]; ok {
|
||||||
bridge.logoutUser(ctx, user, true, true, !bridge.GetTelemetryDisabled())
|
bridge.logoutUser(ctx, user, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := imapservice.DeleteSyncState(syncConfigDir, userID); err != nil {
|
if err := imapservice.DeleteSyncState(syncConfigDir, userID); err != nil {
|
||||||
@ -278,7 +289,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{
|
||||||
@ -291,7 +302,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]
|
||||||
@ -327,7 +338,7 @@ 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.RLockRet(func() error {
|
return safe.RLockRet(func() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@ -338,31 +349,17 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
if doResync {
|
if doResync {
|
||||||
if rerr := bridge.reporter.ReportMessageWithContext(
|
|
||||||
"Failed to handle event: feedback resync",
|
|
||||||
reporter.Context{"user_id": userID},
|
|
||||||
); rerr != nil {
|
|
||||||
logrus.WithError(rerr).Error("Failed to report feedback failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
return user.BadEventFeedbackResync(ctx)
|
return user.BadEventFeedbackResync(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rerr := bridge.reporter.ReportMessageWithContext(
|
bridge.logoutUser(ctx, user, true, false)
|
||||||
"Failed to handle event: feedback logout",
|
|
||||||
reporter.Context{"user_id": userID},
|
|
||||||
); rerr != nil {
|
|
||||||
logrus.WithError(rerr).Error("Failed to report feedback failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
bridge.logoutUser(ctx, user, true, false, false)
|
|
||||||
|
|
||||||
bridge.publish(events.UserLoggedOut{
|
bridge.publish(events.UserLoggedOut{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
@ -372,8 +369,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)
|
||||||
}
|
}
|
||||||
@ -403,11 +400,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)")
|
||||||
@ -451,7 +448,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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,24 +493,24 @@ func (bridge *Bridge) addUser(
|
|||||||
|
|
||||||
if err := bridge.addUserWithVault(ctx, client, apiUser, vaultUser, isNew); 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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,11 +528,6 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
vault *vault.User,
|
vault *vault.User,
|
||||||
isNew bool,
|
isNew bool,
|
||||||
) error {
|
) error {
|
||||||
statsPath, err := bridge.locator.ProvideStatsPath()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get Statistics directory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
syncSettingsPath, err := bridge.locator.ProvideIMAPSyncConfigPath()
|
syncSettingsPath, err := bridge.locator.ProvideIMAPSyncConfigPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get IMAP sync config path: %w", err)
|
return fmt.Errorf("failed to get IMAP sync config path: %w", err)
|
||||||
@ -550,14 +542,16 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
bridge.panicHandler,
|
bridge.panicHandler,
|
||||||
bridge.vault.GetShowAllMail(),
|
bridge.vault.GetShowAllMail(),
|
||||||
bridge.vault.GetMaxSyncMemory(),
|
bridge.vault.GetMaxSyncMemory(),
|
||||||
statsPath,
|
|
||||||
bridge,
|
bridge,
|
||||||
bridge.serverManager,
|
bridge.serverManager,
|
||||||
bridge.serverManager,
|
bridge.serverManager,
|
||||||
&bridgeEventSubscription{b: bridge},
|
&bridgeEventSubscription{b: bridge},
|
||||||
bridge.syncService,
|
bridge.syncService,
|
||||||
|
bridge.observabilityService,
|
||||||
syncSettingsPath,
|
syncSettingsPath,
|
||||||
isNew,
|
isNew,
|
||||||
|
bridge.notificationStore,
|
||||||
|
bridge.unleashService.GetFlagValue,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create user: %w", err)
|
return fmt.Errorf("failed to create user: %w", err)
|
||||||
@ -567,7 +561,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")
|
||||||
@ -590,12 +584,17 @@ func (bridge *Bridge) addUserWithVault(
|
|||||||
// Finally, save the user in the bridge.
|
// Finally, save the user in the bridge.
|
||||||
safe.Lock(func() {
|
safe.Lock(func() {
|
||||||
bridge.users[apiUser.ID] = user
|
bridge.users[apiUser.ID] = user
|
||||||
bridge.heartbeat.SetNbAccount(len(bridge.users))
|
bridge.heartbeat.SetNumberConnectedAccounts(len(bridge.users))
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
|
|
||||||
|
// Set user plan if its of a higher rank.
|
||||||
|
bridge.heartbeat.SetUserPlan(user.GetUserPlanName())
|
||||||
|
|
||||||
// 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.
|
||||||
bridge.heartbeat.start()
|
bridge.heartbeat.start()
|
||||||
|
|
||||||
|
user.PublishEvent(ctx, events.UserLoadedCheckResync{UserID: user.ID()})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -609,26 +608,21 @@ func (bridge *Bridge) newVaultUser(
|
|||||||
return bridge.vault.GetOrAddUser(apiUser.ID, apiUser.Name, apiUser.Email, authUID, authRef, saltedKeyPass)
|
return bridge.vault.GetOrAddUser(apiUser.ID, apiUser.Name, apiUser.Email, authUID, authRef, saltedKeyPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
// logout logs out the given user, optionally logging them out from the API too.
|
// logoutUser logs out the given user, optionally logging them out from the API and deleting user related gluon data.
|
||||||
func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI, withData, withTelemetry bool) {
|
func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI, withData bool) {
|
||||||
defer delete(bridge.users, user.ID())
|
defer delete(bridge.users, user.ID())
|
||||||
|
|
||||||
// if this is actually a remove account
|
logUser.WithFields(logrus.Fields{
|
||||||
if withData && withAPI {
|
|
||||||
user.SendConfigStatusAbort(ctx, withTelemetry)
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.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, withData, bridge.unleashService.GetFlagValue(unleash.UserRemovalGluonDataCleanupDisabled)); 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.SetNumberConnectedAccounts(len(bridge.users) - 1)
|
||||||
|
|
||||||
user.Close()
|
user.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,15 +36,14 @@ 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.UncategorizedEventError:
|
case events.UserLoadedCheckResync:
|
||||||
bridge.handleUncategorizedErrorEvent(event)
|
user.VerifyResyncAndExecute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) handleUserDeauth(ctx context.Context, user *user.User) {
|
func (bridge *Bridge) handleUserDeauth(ctx context.Context, user *user.User) {
|
||||||
safe.Lock(func() {
|
safe.Lock(func() {
|
||||||
bridge.logoutUser(ctx, user, false, false, false)
|
bridge.logoutUser(ctx, user, false, false)
|
||||||
user.ReportConfigStatusFailure("User deauth.")
|
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,18 +57,9 @@ 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)
|
||||||
}, bridge.usersLock)
|
}, bridge.usersLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) handleUncategorizedErrorEvent(event events.UncategorizedEventError) {
|
|
||||||
if rerr := bridge.reporter.ReportMessageWithContext("Failed to handle due to uncategorized error", reporter.Context{
|
|
||||||
"error_type": internal.ErrCauseType(event.Error),
|
|
||||||
"error": event.Error,
|
|
||||||
}); rerr != nil {
|
|
||||||
logrus.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.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -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"
|
||||||
@ -70,24 +71,24 @@ func prepareMobileConfig(
|
|||||||
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: displayName,
|
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,228 +0,0 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const version = "1.0.0"
|
|
||||||
|
|
||||||
func LoadConfigurationStatus(filepath string) (*ConfigurationStatus, error) {
|
|
||||||
status := ConfigurationStatus{
|
|
||||||
FilePath: filepath,
|
|
||||||
DataLock: safe.NewRWMutex(),
|
|
||||||
Data: &ConfigurationStatusData{},
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(filepath); err == nil {
|
|
||||||
if err := status.Load(); err == nil {
|
|
||||||
return &status, nil
|
|
||||||
}
|
|
||||||
logrus.WithError(err).Warn("Cannot load configuration status file. Reset it.")
|
|
||||||
}
|
|
||||||
|
|
||||||
status.Data.init()
|
|
||||||
if err := status.Save(); err != nil {
|
|
||||||
return &status, err
|
|
||||||
}
|
|
||||||
return &status, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) Load() error {
|
|
||||||
bytes, err := os.ReadFile(status.FilePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadata MetadataOnly
|
|
||||||
if err := json.Unmarshal(bytes, &metadata); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if metadata.Metadata.Version != version {
|
|
||||||
return fmt.Errorf("unsupported configstatus file version %s", metadata.Metadata.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Unmarshal(bytes, status.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) Save() error {
|
|
||||||
temp := status.FilePath + "_temp"
|
|
||||||
f, err := os.Create(temp) //nolint:gosec
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
enc := json.NewEncoder(f)
|
|
||||||
enc.SetIndent("", " ")
|
|
||||||
err = enc.Encode(status.Data)
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
logrus.WithError(err).Error("Error while closing configstatus file.")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.Rename(temp, status.FilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) IsPending() bool {
|
|
||||||
status.DataLock.RLock()
|
|
||||||
defer status.DataLock.RUnlock()
|
|
||||||
|
|
||||||
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 {
|
|
||||||
status.DataLock.RLock()
|
|
||||||
defer status.DataLock.RUnlock()
|
|
||||||
|
|
||||||
return status.Data.DataV1.FailureDetails != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) ApplySuccess() error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
status.Data.init()
|
|
||||||
status.Data.DataV1.PendingSince = time.Time{}
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) ApplyFailure(err string) error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
status.Data.init()
|
|
||||||
status.Data.DataV1.FailureDetails = err
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) ApplyProgress() error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
status.Data.DataV1.LastProgress = time.Now()
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) RecordLinkClicked(link uint64) error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
if !status.Data.hasLinkClicked(link) {
|
|
||||||
status.Data.setClickedLink(link)
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) ReportClicked() error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
if !status.Data.DataV1.ReportClick {
|
|
||||||
status.Data.DataV1.ReportClick = true
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) ReportSent() error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
if !status.Data.DataV1.ReportSent {
|
|
||||||
status.Data.DataV1.ReportSent = true
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) AutoconfigUsed(client string) error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
|
|
||||||
if client != status.Data.DataV1.Autoconf {
|
|
||||||
status.Data.DataV1.Autoconf = client
|
|
||||||
return status.Save()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (status *ConfigurationStatus) Remove() error {
|
|
||||||
status.DataLock.Lock()
|
|
||||||
defer status.DataLock.Unlock()
|
|
||||||
return os.Remove(status.FilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *ConfigurationStatusData) init() {
|
|
||||||
data.Metadata = Metadata{
|
|
||||||
Version: version,
|
|
||||||
}
|
|
||||||
data.DataV1.PendingSince = time.Now()
|
|
||||||
data.DataV1.LastProgress = time.Time{}
|
|
||||||
data.DataV1.Autoconf = ""
|
|
||||||
data.DataV1.ClickedLink = 0
|
|
||||||
data.DataV1.ReportSent = false
|
|
||||||
data.DataV1.ReportClick = false
|
|
||||||
data.DataV1.FailureDetails = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *ConfigurationStatusData) setClickedLink(pos uint64) {
|
|
||||||
data.DataV1.ClickedLink |= 1 << pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *ConfigurationStatusData) hasLinkClicked(pos uint64) bool {
|
|
||||||
val := data.DataV1.ClickedLink & (1 << pos)
|
|
||||||
return val > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *ConfigurationStatusData) clickedLinkToString() string {
|
|
||||||
var str = ""
|
|
||||||
var first = true
|
|
||||||
for i := 0; i < 64; i++ {
|
|
||||||
if data.hasLinkClicked(uint64(i)) {
|
|
||||||
if !first {
|
|
||||||
str += ","
|
|
||||||
} else {
|
|
||||||
first = false
|
|
||||||
str += "["
|
|
||||||
}
|
|
||||||
str += strconv.Itoa(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if str != "" {
|
|
||||||
str += "]"
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
@ -1,252 +0,0 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigStatus_init_virgin(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
|
|
||||||
|
|
||||||
require.Equal(t, false, config.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, true, config.Data.DataV1.LastProgress.IsZero())
|
|
||||||
|
|
||||||
require.Equal(t, "", config.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(0), config.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, false, config.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, false, config.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "", config.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_init_existing(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{Autoconf: "Mr TBird"},
|
|
||||||
}
|
|
||||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
|
||||||
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
|
|
||||||
require.Equal(t, "Mr TBird", config.Data.DataV1.Autoconf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_init_bad_version(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
var data = configstatus.ConfigurationStatusData{
|
|
||||||
Metadata: configstatus.Metadata{Version: "2.0.0"},
|
|
||||||
DataV1: configstatus.DataV1{Autoconf: "Mr TBird"},
|
|
||||||
}
|
|
||||||
require.NoError(t, dumpConfigStatusInFile(&data, file))
|
|
||||||
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config.Data.Metadata.Version)
|
|
||||||
require.Equal(t, "", config.Data.DataV1.Autoconf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_IsPending(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, true, config.IsPending())
|
|
||||||
config.Data.DataV1.PendingSince = time.Time{}
|
|
||||||
require.Equal(t, false, config.IsPending())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_IsFromFailure(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, false, config.IsFromFailure())
|
|
||||||
config.Data.DataV1.FailureDetails = "test"
|
|
||||||
require.Equal(t, true, config.IsFromFailure())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_ApplySuccess(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, true, config.IsPending())
|
|
||||||
require.NoError(t, config.ApplySuccess())
|
|
||||||
require.Equal(t, false, config.IsPending())
|
|
||||||
|
|
||||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_ApplyFailure(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, config.ApplySuccess())
|
|
||||||
|
|
||||||
require.NoError(t, config.ApplyFailure("Big Failure"))
|
|
||||||
require.Equal(t, true, config.IsFromFailure())
|
|
||||||
require.Equal(t, true, config.IsPending())
|
|
||||||
|
|
||||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "Big Failure", config2.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_ApplyProgress(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, true, config.IsPending())
|
|
||||||
require.Equal(t, true, config.Data.DataV1.LastProgress.IsZero())
|
|
||||||
|
|
||||||
require.NoError(t, config.ApplyProgress())
|
|
||||||
|
|
||||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.LastProgress.IsZero())
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_RecordLinkClicked(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, uint64(0), config.Data.DataV1.ClickedLink)
|
|
||||||
require.NoError(t, config.RecordLinkClicked(0))
|
|
||||||
require.Equal(t, uint64(1), config.Data.DataV1.ClickedLink)
|
|
||||||
require.NoError(t, config.RecordLinkClicked(1))
|
|
||||||
require.Equal(t, uint64(3), config.Data.DataV1.ClickedLink)
|
|
||||||
|
|
||||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(3), config2.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_ReportClicked(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, false, config.Data.DataV1.ReportClick)
|
|
||||||
require.NoError(t, config.ReportClicked())
|
|
||||||
require.Equal(t, true, config.Data.DataV1.ReportClick)
|
|
||||||
|
|
||||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigStatus_ReportSent(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, false, config.Data.DataV1.ReportSent)
|
|
||||||
require.NoError(t, config.ReportSent())
|
|
||||||
require.Equal(t, true, config.Data.DataV1.ReportSent)
|
|
||||||
|
|
||||||
config2, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.Autoconf)
|
|
||||||
require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
|
|
||||||
require.Equal(t, true, config2.Data.DataV1.ReportSent)
|
|
||||||
require.Equal(t, false, config2.Data.DataV1.ReportClick)
|
|
||||||
require.Equal(t, "", config2.Data.DataV1.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dumpConfigStatusInFile(data *configstatus.ConfigurationStatusData, file string) error {
|
|
||||||
f, err := os.Create(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() { _ = f.Close() }()
|
|
||||||
|
|
||||||
return json.NewEncoder(f).Encode(data)
|
|
||||||
}
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigAbortValues struct {
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigAbortDimensions struct {
|
|
||||||
ReportClick string `json:"report_click"`
|
|
||||||
ReportSent string `json:"report_sent"`
|
|
||||||
ClickedLink string `json:"clicked_link"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigAbortData struct {
|
|
||||||
MeasurementGroup string
|
|
||||||
Event string
|
|
||||||
Values ConfigSuccessValues
|
|
||||||
Dimensions ConfigSuccessDimensions
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigAbortBuilder struct{}
|
|
||||||
|
|
||||||
func (*ConfigAbortBuilder) New(config *ConfigurationStatus) ConfigAbortData {
|
|
||||||
config.DataLock.RLock()
|
|
||||||
defer config.DataLock.RUnlock()
|
|
||||||
|
|
||||||
return ConfigAbortData{
|
|
||||||
MeasurementGroup: "bridge.any.configuration",
|
|
||||||
Event: "bridge_config_abort",
|
|
||||||
Values: ConfigSuccessValues{
|
|
||||||
Duration: config.isPendingSinceMin(),
|
|
||||||
},
|
|
||||||
Dimensions: ConfigSuccessDimensions{
|
|
||||||
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
|
||||||
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
|
||||||
ClickedLink: config.Data.clickedLinkToString(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigurationAbort_default(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var builder = configstatus.ConfigAbortBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_abort", req.Event)
|
|
||||||
require.Equal(t, 0, req.Values.Duration)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportClick)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
|
||||||
require.Equal(t, "", req.Dimensions.ClickedLink)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigurationAbort_fed(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().Add(-10 * time.Minute),
|
|
||||||
LastProgress: time.Time{},
|
|
||||||
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.ConfigAbortBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_abort", req.Event)
|
|
||||||
require.Equal(t, 10, req.Values.Duration)
|
|
||||||
require.Equal(t, "true", req.Dimensions.ReportClick)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
|
||||||
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type ConfigProgressValues struct {
|
|
||||||
NbDay int `json:"nb_day"`
|
|
||||||
NbDaySinceLast int `json:"nb_day_since_last"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigProgressData struct {
|
|
||||||
MeasurementGroup string
|
|
||||||
Event string
|
|
||||||
Values ConfigProgressValues
|
|
||||||
Dimensions struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigProgressBuilder struct{}
|
|
||||||
|
|
||||||
func (*ConfigProgressBuilder) New(config *ConfigurationStatus) ConfigProgressData {
|
|
||||||
config.DataLock.RLock()
|
|
||||||
defer config.DataLock.RUnlock()
|
|
||||||
|
|
||||||
return ConfigProgressData{
|
|
||||||
MeasurementGroup: "bridge.any.configuration",
|
|
||||||
Event: "bridge_config_progress",
|
|
||||||
Values: ConfigProgressValues{
|
|
||||||
NbDay: numberOfDay(time.Now(), config.Data.DataV1.PendingSince),
|
|
||||||
NbDaySinceLast: numberOfDay(time.Now(), config.Data.DataV1.LastProgress),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func numberOfDay(now, prev time.Time) int {
|
|
||||||
if now.IsZero() || prev.IsZero() {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if now.Year() > prev.Year() {
|
|
||||||
if now.YearDay() > prev.YearDay() {
|
|
||||||
return 365 + (now.YearDay() - prev.YearDay())
|
|
||||||
}
|
|
||||||
return (prev.YearDay() + now.YearDay()) - 365
|
|
||||||
} else if now.YearDay() > prev.YearDay() {
|
|
||||||
return now.YearDay() - prev.YearDay()
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigurationProgress_default(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
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.Equal(t, 0, req.Values.NbDay)
|
|
||||||
require.Equal(t, 1, req.Values.NbDaySinceLast)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigurationProgress_fed(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(0, 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.Equal(t, 5, req.Values.NbDay)
|
|
||||||
require.Equal(t, 2, req.Values.NbDaySinceLast)
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigRecoveryValues struct {
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigRecoveryDimensions struct {
|
|
||||||
Autoconf string `json:"autoconf"`
|
|
||||||
ReportClick string `json:"report_click"`
|
|
||||||
ReportSent string `json:"report_sent"`
|
|
||||||
ClickedLink string `json:"clicked_link"`
|
|
||||||
FailureDetails string `json:"failure_details"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigRecoveryData struct {
|
|
||||||
MeasurementGroup string
|
|
||||||
Event string
|
|
||||||
Values ConfigRecoveryValues
|
|
||||||
Dimensions ConfigRecoveryDimensions
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigRecoveryBuilder struct{}
|
|
||||||
|
|
||||||
func (*ConfigRecoveryBuilder) New(config *ConfigurationStatus) ConfigRecoveryData {
|
|
||||||
config.DataLock.RLock()
|
|
||||||
defer config.DataLock.RUnlock()
|
|
||||||
|
|
||||||
return ConfigRecoveryData{
|
|
||||||
MeasurementGroup: "bridge.any.configuration",
|
|
||||||
Event: "bridge_config_recovery",
|
|
||||||
Values: ConfigRecoveryValues{
|
|
||||||
Duration: config.isPendingSinceMin(),
|
|
||||||
},
|
|
||||||
Dimensions: ConfigRecoveryDimensions{
|
|
||||||
Autoconf: config.Data.DataV1.Autoconf,
|
|
||||||
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
|
||||||
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
|
||||||
ClickedLink: config.Data.clickedLinkToString(),
|
|
||||||
FailureDetails: config.Data.DataV1.FailureDetails,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigurationRecovery_default(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var builder = configstatus.ConfigRecoveryBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_recovery", req.Event)
|
|
||||||
require.Equal(t, 0, req.Values.Duration)
|
|
||||||
require.Equal(t, "", req.Dimensions.Autoconf)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportClick)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
|
||||||
require.Equal(t, "", req.Dimensions.ClickedLink)
|
|
||||||
require.Equal(t, "", req.Dimensions.FailureDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigurationRecovery_fed(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().Add(-10 * time.Minute),
|
|
||||||
LastProgress: time.Time{},
|
|
||||||
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.ConfigRecoveryBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_recovery", req.Event)
|
|
||||||
require.Equal(t, 10, req.Values.Duration)
|
|
||||||
require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
|
|
||||||
require.Equal(t, "true", req.Dimensions.ReportClick)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
|
||||||
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
|
|
||||||
require.Equal(t, "Not an error", req.Dimensions.FailureDetails)
|
|
||||||
}
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigSuccessValues struct {
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigSuccessDimensions struct {
|
|
||||||
Autoconf string `json:"autoconf"`
|
|
||||||
ReportClick string `json:"report_click"`
|
|
||||||
ReportSent string `json:"report_sent"`
|
|
||||||
ClickedLink string `json:"clicked_link"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigSuccessData struct {
|
|
||||||
MeasurementGroup string
|
|
||||||
Event string
|
|
||||||
Values ConfigSuccessValues
|
|
||||||
Dimensions ConfigSuccessDimensions
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigSuccessBuilder struct{}
|
|
||||||
|
|
||||||
func (*ConfigSuccessBuilder) New(config *ConfigurationStatus) ConfigSuccessData {
|
|
||||||
config.DataLock.RLock()
|
|
||||||
defer config.DataLock.RUnlock()
|
|
||||||
|
|
||||||
return ConfigSuccessData{
|
|
||||||
MeasurementGroup: "bridge.any.configuration",
|
|
||||||
Event: "bridge_config_success",
|
|
||||||
Values: ConfigSuccessValues{
|
|
||||||
Duration: config.isPendingSinceMin(),
|
|
||||||
},
|
|
||||||
Dimensions: ConfigSuccessDimensions{
|
|
||||||
Autoconf: config.Data.DataV1.Autoconf,
|
|
||||||
ReportClick: strconv.FormatBool(config.Data.DataV1.ReportClick),
|
|
||||||
ReportSent: strconv.FormatBool(config.Data.DataV1.ReportSent),
|
|
||||||
ClickedLink: config.Data.clickedLinkToString(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigurationSuccess_default(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
file := filepath.Join(dir, "dummy.json")
|
|
||||||
config, err := configstatus.LoadConfigurationStatus(file)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var builder = configstatus.ConfigSuccessBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_success", req.Event)
|
|
||||||
require.Equal(t, 0, req.Values.Duration)
|
|
||||||
require.Equal(t, "", req.Dimensions.Autoconf)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportClick)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
|
||||||
require.Equal(t, "", req.Dimensions.ClickedLink)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigurationSuccess_fed(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().Add(-10 * time.Minute),
|
|
||||||
LastProgress: time.Time{},
|
|
||||||
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.ConfigSuccessBuilder{}
|
|
||||||
req := builder.New(config)
|
|
||||||
|
|
||||||
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
|
|
||||||
require.Equal(t, "bridge_config_success", req.Event)
|
|
||||||
require.Equal(t, 10, req.Values.Duration)
|
|
||||||
require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
|
|
||||||
require.Equal(t, "true", req.Dimensions.ReportClick)
|
|
||||||
require.Equal(t, "false", req.Dimensions.ReportSent)
|
|
||||||
require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
// Copyright (c) 2023 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package configstatus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ProgressCheckInterval = time.Hour
|
|
||||||
|
|
||||||
type Metadata struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MetadataOnly struct {
|
|
||||||
Metadata Metadata `json:"metadata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DataV1 struct {
|
|
||||||
PendingSince time.Time `json:"pending_since"`
|
|
||||||
LastProgress time.Time `json:"last_progress"`
|
|
||||||
Autoconf string `json:"auto_conf"`
|
|
||||||
ClickedLink uint64 `json:"clicked_link"`
|
|
||||||
ReportSent bool `json:"report_sent"`
|
|
||||||
ReportClick bool `json:"report_click"`
|
|
||||||
FailureDetails string `json:"failure_details"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigurationStatusData struct {
|
|
||||||
Metadata Metadata `json:"metadata"`
|
|
||||||
DataV1 DataV1 `json:"dataV1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigurationStatus struct {
|
|
||||||
FilePath string
|
|
||||||
DataLock safe.RWMutex
|
|
||||||
|
|
||||||
Data *ConfigurationStatusData
|
|
||||||
}
|
|
||||||
@ -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.
|
||||||
//
|
//
|
||||||
@ -66,7 +66,7 @@ const (
|
|||||||
KeyChainName = "bridge-v3"
|
KeyChainName = "bridge-v3"
|
||||||
|
|
||||||
// Host is the hostname of the bridge server.
|
// Host is the hostname of the bridge server.
|
||||||
Host = "127.0.0.1"
|
Host = "0.0.0.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nolint:goconst
|
// nolint:goconst
|
||||||
|
|||||||
@ -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