Other(refactor): Remove bridgeWrap from frontend interface

This commit is contained in:
James Houlahan
2022-08-26 15:12:19 +02:00
committed by Jakub
parent 9786deef48
commit 6bbe2d0e00
16 changed files with 307 additions and 168 deletions

View File

@ -85,6 +85,8 @@ Proton Mail Bridge includes the following 3rd party software:
* [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)
* [juniper](https://github.com/bradenaw/juniper) available under [license](https://github.com/bradenaw/juniper/blob/master/LICENSE)
* [exp](https://golang.org/x/exp) available under [license](https://cs.opensource.google/go/x/exp/+/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)
* [cascadia](https://github.com/andybalholm/cascadia) available under [license](https://github.com/andybalholm/cascadia/blob/master/LICENSE) * [cascadia](https://github.com/andybalholm/cascadia) available under [license](https://github.com/andybalholm/cascadia/blob/master/LICENSE)
* [antlr4](https://github.com/antlr/antlr4) available under [license](https://github.com/antlr/antlr4/blob/master/LICENSE) * [antlr4](https://github.com/antlr/antlr4) available under [license](https://github.com/antlr/antlr4/blob/master/LICENSE)
@ -109,7 +111,6 @@ Proton Mail Bridge includes the following 3rd party software:
github.com/shurcooL/sanitized_anchor_name github.com/shurcooL/sanitized_anchor_name
* [pflag](https://github.com/spf13/pflag) available under [license](https://github.com/spf13/pflag/blob/master/LICENSE) * [pflag](https://github.com/spf13/pflag) available under [license](https://github.com/spf13/pflag/blob/master/LICENSE)
* [tagparser](https://github.com/vmihailenco/tagparser) available under [license](https://github.com/vmihailenco/tagparser/blob/master/LICENSE) * [tagparser](https://github.com/vmihailenco/tagparser) available under [license](https://github.com/vmihailenco/tagparser/blob/master/LICENSE)
* [xerrors](https://golang.org/x/xerrors) available under [license](https://cs.opensource.google/go/x/xerrors/+/master:LICENSE)
* [genproto](https://google.golang.org/genproto) * [genproto](https://google.golang.org/genproto)
gopkg.in/yaml.v3 gopkg.in/yaml.v3
* [docker-credential-helpers](https://github.com/ProtonMail/docker-credential-helpers) available under [license](https://github.com/ProtonMail/docker-credential-helpers/blob/master/LICENSE) * [docker-credential-helpers](https://github.com/ProtonMail/docker-credential-helpers) available under [license](https://github.com/ProtonMail/docker-credential-helpers/blob/master/LICENSE)

10
go.mod
View File

@ -44,7 +44,7 @@ require (
github.com/go-resty/resty/v2 v2.6.0 github.com/go-resty/resty/v2 v2.6.0
github.com/godbus/dbus v4.1.0+incompatible github.com/godbus/dbus v4.1.0+incompatible
github.com/golang/mock v1.4.4 github.com/golang/mock v1.4.4
github.com/google/go-cmp v0.5.6 github.com/google/go-cmp v0.5.8
github.com/google/uuid v1.1.2 github.com/google/uuid v1.1.2
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e // indirect github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e // indirect
github.com/hashicorp/go-multierror v1.1.0 github.com/hashicorp/go-multierror v1.1.0
@ -67,13 +67,18 @@ require (
go.etcd.io/bbolt v1.3.6 go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
golang.org/x/text v0.3.7 golang.org/x/text v0.3.7
google.golang.org/grpc v1.46.2 google.golang.org/grpc v1.46.2
google.golang.org/protobuf v1.28.0 google.golang.org/protobuf v1.28.0
howett.net/plist v1.0.0 howett.net/plist v1.0.0
) )
require (
github.com/bradenaw/juniper v0.7.0
golang.org/x/exp v0.0.0-20220823124025-807a23277127
)
require ( require (
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f // indirect github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f // indirect
github.com/andybalholm/cascadia v1.1.0 // indirect github.com/andybalholm/cascadia v1.1.0 // indirect
@ -99,7 +104,6 @@ require (
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/vmihailenco/tagparser v0.1.2 // indirect github.com/vmihailenco/tagparser v0.1.2 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
) )

12
go.sum
View File

@ -75,6 +75,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bradenaw/juniper v0.7.0 h1:8JaJpY2Sm+EheEows6ZsS7s8ZM86Fa3yfaq5xXQH4SI=
github.com/bradenaw/juniper v0.7.0/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 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/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -221,8 +223,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.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.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/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.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@ -500,6 +503,8 @@ golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxT
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20220823124025-807a23277127 h1:S4NrSKDfihhl3+4jSTgwoIevKxX9p7Iv9x++OEIptDo=
golang.org/x/exp v0.0.0-20220823124025-807a23277127/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -593,8 +598,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
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-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -632,7 +637,6 @@ golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapK
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=

View File

@ -22,7 +22,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types" "github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/abiosoft/ishell" "github.com/abiosoft/ishell"
) )
@ -35,9 +35,13 @@ func (f *frontendCLI) completeUsernames(args []string) (usernames []string) {
if len(args) == 1 { if len(args) == 1 {
arg = args[0] arg = args[0]
} }
for _, user := range f.bridge.GetUsers() { for _, userID := range f.bridge.GetUserIDs() {
if strings.HasPrefix(strings.ToLower(user.Username()), strings.ToLower(arg)) { user, err := f.bridge.GetUserInfo(userID)
usernames = append(usernames, user.Username()) if err != nil {
panic(err)
}
if strings.HasPrefix(strings.ToLower(user.Username), strings.ToLower(arg)) {
usernames = append(usernames, user.Username)
} }
} }
return return
@ -46,7 +50,7 @@ func (f *frontendCLI) completeUsernames(args []string) (usernames []string) {
// noAccountWrapper is a decorator for functions which need any account to be properly functional. // noAccountWrapper is a decorator for functions which need any account to be properly functional.
func (f *frontendCLI) noAccountWrapper(callback func(*ishell.Context)) func(*ishell.Context) { func (f *frontendCLI) noAccountWrapper(callback func(*ishell.Context)) func(*ishell.Context) {
return func(c *ishell.Context) { return func(c *ishell.Context) {
users := f.bridge.GetUsers() users := f.bridge.GetUserIDs()
if len(users) == 0 { if len(users) == 0 {
f.Println("No active accounts. Please add account to continue.") f.Println("No active accounts. Please add account to continue.")
} else { } else {
@ -55,46 +59,54 @@ func (f *frontendCLI) noAccountWrapper(callback func(*ishell.Context)) func(*ish
} }
} }
func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) types.User { func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) users.UserInfo {
user := f.getUserByIndexOrName("") user := f.getUserByIndexOrName("")
if user != nil { if user.ID != "" {
return user return user
} }
numberOfAccounts := len(f.bridge.GetUsers()) numberOfAccounts := len(f.bridge.GetUserIDs())
indexRange := fmt.Sprintf("number between 0 and %d", numberOfAccounts-1) indexRange := fmt.Sprintf("number between 0 and %d", numberOfAccounts-1)
if len(c.Args) == 0 { if len(c.Args) == 0 {
f.Printf("Please choose %s or username.\n", indexRange) f.Printf("Please choose %s or username.\n", indexRange)
return nil return users.UserInfo{}
} }
arg := c.Args[0] arg := c.Args[0]
user = f.getUserByIndexOrName(arg) user = f.getUserByIndexOrName(arg)
if user == nil { if user.ID == "" {
f.Printf("Wrong input '%s'. Choose %s or username.\n", bold(arg), indexRange) f.Printf("Wrong input '%s'. Choose %s or username.\n", bold(arg), indexRange)
return nil return users.UserInfo{}
} }
return user return user
} }
func (f *frontendCLI) getUserByIndexOrName(arg string) types.User { func (f *frontendCLI) getUserByIndexOrName(arg string) users.UserInfo {
users := f.bridge.GetUsers() userIDs := f.bridge.GetUserIDs()
numberOfAccounts := len(users) numberOfAccounts := len(userIDs)
if numberOfAccounts == 0 { if numberOfAccounts == 0 {
return nil return users.UserInfo{}
}
res := make([]users.UserInfo, len(userIDs))
for idx, userID := range userIDs {
user, err := f.bridge.GetUserInfo(userID)
if err != nil {
panic(err)
}
res[idx] = user
} }
if numberOfAccounts == 1 { if numberOfAccounts == 1 {
return users[0] return res[0]
} }
if index, err := strconv.Atoi(arg); err == nil { if index, err := strconv.Atoi(arg); err == nil {
if index < 0 || index >= numberOfAccounts { if index < 0 || index >= numberOfAccounts {
return nil return users.UserInfo{}
} }
return users[index] return res[index]
} }
for _, user := range users { for _, user := range res {
if user.Username() == arg { if user.Username == arg {
return user return user
} }
} }
return nil return users.UserInfo{}
} }

View File

@ -23,48 +23,52 @@ import (
"github.com/ProtonMail/proton-bridge/v2/internal/bridge" "github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings" "github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types" "github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/abiosoft/ishell" "github.com/abiosoft/ishell"
) )
func (f *frontendCLI) listAccounts(c *ishell.Context) { func (f *frontendCLI) listAccounts(c *ishell.Context) {
spacing := "%-2d: %-20s (%-15s, %-15s)\n" spacing := "%-2d: %-20s (%-15s, %-15s)\n"
f.Printf(bold(strings.ReplaceAll(spacing, "d", "s")), "#", "account", "status", "address mode") f.Printf(bold(strings.ReplaceAll(spacing, "d", "s")), "#", "account", "status", "address mode")
for idx, user := range f.bridge.GetUsers() { for idx, userID := range f.bridge.GetUserIDs() {
user, err := f.bridge.GetUserInfo(userID)
if err != nil {
panic(err)
}
connected := "disconnected" connected := "disconnected"
if user.IsConnected() { if user.Connected {
connected = "connected" connected = "connected"
} }
mode := "split" mode := "split"
if user.IsCombinedAddressMode() { if user.Mode == users.CombinedMode {
mode = "combined" mode = "combined"
} }
f.Printf(spacing, idx, user.Username(), connected, mode) f.Printf(spacing, idx, user.Username, connected, mode)
} }
f.Println() f.Println()
} }
func (f *frontendCLI) showAccountInfo(c *ishell.Context) { func (f *frontendCLI) showAccountInfo(c *ishell.Context) {
user := f.askUserByIndexOrName(c) user := f.askUserByIndexOrName(c)
if user == nil { if user.ID == "" {
return return
} }
if !user.IsConnected() { if !user.Connected {
f.Printf("Please login to %s to get email client configuration.\n", bold(user.Username())) f.Printf("Please login to %s to get email client configuration.\n", bold(user.Username))
return return
} }
if user.IsCombinedAddressMode() { if user.Mode == users.CombinedMode {
f.showAccountAddressInfo(user, user.GetPrimaryAddress()) f.showAccountAddressInfo(user, user.Addresses[user.Primary])
} else { } else {
for _, address := range user.GetAddresses() { for _, address := range user.Addresses {
f.showAccountAddressInfo(user, address) f.showAccountAddressInfo(user, address)
} }
} }
} }
func (f *frontendCLI) showAccountAddressInfo(user types.User, address string) { func (f *frontendCLI) showAccountAddressInfo(user users.UserInfo, address string) {
smtpSecurity := "STARTTLS" smtpSecurity := "STARTTLS"
if f.bridge.GetBool(settings.SMTPSSLKey) { if f.bridge.GetBool(settings.SMTPSSLKey) {
smtpSecurity = "SSL" smtpSecurity = "SSL"
@ -74,7 +78,7 @@ func (f *frontendCLI) showAccountAddressInfo(user types.User, address string) {
bridge.Host, bridge.Host,
f.bridge.GetInt(settings.IMAPPortKey), f.bridge.GetInt(settings.IMAPPortKey),
address, address,
user.GetBridgePassword(), user.Password,
"STARTTLS", "STARTTLS",
) )
f.Println("") f.Println("")
@ -82,7 +86,7 @@ func (f *frontendCLI) showAccountAddressInfo(user types.User, address string) {
bridge.Host, bridge.Host,
f.bridge.GetInt(settings.SMTPPortKey), f.bridge.GetInt(settings.SMTPPortKey),
address, address,
user.GetBridgePassword(), user.Password,
smtpSecurity, smtpSecurity,
) )
f.Println("") f.Println("")
@ -95,8 +99,8 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { //nolint:funlen
loginName := "" loginName := ""
if len(c.Args) > 0 { if len(c.Args) > 0 {
user := f.getUserByIndexOrName(c.Args[0]) user := f.getUserByIndexOrName(c.Args[0])
if user != nil { if user.ID != "" {
loginName = user.GetPrimaryAddress() loginName = user.Addresses[user.Primary]
} }
} }
@ -143,14 +147,19 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { //nolint:funlen
} }
f.Println("Adding account ...") f.Println("Adding account ...")
user, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword)) userID, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
if err != nil { if err != nil {
log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful") log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful")
f.Println("Adding account was unsuccessful:", err) f.Println("Adding account was unsuccessful:", err)
return return
} }
f.Printf("Account %s was added successfully.\n", bold(user.Username())) user, err := f.bridge.GetUserInfo(userID)
if err != nil {
panic(err)
}
f.Printf("Account %s was added successfully.\n", bold(user.Username))
} }
func (f *frontendCLI) logoutAccount(c *ishell.Context) { func (f *frontendCLI) logoutAccount(c *ishell.Context) {
@ -158,11 +167,11 @@ func (f *frontendCLI) logoutAccount(c *ishell.Context) {
defer f.ShowPrompt(true) defer f.ShowPrompt(true)
user := f.askUserByIndexOrName(c) user := f.askUserByIndexOrName(c)
if user == nil { if user.ID == "" {
return return
} }
if f.yesNoQuestion("Are you sure you want to logout account " + bold(user.Username())) { if f.yesNoQuestion("Are you sure you want to logout account " + bold(user.Username)) {
if err := user.Logout(); err != nil { if err := f.bridge.LogoutUser(user.ID); err != nil {
f.printAndLogError("Logging out failed: ", err) f.printAndLogError("Logging out failed: ", err)
} }
} }
@ -173,12 +182,12 @@ func (f *frontendCLI) deleteAccount(c *ishell.Context) {
defer f.ShowPrompt(true) defer f.ShowPrompt(true)
user := f.askUserByIndexOrName(c) user := f.askUserByIndexOrName(c)
if user == nil { if user.ID == "" {
return return
} }
if f.yesNoQuestion("Are you sure you want to " + bold("remove account "+user.Username())) { if f.yesNoQuestion("Are you sure you want to " + bold("remove account "+user.Username)) {
clearCache := f.yesNoQuestion("Do you want to remove cache for this account") clearCache := f.yesNoQuestion("Do you want to remove cache for this account")
if err := f.bridge.DeleteUser(user.ID(), clearCache); err != nil { if err := f.bridge.DeleteUser(user.ID, clearCache); err != nil {
f.printAndLogError("Cannot delete account: ", err) f.printAndLogError("Cannot delete account: ", err)
return return
} }
@ -193,9 +202,13 @@ func (f *frontendCLI) deleteAccounts(c *ishell.Context) {
return return
} }
for _, user := range f.bridge.GetUsers() { for _, userID := range f.bridge.GetUserIDs() {
if err := f.bridge.DeleteUser(user.ID(), false); err != nil { user, err := f.bridge.GetUserInfo(userID)
f.printAndLogError("Cannot delete account ", user.Username(), ": ", err) if err != nil {
panic(err)
}
if err := f.bridge.DeleteUser(user.ID, false); err != nil {
f.printAndLogError("Cannot delete account ", user.Username, ": ", err)
} }
} }
@ -222,19 +235,25 @@ func (f *frontendCLI) deleteEverything(c *ishell.Context) {
func (f *frontendCLI) changeMode(c *ishell.Context) { func (f *frontendCLI) changeMode(c *ishell.Context) {
user := f.askUserByIndexOrName(c) user := f.askUserByIndexOrName(c)
if user == nil { if user.ID == "" {
return return
} }
newMode := "combined mode" var targetMode users.AddressMode
if user.IsCombinedAddressMode() {
newMode = "split mode" if user.Mode == users.CombinedMode {
targetMode = users.SplitMode
} else {
targetMode = users.CombinedMode
} }
if !f.yesNoQuestion("Are you sure you want to change the mode for account " + bold(user.Username()) + " to " + bold(newMode)) {
if !f.yesNoQuestion("Are you sure you want to change the mode for account " + bold(user.Username) + " to " + bold(targetMode)) {
return return
} }
if err := user.SwitchAddressMode(); err != nil {
if err := f.bridge.SetAddressMode(user.ID, targetMode); err != nil {
f.printAndLogError("Cannot switch address mode:", err) f.printAndLogError("Cannot switch address mode:", err)
} }
f.Printf("Address mode for account %s changed to %s\n", user.Username(), newMode)
f.Printf("Address mode for account %s changed to %s\n", user.Username, targetMode)
} }

View File

@ -307,11 +307,11 @@ func (f *frontendCLI) watchEvents() {
case address := <-addressChangedLogoutCh: case address := <-addressChangedLogoutCh:
f.notifyLogout(address) f.notifyLogout(address)
case userID := <-logoutCh: case userID := <-logoutCh:
user, err := f.bridge.GetUser(userID) user, err := f.bridge.GetUserInfo(userID)
if err != nil { if err != nil {
return return
} }
f.notifyLogout(user.Username()) f.notifyLogout(user.Username)
case <-certIssue: case <-certIssue:
f.notifyCertIssue() f.notifyCertIssue()
} }

View File

@ -46,7 +46,6 @@ func New(
bridge *bridge.Bridge, bridge *bridge.Bridge,
restarter types.Restarter, restarter types.Restarter,
) Frontend { ) Frontend {
bridgeWrap := types.NewBridgeWrap(bridge)
switch frontendType { switch frontendType {
case "grpc": case "grpc":
return grpc.NewService( return grpc.NewService(
@ -54,7 +53,7 @@ func New(
panicHandler, panicHandler,
eventListener, eventListener,
updater, updater,
bridgeWrap, bridge,
restarter, restarter,
) )
@ -63,7 +62,7 @@ func New(
panicHandler, panicHandler,
eventListener, eventListener,
updater, updater,
bridgeWrap, bridge,
restarter, restarter,
) )

View File

@ -8,6 +8,7 @@ package grpc
import ( import (
context "context" context "context"
grpc "google.golang.org/grpc" grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes" codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status" status "google.golang.org/grpc/status"

View File

@ -229,11 +229,11 @@ func (s *Service) watchEvents() { // nolint:funlen
case address := <-addressChangedLogoutCh: case address := <-addressChangedLogoutCh:
_ = s.SendEvent(NewMailAddressChangeLogoutEvent(address)) _ = s.SendEvent(NewMailAddressChangeLogoutEvent(address))
case userID := <-logoutCh: case userID := <-logoutCh:
user, err := s.bridge.GetUser(userID) user, err := s.bridge.GetUserInfo(userID)
if err != nil { if err != nil {
return return
} }
_ = s.SendEvent(NewUserDisconnectedEvent(user.Username())) _ = s.SendEvent(NewUserDisconnectedEvent(user.Username))
case <-updateApplicationCh: case <-updateApplicationCh:
s.updateForce() s.updateForce()
case userID := <-userChangedCh: case userID := <-userChangedCh:
@ -275,7 +275,7 @@ func (s *Service) finishLogin() {
s.eventListener.Add(events.UserChangeDone, done) s.eventListener.Add(events.UserChangeDone, done)
defer s.eventListener.Remove(events.UserChangeDone, done) defer s.eventListener.Remove(events.UserChangeDone, done)
user, err := s.bridge.FinishLogin(s.authClient, s.auth, s.password) userID, err := s.bridge.FinishLogin(s.authClient, s.auth, s.password)
if err != nil && err != users.ErrUserAlreadyConnected { if err != nil && err != users.ErrUserAlreadyConnected {
s.log.WithError(err).Errorf("Finish login failed") s.log.WithError(err).Errorf("Finish login failed")
@ -286,14 +286,14 @@ func (s *Service) finishLogin() {
// The user changed should be triggered by FinishLogin, but it is not // The user changed should be triggered by FinishLogin, but it is not
// guaranteed when this is going to happen. Therefor we should wait // guaranteed when this is going to happen. Therefor we should wait
// until we receive the signal from userChanged function. // until we receive the signal from userChanged function.
s.waitForUserChangeDone(done, user.ID()) s.waitForUserChangeDone(done, userID)
s.log.WithField("userID", user.ID()).Debug("Login finished") s.log.WithField("userID", userID).Debug("Login finished")
_ = s.SendEvent(NewLoginFinishedEvent(user.ID())) _ = s.SendEvent(NewLoginFinishedEvent(userID))
if err == users.ErrUserAlreadyConnected { if err == users.ErrUserAlreadyConnected {
s.log.WithError(err).Error("User already logged in") s.log.WithError(err).Error("User already logged in")
_ = s.SendEvent(NewLoginAlreadyLoggedInEvent(user.ID())) _ = s.SendEvent(NewLoginAlreadyLoggedInEvent(userID))
} }
} }

View File

@ -21,6 +21,8 @@ import (
"context" "context"
"time" "time"
"github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/emptypb"
@ -30,11 +32,15 @@ import (
func (s *Service) GetUserList(context.Context, *emptypb.Empty) (*UserListResponse, error) { func (s *Service) GetUserList(context.Context, *emptypb.Empty) (*UserListResponse, error) {
s.log.Info("GetUserList") s.log.Info("GetUserList")
users := s.bridge.GetUsers() var userList []*User
userList := make([]*User, len(users)) for idx, userID := range s.bridge.GetUserIDs() {
for i, user := range users { user, err := s.bridge.GetUserInfo(userID)
userList[i] = grpcUserFromBridge(user) if err != nil {
return nil, err
}
userList[idx] = grpcUserFromInfo(user)
} }
// If there are no active accounts. // If there are no active accounts.
@ -48,18 +54,18 @@ func (s *Service) GetUserList(context.Context, *emptypb.Empty) (*UserListRespons
func (s *Service) GetUser(_ context.Context, userID *wrapperspb.StringValue) (*User, error) { func (s *Service) GetUser(_ context.Context, userID *wrapperspb.StringValue) (*User, error) {
s.log.WithField("userID", userID).Info("GetUser") s.log.WithField("userID", userID).Info("GetUser")
user, err := s.bridge.GetUser(userID.Value) user, err := s.bridge.GetUserInfo(userID.Value)
if err != nil { if err != nil {
return nil, status.Errorf(codes.NotFound, "user not found %v", userID.Value) return nil, status.Errorf(codes.NotFound, "user not found %v", userID.Value)
} }
return grpcUserFromBridge(user), nil return grpcUserFromInfo(user), nil
} }
func (s *Service) SetUserSplitMode(_ context.Context, splitMode *UserSplitModeRequest) (*emptypb.Empty, error) { func (s *Service) SetUserSplitMode(_ context.Context, splitMode *UserSplitModeRequest) (*emptypb.Empty, error) {
s.log.WithField("UserID", splitMode.UserID).WithField("Active", splitMode.Active).Info("SetUserSplitMode") s.log.WithField("UserID", splitMode.UserID).WithField("Active", splitMode.Active).Info("SetUserSplitMode")
user, err := s.bridge.GetUser(splitMode.UserID) user, err := s.bridge.GetUserInfo(splitMode.UserID)
if err != nil { if err != nil {
return nil, status.Errorf(codes.NotFound, "user not found %v", splitMode.UserID) return nil, status.Errorf(codes.NotFound, "user not found %v", splitMode.UserID)
} }
@ -67,8 +73,17 @@ func (s *Service) SetUserSplitMode(_ context.Context, splitMode *UserSplitModeRe
go func() { go func() {
defer s.panicHandler.HandlePanic() defer s.panicHandler.HandlePanic()
defer func() { _ = s.SendEvent(NewUserToggleSplitModeFinishedEvent(splitMode.UserID)) }() defer func() { _ = s.SendEvent(NewUserToggleSplitModeFinishedEvent(splitMode.UserID)) }()
if splitMode.Active == user.IsCombinedAddressMode() {
_ = user.SwitchAddressMode() // check for errors var targetMode users.AddressMode
if splitMode.Active && user.Mode == users.CombinedMode {
targetMode = users.SplitMode
} else if !splitMode.Active && user.Mode == users.SplitMode {
targetMode = users.CombinedMode
}
if err := s.bridge.SetAddressMode(user.ID, targetMode); err != nil {
logrus.WithError(err).Error("Failed to set address mode")
} }
}() }()
@ -78,14 +93,16 @@ func (s *Service) SetUserSplitMode(_ context.Context, splitMode *UserSplitModeRe
func (s *Service) LogoutUser(_ context.Context, userID *wrapperspb.StringValue) (*emptypb.Empty, error) { func (s *Service) LogoutUser(_ context.Context, userID *wrapperspb.StringValue) (*emptypb.Empty, error) {
s.log.WithField("UserID", userID.Value).Info("LogoutUser") s.log.WithField("UserID", userID.Value).Info("LogoutUser")
user, err := s.bridge.GetUser(userID.Value) if _, err := s.bridge.GetUserInfo(userID.Value); err != nil {
if err != nil {
return nil, status.Errorf(codes.NotFound, "user not found %v", userID.Value) return nil, status.Errorf(codes.NotFound, "user not found %v", userID.Value)
} }
go func() { go func() {
defer s.panicHandler.HandlePanic() defer s.panicHandler.HandlePanic()
_ = user.Logout()
if err := s.bridge.LogoutUser(userID.Value); err != nil {
logrus.WithError(err).Error("Failed to log user out")
}
}() }()
return &emptypb.Empty{}, nil return &emptypb.Empty{}, nil

View File

@ -21,7 +21,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types" "github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -57,19 +57,19 @@ func getInitials(fullName string) string {
return strings.ToUpper(initials) return strings.ToUpper(initials)
} }
// grpcUserFromBridge converts a bridge user to a gRPC user. // grpcUserFromInfo converts a bridge user to a gRPC user.
func grpcUserFromBridge(user types.User) *User { func grpcUserFromInfo(user users.UserInfo) *User {
return &User{ return &User{
Id: user.ID(), Id: user.ID,
Username: user.Username(), Username: user.Username,
AvatarText: getInitials(user.Username()), AvatarText: getInitials(user.Username),
LoggedIn: user.IsConnected(), LoggedIn: user.Connected,
SplitMode: !user.IsCombinedAddressMode(), SplitMode: user.Mode == users.SplitMode,
SetupGuideSeen: true, // users listed have already seen the setup guide. SetupGuideSeen: true, // users listed have already seen the setup guide.
UsedBytes: user.UsedBytes(), UsedBytes: user.UsedBytes,
TotalBytes: user.TotalBytes(), TotalBytes: user.TotalBytes,
Password: user.GetBridgePassword(), Password: user.Password,
Addresses: user.GetAddresses(), Addresses: user.Addresses,
} }
} }

View File

@ -21,9 +21,9 @@ package types
import ( import (
"crypto/tls" "crypto/tls"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings" "github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/updater" "github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi" "github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
) )
@ -45,39 +45,22 @@ type Updater interface {
CanInstall(updater.VersionInfo) bool CanInstall(updater.VersionInfo) bool
} }
// UserManager is an interface of users needed by frontend. // Bridger is an interface of bridge needed by frontend.
type UserManager interface { type Bridger interface {
Login(username string, password []byte) (pmapi.Client, *pmapi.Auth, error) Login(username string, password []byte) (pmapi.Client, *pmapi.Auth, error)
FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword []byte) (User, error) FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword []byte) (string, error)
GetUsers() []User
GetUser(query string) (User, error) GetUserIDs() []string
GetUserInfo(string) (users.UserInfo, error)
LogoutUser(userID string) error
DeleteUser(userID string, clearCache bool) error DeleteUser(userID string, clearCache bool) error
SetAddressMode(userID string, split users.AddressMode) error
ClearData() error ClearData() error
ClearUsers() error ClearUsers() error
FactoryReset() FactoryReset()
}
// User is an interface of user needed by frontend.
type User interface {
ID() string
UsedBytes() int64
TotalBytes() int64
Username() string
IsConnected() bool
IsCombinedAddressMode() bool
GetPrimaryAddress() string
GetAddresses() []string
GetBridgePassword() string
SwitchAddressMode() error
Logout() error
}
// Bridger is an interface of bridge needed by frontend.
type Bridger interface {
UserManager
GetTLSConfig() (*tls.Config, error) GetTLSConfig() (*tls.Config, error)
ProvideLogsPath() (string, error) ProvideLogsPath() (string, error)
GetLicenseFilePath() string GetLicenseFilePath() string
GetDependencyLicensesLink() string GetDependencyLicensesLink() string
@ -115,29 +98,3 @@ type Bridger interface {
IsAllMailVisible() bool IsAllMailVisible() bool
SetIsAllMailVisible(bool) SetIsAllMailVisible(bool)
} }
type bridgeWrap struct {
*bridge.Bridge
}
// NewBridgeWrap wraps bridge struct into local bridgeWrap to implement local interface.
// The problem is that Bridge returns the bridge package's User type.
// Every method which returns User therefore has to be overridden to fulfill the interface.
func NewBridgeWrap(bridge *bridge.Bridge) *bridgeWrap { //nolint:revive
return &bridgeWrap{Bridge: bridge}
}
func (b *bridgeWrap) FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword []byte) (User, error) {
return b.Bridge.FinishLogin(client, auth, mailboxPassword)
}
func (b *bridgeWrap) GetUsers() (users []User) {
for _, user := range b.Bridge.GetUsers() {
users = append(users, user)
}
return
}
func (b *bridgeWrap) GetUser(query string) (User, error) {
return b.Bridge.GetUser(query)
}

View File

@ -46,3 +46,38 @@ type StoreMaker interface {
New(user store.BridgeUser) (*store.Store, error) New(user store.BridgeUser) (*store.Store, error)
Remove(userID string) error Remove(userID string) error
} }
type UserInfo struct {
ID string
Username string
Password string
Addresses []string
Primary int
UsedBytes int64
TotalBytes int64
Connected bool
Mode AddressMode
}
type AddressMode int
const (
SplitMode AddressMode = iota
CombinedMode
)
func (mode AddressMode) String() string {
switch mode {
case SplitMode:
return "split mode"
case CombinedMode:
return "combined mode"
default:
return "unknown mode"
}
}

View File

@ -30,9 +30,11 @@ import (
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain" "github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
"github.com/ProtonMail/proton-bridge/v2/pkg/listener" "github.com/ProtonMail/proton-bridge/v2/pkg/listener"
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi" "github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
"github.com/bradenaw/juniper/xslices"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/pkg/errors" "github.com/pkg/errors"
logrus "github.com/sirupsen/logrus" logrus "github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
) )
var ( var (
@ -215,10 +217,10 @@ func (u *Users) Login(username string, password []byte) (authClient pmapi.Client
} }
// FinishLogin finishes the login procedure and adds the user into the credentials store. // FinishLogin finishes the login procedure and adds the user into the credentials store.
func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password []byte) (user *User, err error) { //nolint:funlen func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password []byte) (userID string, err error) { //nolint:funlen
apiUser, passphrase, err := getAPIUser(context.Background(), client, password) apiUser, passphrase, err := getAPIUser(context.Background(), client, password)
if err != nil { if err != nil {
return nil, err return "", err
} }
if user, ok := u.hasUser(apiUser.ID); ok { if user, ok := u.hasUser(apiUser.ID); ok {
@ -227,39 +229,39 @@ func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password []by
logrus.WithError(err).Warn("Failed to delete new auth session") logrus.WithError(err).Warn("Failed to delete new auth session")
} }
return user, ErrUserAlreadyConnected return user.ID(), ErrUserAlreadyConnected
} }
// Update the user's credentials with the latest auth used to connect this user. // Update the user's credentials with the latest auth used to connect this user.
if _, err := u.credStorer.UpdateToken(auth.UserID, auth.UID, auth.RefreshToken); err != nil { if _, err := u.credStorer.UpdateToken(auth.UserID, auth.UID, auth.RefreshToken); err != nil {
notifyKeychainRepair(u.events, err) notifyKeychainRepair(u.events, err)
return nil, errors.Wrap(err, "failed to load user credentials") return "", errors.Wrap(err, "failed to load user credentials")
} }
// Update the password in case the user changed it. // Update the password in case the user changed it.
creds, err := u.credStorer.UpdatePassword(apiUser.ID, passphrase) creds, err := u.credStorer.UpdatePassword(apiUser.ID, passphrase)
if err != nil { if err != nil {
notifyKeychainRepair(u.events, err) notifyKeychainRepair(u.events, err)
return nil, errors.Wrap(err, "failed to update password of user in credentials store") return "", errors.Wrap(err, "failed to update password of user in credentials store")
} }
// will go and unlock cache if not already done // will go and unlock cache if not already done
if err := user.connect(client, creds); err != nil { if err := user.connect(client, creds); err != nil {
return nil, errors.Wrap(err, "failed to reconnect existing user") return "", errors.Wrap(err, "failed to reconnect existing user")
} }
u.events.Emit(events.UserRefreshEvent, apiUser.ID) u.events.Emit(events.UserRefreshEvent, apiUser.ID)
return user, nil return user.ID(), nil
} }
if err := u.addNewUser(client, apiUser, auth, passphrase); err != nil { if err := u.addNewUser(client, apiUser, auth, passphrase); err != nil {
return nil, errors.Wrap(err, "failed to add new user") return "", errors.Wrap(err, "failed to add new user")
} }
u.events.Emit(events.UserRefreshEvent, apiUser.ID) u.events.Emit(events.UserRefreshEvent, apiUser.ID)
return u.GetUser(apiUser.ID) return apiUser.ID, nil
} }
// addNewUser adds a new user. // addNewUser adds a new user.
@ -322,6 +324,16 @@ func (u *Users) GetUsers() []*User {
return u.users return u.users
} }
// GetUserIDs returns IDs of all added users into keychain (even logged out users).
func (u *Users) GetUserIDs() []string {
u.lock.RLock()
defer u.lock.RUnlock()
return xslices.Map(u.users, func(user *User) string {
return user.ID()
})
}
// GetUser returns a user by `query` which is compared to users' ID, username or any attached e-mail address. // GetUser returns a user by `query` which is compared to users' ID, username or any attached e-mail address.
func (u *Users) GetUser(query string) (*User, error) { func (u *Users) GetUser(query string) (*User, error) {
u.crashBandicoot(query) u.crashBandicoot(query)
@ -343,6 +355,44 @@ func (u *Users) GetUser(query string) (*User, error) {
return nil, errors.New("user " + query + " not found") return nil, errors.New("user " + query + " not found")
} }
// GetUserInfo returns user about the user with the given ID.
func (u *Users) GetUserInfo(userID string) (UserInfo, error) {
u.lock.RLock()
defer u.lock.RUnlock()
idx := slices.IndexFunc(u.users, func(user *User) bool {
return user.userID == userID
})
if idx < 0 {
return UserInfo{}, errors.New("no such user")
}
user := u.users[idx]
var mode AddressMode
if user.IsCombinedAddressMode() {
mode = CombinedMode
} else {
mode = SplitMode
}
return UserInfo{
ID: userID,
Username: user.Username(),
Password: user.GetBridgePassword(),
Addresses: user.GetAddresses(),
Primary: slices.Index(user.GetAddresses(), user.GetPrimaryAddress()),
UsedBytes: user.UsedBytes(),
TotalBytes: user.TotalBytes(),
Connected: user.IsConnected(),
Mode: mode,
}, nil
}
// ClearData closes all connections (to release db files and so on) and clears all data. // ClearData closes all connections (to release db files and so on) and clears all data.
func (u *Users) ClearData() error { func (u *Users) ClearData() error {
var result error var result error
@ -364,6 +414,42 @@ func (u *Users) ClearData() error {
return result return result
} }
func (u *Users) LogoutUser(userID string) error {
u.lock.RLock()
defer u.lock.RUnlock()
idx := slices.IndexFunc(u.users, func(user *User) bool {
return user.userID == userID
})
if idx < 0 {
return errors.New("no such user")
}
return u.users[idx].Logout()
}
func (u *Users) SetAddressMode(userID string, mode AddressMode) error {
u.lock.RLock()
defer u.lock.RUnlock()
idx := slices.IndexFunc(u.users, func(user *User) bool {
return user.userID == userID
})
if idx < 0 {
return errors.New("no such user")
}
if mode == CombinedMode && u.users[idx].IsCombinedAddressMode() {
return nil
}
if mode == SplitMode && !u.users[idx].IsCombinedAddressMode() {
return nil
}
return u.users[idx].SwitchAddressMode()
}
// DeleteUser deletes user completely; it logs user out from the API, stops any // DeleteUser deletes user completely; it logs user out from the API, stops any
// active connection, deletes from credentials store and removes from the Bridge struct. // active connection, deletes from credentials store and removes from the Bridge struct.
func (u *Users) DeleteUser(userID string, clearStore bool) error { func (u *Users) DeleteUser(userID string, clearStore bool) error {

View File

@ -117,16 +117,16 @@ func checkUsersFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassw
users := testNewUsers(t, m) users := testNewUsers(t, m)
defer cleanUpUsersData(users) defer cleanUpUsersData(users)
user, err := users.FinishLogin(m.pmapiClient, auth, mailboxPassword) userID, err := users.FinishLogin(m.pmapiClient, auth, mailboxPassword)
r.Equal(t, expectedErr, err) r.Equal(t, expectedErr, err)
if expectedUserID != "" { if expectedUserID != "" {
r.Equal(t, expectedUserID, user.ID()) r.Equal(t, expectedUserID, userID)
r.Equal(t, 1, len(users.users)) r.Equal(t, 1, len(users.users))
r.Equal(t, expectedUserID, users.users[0].ID()) r.Equal(t, expectedUserID, users.users[0].ID())
} else { } else {
r.Equal(t, (*User)(nil), user) r.Equal(t, "", userID)
r.Equal(t, 0, len(users.users)) r.Equal(t, 0, len(users.users))
} }
} }

View File

@ -52,12 +52,14 @@ func (ctx *TestContext) LoginUser(username string, password, mailboxPassword []b
} }
} }
user, err := ctx.users.FinishLogin(client, auth, mailboxPassword) userID, err := ctx.users.FinishLogin(client, auth, mailboxPassword)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to finish login") return errors.Wrap(err, "failed to finish login")
} }
ctx.addCleanupChecked(user.Logout, "Logging out user") ctx.addCleanupChecked(func() error {
return ctx.bridge.LogoutUser(userID)
}, "Logging out user")
return nil return nil
} }
@ -73,12 +75,14 @@ func (ctx *TestContext) FinishLogin(client pmapi.Client, mailboxPassword []byte)
return errors.New("cannot get current auth tokens from client") return errors.New("cannot get current auth tokens from client")
} }
user, err := ctx.users.FinishLogin(client, c.GetCurrentAuth(), mailboxPassword) userID, err := ctx.users.FinishLogin(client, c.GetCurrentAuth(), mailboxPassword)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to finish login") return errors.Wrap(err, "failed to finish login")
} }
ctx.addCleanupChecked(user.Logout, "Logging out user") ctx.addCleanupChecked(func() error {
return ctx.bridge.LogoutUser(userID)
}, "Logging out user")
return nil return nil
} }