mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 12:46:46 +00:00
Renamed bridge to general users and keep bridge only for bridge stuff
This commit is contained in:
@ -6,6 +6,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
## unreleased
|
## unreleased
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
* GODT-386 renamed bridge to general users and keep bridge only for bridge stuff
|
||||||
* GODT-308 better user error message when request is canceled
|
* GODT-308 better user error message when request is canceled
|
||||||
* GODT-312 validate recipient emails in send before asking for their public keys
|
* GODT-312 validate recipient emails in send before asking for their public keys
|
||||||
|
|
||||||
|
|||||||
4
Makefile
4
Makefile
@ -165,9 +165,11 @@ test: gofiles
|
|||||||
./internal/frontend/autoconfig/... \
|
./internal/frontend/autoconfig/... \
|
||||||
./internal/frontend/cli/... \
|
./internal/frontend/cli/... \
|
||||||
./internal/imap/... \
|
./internal/imap/... \
|
||||||
|
./internal/metrics/... \
|
||||||
./internal/preferences/... \
|
./internal/preferences/... \
|
||||||
./internal/smtp/... \
|
./internal/smtp/... \
|
||||||
./internal/store/... \
|
./internal/store/... \
|
||||||
|
./internal/users/... \
|
||||||
./pkg/...
|
./pkg/...
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
@ -179,7 +181,7 @@ coverage: test
|
|||||||
go tool cover -html=/tmp/coverage.out -o=coverage.html
|
go tool cover -html=/tmp/coverage.out -o=coverage.html
|
||||||
|
|
||||||
mocks:
|
mocks:
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/bridge Configer,PreferenceProvider,PanicHandler,ClientManager,CredentialsStorer > internal/bridge/mocks/mocks.go
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/users Configer,PreferenceProvider,PanicHandler,ClientManager,CredentialsStorer > internal/users/mocks/mocks.go
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,ClientManager,BridgeUser > internal/store/mocks/mocks.go
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,ClientManager,BridgeUser > internal/store/mocks/mocks.go
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/listener Listener > internal/store/mocks/utils_mocks.go
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/listener Listener > internal/store/mocks/utils_mocks.go
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/pmapi Client > pkg/pmapi/mocks/mocks.go
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/pmapi Client > pkg/pmapi/mocks/mocks.go
|
||||||
|
|||||||
@ -47,12 +47,12 @@ import (
|
|||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/api"
|
"github.com/ProtonMail/proton-bridge/internal/api"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/imap"
|
"github.com/ProtonMail/proton-bridge/internal/imap"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/smtp"
|
"github.com/ProtonMail/proton-bridge/internal/smtp"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/args"
|
"github.com/ProtonMail/proton-bridge/pkg/args"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
"github.com/ProtonMail/proton-bridge/pkg/config"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/constants"
|
"github.com/ProtonMail/proton-bridge/pkg/constants"
|
||||||
@ -261,7 +261,7 @@ func run(context *cli.Context) (contextError error) { // nolint[funlen]
|
|||||||
eventListener := listener.New()
|
eventListener := listener.New()
|
||||||
events.SetupEvents(eventListener)
|
events.SetupEvents(eventListener)
|
||||||
|
|
||||||
credentialsStore, credentialsError := credentials.NewStore()
|
credentialsStore, credentialsError := credentials.NewStore("bridge")
|
||||||
if credentialsError != nil {
|
if credentialsError != nil {
|
||||||
log.Error("Could not get credentials store: ", credentialsError)
|
log.Error("Could not get credentials store: ", credentialsError)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,57 +15,24 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package bridge provides core business logic providing API over credentials store and PM API.
|
// Package bridge provides core functionality of Bridge app.
|
||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/store"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
|
||||||
imapBackend "github.com/emersion/go-imap/backend"
|
|
||||||
"github.com/hashicorp/go-multierror"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
logrus "github.com/sirupsen/logrus"
|
logrus "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
log = logrus.WithField("pkg", "bridge") //nolint[gochecknoglobals]
|
log = logrus.WithField("pkg", "bridge") //nolint[gochecknoglobals]
|
||||||
isApplicationOutdated = false //nolint[gochecknoglobals]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bridge is a struct handling users.
|
|
||||||
type Bridge struct {
|
type Bridge struct {
|
||||||
config Configer
|
*users.Users
|
||||||
pref PreferenceProvider
|
|
||||||
panicHandler PanicHandler
|
|
||||||
events listener.Listener
|
|
||||||
clientManager ClientManager
|
|
||||||
credStorer CredentialsStorer
|
|
||||||
storeCache *store.Cache
|
|
||||||
|
|
||||||
// users is a list of accounts that have been added to bridge.
|
clientManager users.ClientManager
|
||||||
// They are stored sorted in the credentials store in the order
|
|
||||||
// that they were added to bridge chronologically.
|
|
||||||
// People are used to that and so we preserve that ordering here.
|
|
||||||
users []*User
|
|
||||||
|
|
||||||
// idleUpdates is a channel which the imap backend listens to and which it uses
|
|
||||||
// to send idle updates to the mail client (eg thunderbird).
|
|
||||||
// The user stores should send idle updates on this channel.
|
|
||||||
idleUpdates chan imapBackend.Update
|
|
||||||
|
|
||||||
lock sync.RWMutex
|
|
||||||
|
|
||||||
// stopAll can be closed to stop all goroutines from looping (watchBridgeOutdated, watchAPIAuths, heartbeat etc).
|
|
||||||
stopAll chan struct{}
|
|
||||||
|
|
||||||
userAgentClientName string
|
userAgentClientName string
|
||||||
userAgentClientVersion string
|
userAgentClientVersion string
|
||||||
@ -73,444 +40,19 @@ type Bridge struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
config Configer,
|
config users.Configer,
|
||||||
pref PreferenceProvider,
|
pref users.PreferenceProvider,
|
||||||
panicHandler PanicHandler,
|
panicHandler users.PanicHandler,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
clientManager ClientManager,
|
clientManager users.ClientManager,
|
||||||
credStorer CredentialsStorer,
|
credStorer users.CredentialsStorer,
|
||||||
) *Bridge {
|
) *Bridge {
|
||||||
log.Trace("Creating new bridge")
|
u := users.New(config, pref, panicHandler, eventListener, clientManager, credStorer)
|
||||||
|
return &Bridge{
|
||||||
|
Users: u,
|
||||||
|
|
||||||
b := &Bridge{
|
|
||||||
config: config,
|
|
||||||
pref: pref,
|
|
||||||
panicHandler: panicHandler,
|
|
||||||
events: eventListener,
|
|
||||||
clientManager: clientManager,
|
clientManager: clientManager,
|
||||||
credStorer: credStorer,
|
|
||||||
storeCache: store.NewCache(config.GetIMAPCachePath()),
|
|
||||||
idleUpdates: make(chan imapBackend.Update),
|
|
||||||
lock: sync.RWMutex{},
|
|
||||||
stopAll: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow DoH before starting bridge if the user has previously set this setting.
|
|
||||||
// This allows us to start even if protonmail is blocked.
|
|
||||||
if pref.GetBool(preferences.AllowProxyKey) {
|
|
||||||
b.AllowProxy()
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer panicHandler.HandlePanic()
|
|
||||||
b.watchBridgeOutdated()
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer panicHandler.HandlePanic()
|
|
||||||
b.watchAPIAuths()
|
|
||||||
}()
|
|
||||||
|
|
||||||
go b.heartbeat()
|
|
||||||
|
|
||||||
if b.credStorer == nil {
|
|
||||||
log.Error("Bridge has no credentials store")
|
|
||||||
} else if err := b.loadUsersFromCredentialsStore(); err != nil {
|
|
||||||
log.WithError(err).Error("Could not load all users from credentials store")
|
|
||||||
}
|
|
||||||
|
|
||||||
if pref.GetBool(preferences.FirstStartKey) {
|
|
||||||
b.SendMetric(metrics.New(metrics.Setup, metrics.FirstStart, metrics.Label(config.GetVersion())))
|
|
||||||
}
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// heartbeat sends a heartbeat signal once a day.
|
|
||||||
func (b *Bridge) heartbeat() {
|
|
||||||
ticker := time.NewTicker(1 * time.Minute)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
next, err := strconv.ParseInt(b.pref.Get(preferences.NextHeartbeatKey), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
nextTime := time.Unix(next, 0)
|
|
||||||
if time.Now().After(nextTime) {
|
|
||||||
b.SendMetric(metrics.New(metrics.Heartbeat, metrics.Daily, metrics.NoLabel))
|
|
||||||
nextTime = nextTime.Add(24 * time.Hour)
|
|
||||||
b.pref.Set(preferences.NextHeartbeatKey, strconv.FormatInt(nextTime.Unix(), 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-b.stopAll:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bridge) loadUsersFromCredentialsStore() (err error) {
|
|
||||||
b.lock.Lock()
|
|
||||||
defer b.lock.Unlock()
|
|
||||||
|
|
||||||
userIDs, err := b.credStorer.List()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, userID := range userIDs {
|
|
||||||
l := log.WithField("user", userID)
|
|
||||||
|
|
||||||
user, newUserErr := newUser(b.panicHandler, userID, b.events, b.credStorer, b.clientManager, b.storeCache, b.config.GetDBDir())
|
|
||||||
if newUserErr != nil {
|
|
||||||
l.WithField("user", userID).WithError(newUserErr).Warn("Could not load user, skipping")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
b.users = append(b.users, user)
|
|
||||||
|
|
||||||
if initUserErr := user.init(b.idleUpdates); initUserErr != nil {
|
|
||||||
l.WithField("user", userID).WithError(initUserErr).Warn("Could not initialise user")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bridge) watchBridgeOutdated() {
|
|
||||||
ch := make(chan string)
|
|
||||||
|
|
||||||
b.events.Add(events.UpgradeApplicationEvent, ch)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ch:
|
|
||||||
isApplicationOutdated = true
|
|
||||||
b.closeAllConnections()
|
|
||||||
|
|
||||||
case <-b.stopAll:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// watchAPIAuths receives auths from the client manager and sends them to the appropriate user.
|
|
||||||
func (b *Bridge) watchAPIAuths() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case auth := <-b.clientManager.GetAuthUpdateChannel():
|
|
||||||
log.Debug("Bridge received auth from ClientManager")
|
|
||||||
|
|
||||||
user, ok := b.hasUser(auth.UserID)
|
|
||||||
if !ok {
|
|
||||||
log.WithField("userID", auth.UserID).Info("User not available for auth update")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if auth.Auth != nil {
|
|
||||||
user.updateAuthToken(auth.Auth)
|
|
||||||
} else if err := user.logout(); err != nil {
|
|
||||||
log.WithError(err).
|
|
||||||
WithField("userID", auth.UserID).
|
|
||||||
Error("User logout failed while watching API auths")
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-b.stopAll:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bridge) closeAllConnections() {
|
|
||||||
for _, user := range b.users {
|
|
||||||
user.closeAllConnections()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login authenticates a user.
|
|
||||||
// The login flow:
|
|
||||||
// * Authenticate user:
|
|
||||||
// client, auth, err := bridge.Authenticate(username, password)
|
|
||||||
//
|
|
||||||
// * In case user `auth.HasTwoFactor()`, ask for it and fully authenticate the user.
|
|
||||||
// auth2FA, err := client.Auth2FA(twoFactorCode)
|
|
||||||
//
|
|
||||||
// * In case user `auth.HasMailboxPassword()`, ask for it, otherwise use `password`
|
|
||||||
// and then finish the login procedure.
|
|
||||||
// user, err := bridge.FinishLogin(client, auth, mailboxPassword)
|
|
||||||
func (b *Bridge) Login(username, password string) (authClient pmapi.Client, auth *pmapi.Auth, err error) {
|
|
||||||
b.crashBandicoot(username)
|
|
||||||
|
|
||||||
// We need to use anonymous client because we don't yet have userID and so can't save auth tokens yet.
|
|
||||||
authClient = b.clientManager.GetAnonymousClient()
|
|
||||||
|
|
||||||
authInfo, err := authClient.AuthInfo(username)
|
|
||||||
if err != nil {
|
|
||||||
log.WithField("username", username).WithError(err).Error("Could not get auth info for user")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if auth, err = authClient.Auth(username, password, authInfo); err != nil {
|
|
||||||
log.WithField("username", username).WithError(err).Error("Could not get auth for user")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FinishLogin finishes the login procedure and adds the user into the credentials store.
|
|
||||||
// See `Login` for more details of the login flow.
|
|
||||||
func (b *Bridge) FinishLogin(authClient pmapi.Client, auth *pmapi.Auth, mbPassword string) (user *User, err error) { //nolint[funlen]
|
|
||||||
defer func() {
|
|
||||||
if err == pmapi.ErrUpgradeApplication {
|
|
||||||
b.events.Emit(events.UpgradeApplicationEvent, "")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Debug("Login not finished; removing auth session")
|
|
||||||
if delAuthErr := authClient.DeleteAuth(); delAuthErr != nil {
|
|
||||||
log.WithError(delAuthErr).Error("Failed to clear login session after unlock")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The anonymous client will be removed from list and authentication will not be deleted.
|
|
||||||
authClient.Logout()
|
|
||||||
}()
|
|
||||||
|
|
||||||
apiUser, hashedPassword, err := getAPIUser(authClient, auth, mbPassword)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Failed to get API user")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
if user, ok = b.hasUser(apiUser.ID); ok {
|
|
||||||
if err = b.connectExistingUser(user, auth, hashedPassword); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to connect existing user")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err = b.addNewUser(apiUser, auth, hashedPassword); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to add new user")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.events.Emit(events.UserRefreshEvent, apiUser.ID)
|
|
||||||
|
|
||||||
return b.GetUser(apiUser.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// connectExistingUser connects an existing bridge user to the bridge.
|
|
||||||
func (b *Bridge) connectExistingUser(user *User, auth *pmapi.Auth, hashedPassword string) (err error) {
|
|
||||||
if user.IsConnected() {
|
|
||||||
return errors.New("user is already connected")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the user's password in the cred store in case they changed it.
|
|
||||||
if err = b.credStorer.UpdatePassword(user.ID(), hashedPassword); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to update password of user in credentials store")
|
|
||||||
}
|
|
||||||
|
|
||||||
client := b.clientManager.GetClient(user.ID())
|
|
||||||
|
|
||||||
if auth, err = client.AuthRefresh(auth.GenToken()); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to refresh auth token of new client")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = b.credStorer.UpdateToken(user.ID(), auth.GenToken()); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to update token of user in credentials store")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = user.init(b.idleUpdates); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to initialise user")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// addNewUser adds a new bridge user to the bridge.
|
|
||||||
func (b *Bridge) addNewUser(user *pmapi.User, auth *pmapi.Auth, hashedPassword string) (err error) {
|
|
||||||
b.lock.Lock()
|
|
||||||
defer b.lock.Unlock()
|
|
||||||
|
|
||||||
client := b.clientManager.GetClient(user.ID)
|
|
||||||
|
|
||||||
if auth, err = client.AuthRefresh(auth.GenToken()); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to refresh token in new client")
|
|
||||||
}
|
|
||||||
|
|
||||||
if user, err = client.CurrentUser(); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to update API user")
|
|
||||||
}
|
|
||||||
|
|
||||||
activeEmails := client.Addresses().ActiveEmails()
|
|
||||||
|
|
||||||
if _, err = b.credStorer.Add(user.ID, user.Name, auth.GenToken(), hashedPassword, activeEmails); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to add user to credentials store")
|
|
||||||
}
|
|
||||||
|
|
||||||
bridgeUser, err := newUser(b.panicHandler, user.ID, b.events, b.credStorer, b.clientManager, b.storeCache, b.config.GetDBDir())
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to create user")
|
|
||||||
}
|
|
||||||
|
|
||||||
// The user needs to be part of the users list in order for it to receive an auth during initialisation.
|
|
||||||
b.users = append(b.users, bridgeUser)
|
|
||||||
|
|
||||||
if err = bridgeUser.init(b.idleUpdates); err != nil {
|
|
||||||
b.users = b.users[:len(b.users)-1]
|
|
||||||
return errors.Wrap(err, "failed to initialise user")
|
|
||||||
}
|
|
||||||
|
|
||||||
b.SendMetric(metrics.New(metrics.Setup, metrics.NewUser, metrics.NoLabel))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAPIUser(client pmapi.Client, auth *pmapi.Auth, mbPassword string) (user *pmapi.User, hashedPassword string, err error) {
|
|
||||||
hashedPassword, err = pmapi.HashMailboxPassword(mbPassword, auth.KeySalt)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Could not hash mailbox password")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We unlock the user's PGP key here to detect if the user's mailbox password is wrong.
|
|
||||||
if _, err = client.Unlock(hashedPassword); err != nil {
|
|
||||||
log.WithError(err).Error("Wrong mailbox password")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if user, err = client.CurrentUser(); err != nil {
|
|
||||||
log.WithError(err).Error("Could not load API user")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsers returns all added users into keychain (even logged out users).
|
|
||||||
func (b *Bridge) GetUsers() []*User {
|
|
||||||
b.lock.RLock()
|
|
||||||
defer b.lock.RUnlock()
|
|
||||||
|
|
||||||
return b.users
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser returns a user by `query` which is compared to users' ID, username or any attached e-mail address.
|
|
||||||
func (b *Bridge) GetUser(query string) (*User, error) {
|
|
||||||
b.crashBandicoot(query)
|
|
||||||
|
|
||||||
b.lock.RLock()
|
|
||||||
defer b.lock.RUnlock()
|
|
||||||
|
|
||||||
for _, user := range b.users {
|
|
||||||
if strings.EqualFold(user.ID(), query) || strings.EqualFold(user.Username(), query) {
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
for _, address := range user.GetAddresses() {
|
|
||||||
if strings.EqualFold(address, query) {
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("user " + query + " not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearData closes all connections (to release db files and so on) and clears all data.
|
|
||||||
func (b *Bridge) ClearData() error {
|
|
||||||
var result *multierror.Error
|
|
||||||
for _, user := range b.users {
|
|
||||||
if err := user.Logout(); err != nil {
|
|
||||||
result = multierror.Append(result, err)
|
|
||||||
}
|
|
||||||
if err := user.closeStore(); err != nil {
|
|
||||||
result = multierror.Append(result, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := b.config.ClearData(); err != nil {
|
|
||||||
result = multierror.Append(result, err)
|
|
||||||
}
|
|
||||||
return result.ErrorOrNil()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (b *Bridge) DeleteUser(userID string, clearStore bool) error {
|
|
||||||
b.lock.Lock()
|
|
||||||
defer b.lock.Unlock()
|
|
||||||
|
|
||||||
log := log.WithField("user", userID)
|
|
||||||
|
|
||||||
for idx, user := range b.users {
|
|
||||||
if user.ID() == userID {
|
|
||||||
if err := user.Logout(); err != nil {
|
|
||||||
log.WithError(err).Error("Cannot logout user")
|
|
||||||
// We can try to continue to remove the user.
|
|
||||||
// Token will still be valid, but will expire eventually.
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := user.closeStore(); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to close user store")
|
|
||||||
}
|
|
||||||
if clearStore {
|
|
||||||
// Clear cache after closing connections (done in logout).
|
|
||||||
if err := user.clearStore(); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to clear user")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.credStorer.Delete(userID); err != nil {
|
|
||||||
log.WithError(err).Error("Cannot remove user")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.users = append(b.users[:idx], b.users[idx+1:]...)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New("user " + userID + " not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReportBug reports a new bug from the user.
|
|
||||||
func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string) error {
|
|
||||||
c := b.clientManager.GetAnonymousClient()
|
|
||||||
defer c.Logout()
|
|
||||||
|
|
||||||
title := "[Bridge] Bug"
|
|
||||||
if err := c.ReportBugWithEmailClient(
|
|
||||||
osType,
|
|
||||||
osVersion,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
accountName,
|
|
||||||
address,
|
|
||||||
emailClient,
|
|
||||||
); err != nil {
|
|
||||||
log.Error("Reporting bug failed: ", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Bug successfully reported")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendMetric sends a metric. We don't want to return any errors, only log them.
|
|
||||||
func (b *Bridge) SendMetric(m metrics.Metric) {
|
|
||||||
c := b.clientManager.GetAnonymousClient()
|
|
||||||
defer c.Logout()
|
|
||||||
|
|
||||||
cat, act, lab := m.Get()
|
|
||||||
if err := c.SendSimpleMetric(string(cat), string(act), string(lab)); err != nil {
|
|
||||||
log.Error("Sending metric failed: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"cat": cat,
|
|
||||||
"act": act,
|
|
||||||
"lab": lab,
|
|
||||||
}).Debug("Metric successfully sent")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentClient returns currently connected client (e.g. Thunderbird).
|
// GetCurrentClient returns currently connected client (e.g. Thunderbird).
|
||||||
@ -541,53 +83,26 @@ func (b *Bridge) updateUserAgent() {
|
|||||||
b.clientManager.SetUserAgent(b.userAgentClientName, b.userAgentClientVersion, b.userAgentOS)
|
b.clientManager.SetUserAgent(b.userAgentClientName, b.userAgentClientVersion, b.userAgentOS)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIMAPUpdatesChannel sets the channel on which idle events should be sent.
|
// ReportBug reports a new bug from the user.
|
||||||
func (b *Bridge) GetIMAPUpdatesChannel() chan imapBackend.Update {
|
func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string) error {
|
||||||
if b.idleUpdates == nil {
|
c := b.clientManager.GetAnonymousClient()
|
||||||
log.Warn("Bridge updates channel is nil")
|
defer c.Logout()
|
||||||
|
|
||||||
|
title := "[Bridge] Bug"
|
||||||
|
if err := c.ReportBugWithEmailClient(
|
||||||
|
osType,
|
||||||
|
osVersion,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
accountName,
|
||||||
|
address,
|
||||||
|
emailClient,
|
||||||
|
); err != nil {
|
||||||
|
log.Error("Reporting bug failed: ", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.idleUpdates
|
log.Info("Bug successfully reported")
|
||||||
}
|
|
||||||
|
|
||||||
// AllowProxy instructs bridge to use DoH to access an API proxy if necessary.
|
return nil
|
||||||
// It also needs to work before bridge is initialised (because we may need to use the proxy at startup).
|
|
||||||
func (b *Bridge) AllowProxy() {
|
|
||||||
b.clientManager.AllowProxy()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisallowProxy instructs bridge to not use DoH to access an API proxy if necessary.
|
|
||||||
// It also needs to work before bridge is initialised (because we may need to use the proxy at startup).
|
|
||||||
func (b *Bridge) DisallowProxy() {
|
|
||||||
b.clientManager.DisallowProxy()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckConnection returns whether there is an internet connection.
|
|
||||||
// This should use the connection manager when it is eventually implemented.
|
|
||||||
func (b *Bridge) CheckConnection() error {
|
|
||||||
return b.clientManager.CheckConnection()
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopWatchers stops all bridge goroutines.
|
|
||||||
func (b *Bridge) StopWatchers() {
|
|
||||||
close(b.stopAll)
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasUser returns whether the bridge currently has a user with ID `id`.
|
|
||||||
func (b *Bridge) hasUser(id string) (user *User, ok bool) {
|
|
||||||
for _, u := range b.users {
|
|
||||||
if u.ID() == id {
|
|
||||||
user, ok = u, true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Easter egg" for testing purposes.
|
|
||||||
func (b *Bridge) crashBandicoot(username string) {
|
|
||||||
if username == "crash@bandicoot" {
|
|
||||||
panic("Your wish is my command… I crash!")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ package imap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -67,10 +68,10 @@ func (b *bridgeWrap) GetUser(query string) (bridgeUser, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type bridgeUserWrap struct {
|
type bridgeUserWrap struct {
|
||||||
*bridge.User
|
*users.User
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBridgeUserWrap(bridgeUser *bridge.User) *bridgeUserWrap {
|
func newBridgeUserWrap(bridgeUser *users.User) *bridgeUserWrap {
|
||||||
return &bridgeUserWrap{User: bridgeUser}
|
return &bridgeUserWrap{User: bridgeUser}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ package smtp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,10 +55,10 @@ func (b *bridgeWrap) GetUser(query string) (bridgeUser, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type bridgeUserWrap struct {
|
type bridgeUserWrap struct {
|
||||||
*bridge.User
|
*users.User
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBridgeUserWrap(bridgeUser *bridge.User) *bridgeUserWrap {
|
func newBridgeUserWrap(bridgeUser *users.User) *bridgeUserWrap {
|
||||||
return &bridgeUserWrap{User: bridgeUser}
|
return &bridgeUserWrap{User: bridgeUser}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,8 +22,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
@ -32,14 +32,14 @@ import (
|
|||||||
|
|
||||||
type mocks struct {
|
type mocks struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
eventListener *bridge.MockListener
|
eventListener *users.MockListener
|
||||||
}
|
}
|
||||||
|
|
||||||
func initMocks(t *testing.T) mocks {
|
func initMocks(t *testing.T) mocks {
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
return mocks{
|
return mocks{
|
||||||
t: t,
|
t: t,
|
||||||
eventListener: bridge.NewMockListener(mockCtrl),
|
eventListener: users.NewMockListener(mockCtrl),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,7 @@ import (
|
|||||||
const sep = "\x00"
|
const sep = "\x00"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
log = logrus.WithField("pkg", "bridge") //nolint[gochecknoglobals]
|
log = logrus.WithField("pkg", "credentials") //nolint[gochecknoglobals]
|
||||||
|
|
||||||
ErrWrongFormat = errors.New("backend/creds: malformed password")
|
ErrWrongFormat = errors.New("backend/creds: malformed password")
|
||||||
)
|
)
|
||||||
@ -36,8 +36,8 @@ type Store struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewStore creates a new encrypted credentials store.
|
// NewStore creates a new encrypted credentials store.
|
||||||
func NewStore() (*Store, error) {
|
func NewStore(appName string) (*Store, error) {
|
||||||
secrets, err := keychain.NewAccess("bridge")
|
secrets, err := keychain.NewAccess(appName)
|
||||||
return &Store{
|
return &Store{
|
||||||
secrets: secrets,
|
secrets: secrets,
|
||||||
}, err
|
}, err
|
||||||
@ -1,8 +1,8 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: ./listener/listener.go
|
// Source: ./listener/listener.go
|
||||||
|
|
||||||
// Package bridge is a generated GoMock package.
|
// Package users is a generated GoMock package.
|
||||||
package bridge
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/ProtonMail/proton-bridge/internal/bridge (interfaces: Configer,PreferenceProvider,PanicHandler,ClientManager,CredentialsStorer)
|
// Source: github.com/ProtonMail/proton-bridge/internal/users (interfaces: Configer,PreferenceProvider,PanicHandler,ClientManager,CredentialsStorer)
|
||||||
|
|
||||||
// Package mocks is a generated GoMock package.
|
// Package mocks is a generated GoMock package.
|
||||||
package mocks
|
package mocks
|
||||||
@ -7,7 +7,7 @@ package mocks
|
|||||||
import (
|
import (
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
credentials "github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
|
credentials "github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
@ -15,10 +15,10 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package bridge
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
|
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,7 +15,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package bridge
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -24,9 +24,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/store"
|
"github.com/ProtonMail/proton-bridge/internal/store"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
imapBackend "github.com/emersion/go-imap/backend"
|
imapBackend "github.com/emersion/go-imap/backend"
|
||||||
@ -34,8 +34,8 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrLoggedOutUser is sent to IMAP and SMTP if user exists, password is OK but user is logged out from bridge.
|
// ErrLoggedOutUser is sent to IMAP and SMTP if user exists, password is OK but user is logged out from the app.
|
||||||
var ErrLoggedOutUser = errors.New("bridge account is logged out, use bridge to login again")
|
var ErrLoggedOutUser = errors.New("account is logged out, use the app to login again")
|
||||||
|
|
||||||
// User is a struct on top of API client and credentials store.
|
// User is a struct on top of API client and credentials store.
|
||||||
type User struct {
|
type User struct {
|
||||||
@ -61,7 +61,7 @@ type User struct {
|
|||||||
wasKeyringUnlocked bool
|
wasKeyringUnlocked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// newUser creates a new bridge user.
|
// newUser creates a new user.
|
||||||
func newUser(
|
func newUser(
|
||||||
panicHandler PanicHandler,
|
panicHandler PanicHandler,
|
||||||
userID string,
|
userID string,
|
||||||
@ -98,7 +98,7 @@ func (u *User) client() pmapi.Client {
|
|||||||
return u.clientManager.GetClient(u.userID)
|
return u.clientManager.GetClient(u.userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// init initialises a bridge user. This includes reloading its credentials from the credentials store
|
// init initialises a user. This includes reloading its credentials from the credentials store
|
||||||
// (such as when logging out and back in, you need to reload the credentials because the new credentials will
|
// (such as when logging out and back in, you need to reload the credentials because the new credentials will
|
||||||
// have the apitoken and password), authorising the user against the api, loading the user store (creating a new one
|
// have the apitoken and password), authorising the user against the api, loading the user store (creating a new one
|
||||||
// if necessary), and setting the imap idle updates channel (used to send imap idle updates to the imap backend if
|
// if necessary), and setting the imap idle updates channel (used to send imap idle updates to the imap backend if
|
||||||
@ -119,7 +119,7 @@ func (u *User) init(idleUpdates chan imapBackend.Update) (err error) {
|
|||||||
u.creds = creds
|
u.creds = creds
|
||||||
|
|
||||||
// Try to authorise the user if they aren't already authorised.
|
// Try to authorise the user if they aren't already authorised.
|
||||||
// Note: we still allow users to set up bridge if the internet is off.
|
// Note: we still allow users to set up accounts if the internet is off.
|
||||||
if authErr := u.authorizeIfNecessary(false); authErr != nil {
|
if authErr := u.authorizeIfNecessary(false); authErr != nil {
|
||||||
switch errors.Cause(authErr) {
|
switch errors.Cause(authErr) {
|
||||||
case pmapi.ErrAPINotReachable, pmapi.ErrUpgradeApplication, ErrLoggedOutUser:
|
case pmapi.ErrAPINotReachable, pmapi.ErrUpgradeApplication, ErrLoggedOutUser:
|
||||||
@ -245,7 +245,7 @@ func (u *User) authorizeAndUnlock() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) updateAuthToken(auth *pmapi.Auth) {
|
func (u *User) updateAuthToken(auth *pmapi.Auth) {
|
||||||
u.log.Debug("User received auth from bridge")
|
u.log.Debug("User received auth")
|
||||||
|
|
||||||
if err := u.credStorer.UpdateToken(u.userID, auth.GenToken()); err != nil {
|
if err := u.credStorer.UpdateToken(u.userID, auth.GenToken()); err != nil {
|
||||||
u.log.WithError(err).Error("Failed to update refresh token in credentials store")
|
u.log.WithError(err).Error("Failed to update refresh token in credentials store")
|
||||||
@ -495,7 +495,7 @@ func (u *User) SwitchAddressMode() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// logout is the same as Logout, but for internal purposes (logged out from
|
// logout is the same as Logout, but for internal purposes (logged out from
|
||||||
// the server) which emits LogoutEvent to notify other parts of the Bridge.
|
// the server) which emits LogoutEvent to notify other parts of the app.
|
||||||
func (u *User) logout() error {
|
func (u *User) logout() error {
|
||||||
u.lock.Lock()
|
u.lock.Lock()
|
||||||
wasConnected := u.creds.IsConnected()
|
wasConnected := u.creds.IsConnected()
|
||||||
@ -15,7 +15,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package bridge
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -213,7 +213,7 @@ func TestCheckBridgeLoginLoggedOut(t *testing.T) {
|
|||||||
|
|
||||||
err = user.CheckBridgeLogin(testCredentialsDisconnected.BridgePassword)
|
err = user.CheckBridgeLogin(testCredentialsDisconnected.BridgePassword)
|
||||||
waitForEvents()
|
waitForEvents()
|
||||||
assert.Equal(t, "bridge account is logged out, use bridge to login again", err.Error())
|
assert.Equal(t, ErrLoggedOutUser, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckBridgeLoginBadPassword(t *testing.T) {
|
func TestCheckBridgeLoginBadPassword(t *testing.T) {
|
||||||
@ -15,14 +15,14 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package bridge
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
a "github.com/stretchr/testify/assert"
|
a "github.com/stretchr/testify/assert"
|
||||||
@ -38,7 +38,7 @@ func TestNewUserNoCredentialsStore(t *testing.T) {
|
|||||||
a.Error(t, err)
|
a.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewUserBridgeOutdated(t *testing.T) {
|
func TestNewUserAppOutdated(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
@ -15,7 +15,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package bridge
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
537
internal/users/users.go
Normal file
537
internal/users/users.go
Normal file
@ -0,0 +1,537 @@
|
|||||||
|
// Copyright (c) 2020 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package users provides core business logic providing API over credentials store and PM API.
|
||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/store"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
|
imapBackend "github.com/emersion/go-imap/backend"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
logrus "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
log = logrus.WithField("pkg", "users") //nolint[gochecknoglobals]
|
||||||
|
isApplicationOutdated = false //nolint[gochecknoglobals]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Users is a struct handling users.
|
||||||
|
type Users struct {
|
||||||
|
config Configer
|
||||||
|
pref PreferenceProvider
|
||||||
|
panicHandler PanicHandler
|
||||||
|
events listener.Listener
|
||||||
|
clientManager ClientManager
|
||||||
|
credStorer CredentialsStorer
|
||||||
|
storeCache *store.Cache
|
||||||
|
|
||||||
|
// users is a list of accounts that have been added to the app.
|
||||||
|
// They are stored sorted in the credentials store in the order
|
||||||
|
// that they were added to the app chronologically.
|
||||||
|
// People are used to that and so we preserve that ordering here.
|
||||||
|
users []*User
|
||||||
|
|
||||||
|
// idleUpdates is a channel which the imap backend listens to and which it uses
|
||||||
|
// to send idle updates to the mail client (eg thunderbird).
|
||||||
|
// The user stores should send idle updates on this channel.
|
||||||
|
idleUpdates chan imapBackend.Update
|
||||||
|
|
||||||
|
lock sync.RWMutex
|
||||||
|
|
||||||
|
// stopAll can be closed to stop all goroutines from looping (watchAppOutdated, watchAPIAuths, heartbeat etc).
|
||||||
|
stopAll chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(
|
||||||
|
config Configer,
|
||||||
|
pref PreferenceProvider,
|
||||||
|
panicHandler PanicHandler,
|
||||||
|
eventListener listener.Listener,
|
||||||
|
clientManager ClientManager,
|
||||||
|
credStorer CredentialsStorer,
|
||||||
|
) *Users {
|
||||||
|
log.Trace("Creating new users")
|
||||||
|
|
||||||
|
u := &Users{
|
||||||
|
config: config,
|
||||||
|
pref: pref,
|
||||||
|
panicHandler: panicHandler,
|
||||||
|
events: eventListener,
|
||||||
|
clientManager: clientManager,
|
||||||
|
credStorer: credStorer,
|
||||||
|
storeCache: store.NewCache(config.GetIMAPCachePath()),
|
||||||
|
idleUpdates: make(chan imapBackend.Update),
|
||||||
|
lock: sync.RWMutex{},
|
||||||
|
stopAll: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow DoH before starting the app if the user has previously set this setting.
|
||||||
|
// This allows us to start even if protonmail is blocked.
|
||||||
|
if pref.GetBool(preferences.AllowProxyKey) {
|
||||||
|
u.AllowProxy()
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer panicHandler.HandlePanic()
|
||||||
|
u.watchAppOutdated()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer panicHandler.HandlePanic()
|
||||||
|
u.watchAPIAuths()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go u.heartbeat()
|
||||||
|
|
||||||
|
if u.credStorer == nil {
|
||||||
|
log.Error("No credentials store is available")
|
||||||
|
} else if err := u.loadUsersFromCredentialsStore(); err != nil {
|
||||||
|
log.WithError(err).Error("Could not load all users from credentials store")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pref.GetBool(preferences.FirstStartKey) {
|
||||||
|
u.SendMetric(metrics.New(metrics.Setup, metrics.FirstStart, metrics.Label(config.GetVersion())))
|
||||||
|
}
|
||||||
|
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
// heartbeat sends a heartbeat signal once a day.
|
||||||
|
func (u *Users) heartbeat() {
|
||||||
|
ticker := time.NewTicker(1 * time.Minute)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
next, err := strconv.ParseInt(u.pref.Get(preferences.NextHeartbeatKey), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nextTime := time.Unix(next, 0)
|
||||||
|
if time.Now().After(nextTime) {
|
||||||
|
u.SendMetric(metrics.New(metrics.Heartbeat, metrics.Daily, metrics.NoLabel))
|
||||||
|
nextTime = nextTime.Add(24 * time.Hour)
|
||||||
|
u.pref.Set(preferences.NextHeartbeatKey, strconv.FormatInt(nextTime.Unix(), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-u.stopAll:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Users) loadUsersFromCredentialsStore() (err error) {
|
||||||
|
u.lock.Lock()
|
||||||
|
defer u.lock.Unlock()
|
||||||
|
|
||||||
|
userIDs, err := u.credStorer.List()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, userID := range userIDs {
|
||||||
|
l := log.WithField("user", userID)
|
||||||
|
|
||||||
|
user, newUserErr := newUser(u.panicHandler, userID, u.events, u.credStorer, u.clientManager, u.storeCache, u.config.GetDBDir())
|
||||||
|
if newUserErr != nil {
|
||||||
|
l.WithField("user", userID).WithError(newUserErr).Warn("Could not load user, skipping")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
u.users = append(u.users, user)
|
||||||
|
|
||||||
|
if initUserErr := user.init(u.idleUpdates); initUserErr != nil {
|
||||||
|
l.WithField("user", userID).WithError(initUserErr).Warn("Could not initialise user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Users) watchAppOutdated() {
|
||||||
|
ch := make(chan string)
|
||||||
|
|
||||||
|
u.events.Add(events.UpgradeApplicationEvent, ch)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
isApplicationOutdated = true
|
||||||
|
u.closeAllConnections()
|
||||||
|
|
||||||
|
case <-u.stopAll:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchAPIAuths receives auths from the client manager and sends them to the appropriate user.
|
||||||
|
func (u *Users) watchAPIAuths() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case auth := <-u.clientManager.GetAuthUpdateChannel():
|
||||||
|
log.Debug("Users received auth from ClientManager")
|
||||||
|
|
||||||
|
user, ok := u.hasUser(auth.UserID)
|
||||||
|
if !ok {
|
||||||
|
log.WithField("userID", auth.UserID).Info("User not available for auth update")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth.Auth != nil {
|
||||||
|
user.updateAuthToken(auth.Auth)
|
||||||
|
} else if err := user.logout(); err != nil {
|
||||||
|
log.WithError(err).
|
||||||
|
WithField("userID", auth.UserID).
|
||||||
|
Error("User logout failed while watching API auths")
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-u.stopAll:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Users) closeAllConnections() {
|
||||||
|
for _, user := range u.users {
|
||||||
|
user.closeAllConnections()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login authenticates a user.
|
||||||
|
// The login flow:
|
||||||
|
// * Authenticate user:
|
||||||
|
// client, auth, err := users.Authenticate(username, password)
|
||||||
|
//
|
||||||
|
// * In case user `auth.HasTwoFactor()`, ask for it and fully authenticate the user.
|
||||||
|
// auth2FA, err := client.Auth2FA(twoFactorCode)
|
||||||
|
//
|
||||||
|
// * In case user `auth.HasMailboxPassword()`, ask for it, otherwise use `password`
|
||||||
|
// and then finish the login procedure.
|
||||||
|
// user, err := users.FinishLogin(client, auth, mailboxPassword)
|
||||||
|
func (u *Users) Login(username, password string) (authClient pmapi.Client, auth *pmapi.Auth, err error) {
|
||||||
|
u.crashBandicoot(username)
|
||||||
|
|
||||||
|
// We need to use anonymous client because we don't yet have userID and so can't save auth tokens yet.
|
||||||
|
authClient = u.clientManager.GetAnonymousClient()
|
||||||
|
|
||||||
|
authInfo, err := authClient.AuthInfo(username)
|
||||||
|
if err != nil {
|
||||||
|
log.WithField("username", username).WithError(err).Error("Could not get auth info for user")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth, err = authClient.Auth(username, password, authInfo); err != nil {
|
||||||
|
log.WithField("username", username).WithError(err).Error("Could not get auth for user")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishLogin finishes the login procedure and adds the user into the credentials store.
|
||||||
|
// See `Login` for more details of the login flow.
|
||||||
|
func (u *Users) FinishLogin(authClient pmapi.Client, auth *pmapi.Auth, mbPassword string) (user *User, err error) { //nolint[funlen]
|
||||||
|
defer func() {
|
||||||
|
if err == pmapi.ErrUpgradeApplication {
|
||||||
|
u.events.Emit(events.UpgradeApplicationEvent, "")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Debug("Login not finished; removing auth session")
|
||||||
|
if delAuthErr := authClient.DeleteAuth(); delAuthErr != nil {
|
||||||
|
log.WithError(delAuthErr).Error("Failed to clear login session after unlock")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The anonymous client will be removed from list and authentication will not be deleted.
|
||||||
|
authClient.Logout()
|
||||||
|
}()
|
||||||
|
|
||||||
|
apiUser, hashedPassword, err := getAPIUser(authClient, auth, mbPassword)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Failed to get API user")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
if user, ok = u.hasUser(apiUser.ID); ok {
|
||||||
|
if err = u.connectExistingUser(user, auth, hashedPassword); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to connect existing user")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = u.addNewUser(apiUser, auth, hashedPassword); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to add new user")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u.events.Emit(events.UserRefreshEvent, apiUser.ID)
|
||||||
|
|
||||||
|
return u.GetUser(apiUser.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// connectExistingUser connects an existing user.
|
||||||
|
func (u *Users) connectExistingUser(user *User, auth *pmapi.Auth, hashedPassword string) (err error) {
|
||||||
|
if user.IsConnected() {
|
||||||
|
return errors.New("user is already connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the user's password in the cred store in case they changed it.
|
||||||
|
if err = u.credStorer.UpdatePassword(user.ID(), hashedPassword); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to update password of user in credentials store")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := u.clientManager.GetClient(user.ID())
|
||||||
|
|
||||||
|
if auth, err = client.AuthRefresh(auth.GenToken()); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to refresh auth token of new client")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = u.credStorer.UpdateToken(user.ID(), auth.GenToken()); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to update token of user in credentials store")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = user.init(u.idleUpdates); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to initialise user")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// addNewUser adds a new user.
|
||||||
|
func (u *Users) addNewUser(apiUser *pmapi.User, auth *pmapi.Auth, hashedPassword string) (err error) {
|
||||||
|
u.lock.Lock()
|
||||||
|
defer u.lock.Unlock()
|
||||||
|
|
||||||
|
client := u.clientManager.GetClient(apiUser.ID)
|
||||||
|
|
||||||
|
if auth, err = client.AuthRefresh(auth.GenToken()); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to refresh token in new client")
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiUser, err = client.CurrentUser(); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to update API user")
|
||||||
|
}
|
||||||
|
|
||||||
|
activeEmails := client.Addresses().ActiveEmails()
|
||||||
|
|
||||||
|
if _, err = u.credStorer.Add(apiUser.ID, apiUser.Name, auth.GenToken(), hashedPassword, activeEmails); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to add user to credentials store")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := newUser(u.panicHandler, apiUser.ID, u.events, u.credStorer, u.clientManager, u.storeCache, u.config.GetDBDir())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create user")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The user needs to be part of the users list in order for it to receive an auth during initialisation.
|
||||||
|
u.users = append(u.users, user)
|
||||||
|
|
||||||
|
if err = user.init(u.idleUpdates); err != nil {
|
||||||
|
u.users = u.users[:len(u.users)-1]
|
||||||
|
return errors.Wrap(err, "failed to initialise user")
|
||||||
|
}
|
||||||
|
|
||||||
|
u.SendMetric(metrics.New(metrics.Setup, metrics.NewUser, metrics.NoLabel))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAPIUser(client pmapi.Client, auth *pmapi.Auth, mbPassword string) (user *pmapi.User, hashedPassword string, err error) {
|
||||||
|
hashedPassword, err = pmapi.HashMailboxPassword(mbPassword, auth.KeySalt)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Could not hash mailbox password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We unlock the user's PGP key here to detect if the user's mailbox password is wrong.
|
||||||
|
if _, err = client.Unlock(hashedPassword); err != nil {
|
||||||
|
log.WithError(err).Error("Wrong mailbox password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user, err = client.CurrentUser(); err != nil {
|
||||||
|
log.WithError(err).Error("Could not load API user")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUsers returns all added users into keychain (even logged out users).
|
||||||
|
func (u *Users) GetUsers() []*User {
|
||||||
|
u.lock.RLock()
|
||||||
|
defer u.lock.RUnlock()
|
||||||
|
|
||||||
|
return u.users
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
u.crashBandicoot(query)
|
||||||
|
|
||||||
|
u.lock.RLock()
|
||||||
|
defer u.lock.RUnlock()
|
||||||
|
|
||||||
|
for _, user := range u.users {
|
||||||
|
if strings.EqualFold(user.ID(), query) || strings.EqualFold(user.Username(), query) {
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
for _, address := range user.GetAddresses() {
|
||||||
|
if strings.EqualFold(address, query) {
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("user " + query + " not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearData closes all connections (to release db files and so on) and clears all data.
|
||||||
|
func (u *Users) ClearData() error {
|
||||||
|
var result *multierror.Error
|
||||||
|
for _, user := range u.users {
|
||||||
|
if err := user.Logout(); err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
}
|
||||||
|
if err := user.closeStore(); err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := u.config.ClearData(); err != nil {
|
||||||
|
result = multierror.Append(result, err)
|
||||||
|
}
|
||||||
|
return result.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (u *Users) DeleteUser(userID string, clearStore bool) error {
|
||||||
|
u.lock.Lock()
|
||||||
|
defer u.lock.Unlock()
|
||||||
|
|
||||||
|
log := log.WithField("user", userID)
|
||||||
|
|
||||||
|
for idx, user := range u.users {
|
||||||
|
if user.ID() == userID {
|
||||||
|
if err := user.Logout(); err != nil {
|
||||||
|
log.WithError(err).Error("Cannot logout user")
|
||||||
|
// We can try to continue to remove the user.
|
||||||
|
// Token will still be valid, but will expire eventually.
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := user.closeStore(); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to close user store")
|
||||||
|
}
|
||||||
|
if clearStore {
|
||||||
|
// Clear cache after closing connections (done in logout).
|
||||||
|
if err := user.clearStore(); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to clear user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.credStorer.Delete(userID); err != nil {
|
||||||
|
log.WithError(err).Error("Cannot remove user")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.users = append(u.users[:idx], u.users[idx+1:]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("user " + userID + " not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMetric sends a metric. We don't want to return any errors, only log them.
|
||||||
|
func (u *Users) SendMetric(m metrics.Metric) {
|
||||||
|
c := u.clientManager.GetAnonymousClient()
|
||||||
|
defer c.Logout()
|
||||||
|
|
||||||
|
cat, act, lab := m.Get()
|
||||||
|
if err := c.SendSimpleMetric(string(cat), string(act), string(lab)); err != nil {
|
||||||
|
log.Error("Sending metric failed: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"cat": cat,
|
||||||
|
"act": act,
|
||||||
|
"lab": lab,
|
||||||
|
}).Debug("Metric successfully sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIMAPUpdatesChannel sets the channel on which idle events should be sent.
|
||||||
|
func (u *Users) GetIMAPUpdatesChannel() chan imapBackend.Update {
|
||||||
|
if u.idleUpdates == nil {
|
||||||
|
log.Warn("IMAP updates channel is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.idleUpdates
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowProxy instructs the app to use DoH to access an API proxy if necessary.
|
||||||
|
// It also needs to work before the app is initialised (because we may need to use the proxy at startup).
|
||||||
|
func (u *Users) AllowProxy() {
|
||||||
|
u.clientManager.AllowProxy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisallowProxy instructs the app to not use DoH to access an API proxy if necessary.
|
||||||
|
// It also needs to work before the app is initialised (because we may need to use the proxy at startup).
|
||||||
|
func (u *Users) DisallowProxy() {
|
||||||
|
u.clientManager.DisallowProxy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckConnection returns whether there is an internet connection.
|
||||||
|
// This should use the connection manager when it is eventually implemented.
|
||||||
|
func (u *Users) CheckConnection() error {
|
||||||
|
return u.clientManager.CheckConnection()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopWatchers stops all goroutines.
|
||||||
|
func (u *Users) StopWatchers() {
|
||||||
|
close(u.stopAll)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasUser returns whether the struct currently has a user with ID `id`.
|
||||||
|
func (u *Users) hasUser(id string) (user *User, ok bool) {
|
||||||
|
for _, u := range u.users {
|
||||||
|
if u.ID() == id {
|
||||||
|
user, ok = u, true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Easter egg" for testing purposes.
|
||||||
|
func (u *Users) crashBandicoot(username string) {
|
||||||
|
if username == "crash@bandicoot" {
|
||||||
|
panic("Your wish is my command… I crash!")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,7 +15,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package bridge
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -33,7 +33,7 @@ func TestGetNoUser(t *testing.T) {
|
|||||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||||
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
||||||
|
|
||||||
checkBridgeGetUser(t, m, "nouser", -1, "user nouser not found")
|
checkUsersGetUser(t, m, "nouser", -1, "user nouser not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUserByID(t *testing.T) {
|
func TestGetUserByID(t *testing.T) {
|
||||||
@ -43,8 +43,8 @@ func TestGetUserByID(t *testing.T) {
|
|||||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||||
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
||||||
|
|
||||||
checkBridgeGetUser(t, m, "user", 0, "")
|
checkUsersGetUser(t, m, "user", 0, "")
|
||||||
checkBridgeGetUser(t, m, "users", 1, "")
|
checkUsersGetUser(t, m, "users", 1, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUserByName(t *testing.T) {
|
func TestGetUserByName(t *testing.T) {
|
||||||
@ -54,8 +54,8 @@ func TestGetUserByName(t *testing.T) {
|
|||||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||||
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
||||||
|
|
||||||
checkBridgeGetUser(t, m, "username", 0, "")
|
checkUsersGetUser(t, m, "username", 0, "")
|
||||||
checkBridgeGetUser(t, m, "usersname", 1, "")
|
checkUsersGetUser(t, m, "usersname", 1, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUserByEmail(t *testing.T) {
|
func TestGetUserByEmail(t *testing.T) {
|
||||||
@ -65,10 +65,10 @@ func TestGetUserByEmail(t *testing.T) {
|
|||||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||||
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
||||||
|
|
||||||
checkBridgeGetUser(t, m, "user@pm.me", 0, "")
|
checkUsersGetUser(t, m, "user@pm.me", 0, "")
|
||||||
checkBridgeGetUser(t, m, "users@pm.me", 1, "")
|
checkUsersGetUser(t, m, "users@pm.me", 1, "")
|
||||||
checkBridgeGetUser(t, m, "anotheruser@pm.me", 1, "")
|
checkUsersGetUser(t, m, "anotheruser@pm.me", 1, "")
|
||||||
checkBridgeGetUser(t, m, "alsouser@pm.me", 1, "")
|
checkUsersGetUser(t, m, "alsouser@pm.me", 1, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteUser(t *testing.T) {
|
func TestDeleteUser(t *testing.T) {
|
||||||
@ -78,8 +78,8 @@ func TestDeleteUser(t *testing.T) {
|
|||||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||||
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
||||||
|
|
||||||
bridge := testNewBridgeWithUsers(t, m)
|
users := testNewUsersWithUsers(t, m)
|
||||||
defer cleanUpBridgeUserData(bridge)
|
defer cleanUpUsersData(users)
|
||||||
|
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
m.pmapiClient.EXPECT().Logout().Return(),
|
m.pmapiClient.EXPECT().Logout().Return(),
|
||||||
@ -90,9 +90,9 @@ func TestDeleteUser(t *testing.T) {
|
|||||||
|
|
||||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||||
|
|
||||||
err := bridge.DeleteUser("user", true)
|
err := users.DeleteUser("user", true)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, 1, len(bridge.users))
|
assert.Equal(t, 1, len(users.users))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Even when logout fails, delete is done.
|
// Even when logout fails, delete is done.
|
||||||
@ -103,8 +103,8 @@ func TestDeleteUserWithFailingLogout(t *testing.T) {
|
|||||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||||
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
||||||
|
|
||||||
bridge := testNewBridgeWithUsers(t, m)
|
users := testNewUsersWithUsers(t, m)
|
||||||
defer cleanUpBridgeUserData(bridge)
|
defer cleanUpUsersData(users)
|
||||||
|
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
m.pmapiClient.EXPECT().Logout().Return(),
|
m.pmapiClient.EXPECT().Logout().Return(),
|
||||||
@ -116,16 +116,16 @@ func TestDeleteUserWithFailingLogout(t *testing.T) {
|
|||||||
|
|
||||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||||
|
|
||||||
err := bridge.DeleteUser("user", true)
|
err := users.DeleteUser("user", true)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, 1, len(bridge.users))
|
assert.Equal(t, 1, len(users.users))
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkBridgeGetUser(t *testing.T, m mocks, query string, index int, expectedError string) {
|
func checkUsersGetUser(t *testing.T, m mocks, query string, index int, expectedError string) {
|
||||||
bridge := testNewBridgeWithUsers(t, m)
|
users := testNewUsersWithUsers(t, m)
|
||||||
defer cleanUpBridgeUserData(bridge)
|
defer cleanUpUsersData(users)
|
||||||
|
|
||||||
user, err := bridge.GetUser(query)
|
user, err := users.GetUser(query)
|
||||||
waitForEvents()
|
waitForEvents()
|
||||||
|
|
||||||
if expectedError != "" {
|
if expectedError != "" {
|
||||||
@ -136,7 +136,7 @@ func checkBridgeGetUser(t *testing.T, m mocks, query string, index int, expected
|
|||||||
|
|
||||||
var expectedUser *User
|
var expectedUser *User
|
||||||
if index >= 0 {
|
if index >= 0 {
|
||||||
expectedUser = bridge.users[index]
|
expectedUser = users.users[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(m.t, expectedUser, user)
|
assert.Equal(m.t, expectedUser, user)
|
||||||
@ -15,27 +15,27 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package bridge
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBridgeFinishLoginBadMailboxPassword(t *testing.T) {
|
func TestUsersFinishLoginBadMailboxPassword(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
err := errors.New("bad password")
|
err := errors.New("bad password")
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
// Init bridge with no user from keychain.
|
// Init users with no user from keychain.
|
||||||
m.credentialsStore.EXPECT().List().Return([]string{}, nil),
|
m.credentialsStore.EXPECT().List().Return([]string{}, nil),
|
||||||
|
|
||||||
// Set up mocks for FinishLogin.
|
// Set up mocks for FinishLogin.
|
||||||
@ -44,16 +44,16 @@ func TestBridgeFinishLoginBadMailboxPassword(t *testing.T) {
|
|||||||
m.pmapiClient.EXPECT().Logout(),
|
m.pmapiClient.EXPECT().Logout(),
|
||||||
)
|
)
|
||||||
|
|
||||||
checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "", err)
|
checkUsersFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBridgeFinishLoginUpgradeApplication(t *testing.T) {
|
func TestUsersFinishLoginUpgradeApplication(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
err := errors.New("Cannot logout when upgrade needed")
|
err := errors.New("Cannot logout when upgrade needed")
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
// Init bridge with no user from keychain.
|
// Init users with no user from keychain.
|
||||||
m.credentialsStore.EXPECT().List().Return([]string{}, nil),
|
m.credentialsStore.EXPECT().List().Return([]string{}, nil),
|
||||||
|
|
||||||
// Set up mocks for FinishLogin.
|
// Set up mocks for FinishLogin.
|
||||||
@ -64,7 +64,7 @@ func TestBridgeFinishLoginUpgradeApplication(t *testing.T) {
|
|||||||
m.pmapiClient.EXPECT().Logout(),
|
m.pmapiClient.EXPECT().Logout(),
|
||||||
)
|
)
|
||||||
|
|
||||||
checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "", pmapi.ErrUpgradeApplication)
|
checkUsersFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "", pmapi.ErrUpgradeApplication)
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshWithToken(token string) *pmapi.Auth {
|
func refreshWithToken(token string) *pmapi.Auth {
|
||||||
@ -81,7 +81,7 @@ func credentialsWithToken(token string) *credentials.Credentials {
|
|||||||
return tmp
|
return tmp
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBridgeFinishLoginNewUser(t *testing.T) {
|
func TestUsersFinishLoginNewUser(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ func TestBridgeFinishLoginNewUser(t *testing.T) {
|
|||||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||||
|
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
// bridge.New() finds no users in keychain.
|
// users.New() finds no users in keychain.
|
||||||
m.credentialsStore.EXPECT().List().Return([]string{}, nil),
|
m.credentialsStore.EXPECT().List().Return([]string{}, nil),
|
||||||
|
|
||||||
// getAPIUser() loads user info from API (e.g. userID).
|
// getAPIUser() loads user info from API (e.g. userID).
|
||||||
@ -128,12 +128,12 @@ func TestBridgeFinishLoginNewUser(t *testing.T) {
|
|||||||
|
|
||||||
mockEventLoopNoAction(m)
|
mockEventLoopNoAction(m)
|
||||||
|
|
||||||
user := checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil)
|
user := checkUsersFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil)
|
||||||
|
|
||||||
mockAuthUpdate(user, "afterCredentials", m)
|
mockAuthUpdate(user, "afterCredentials", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBridgeFinishLoginExistingDisconnectedUser(t *testing.T) {
|
func TestUsersFinishLoginExistingDisconnectedUser(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ func TestBridgeFinishLoginExistingDisconnectedUser(t *testing.T) {
|
|||||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||||
|
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
// bridge.New() finds one existing user in keychain.
|
// users.New() finds one existing user in keychain.
|
||||||
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil),
|
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil),
|
||||||
|
|
||||||
// newUser()
|
// newUser()
|
||||||
@ -186,12 +186,12 @@ func TestBridgeFinishLoginExistingDisconnectedUser(t *testing.T) {
|
|||||||
|
|
||||||
mockEventLoopNoAction(m)
|
mockEventLoopNoAction(m)
|
||||||
|
|
||||||
user := checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil)
|
user := checkUsersFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil)
|
||||||
|
|
||||||
mockAuthUpdate(user, "afterCredentials", m)
|
mockAuthUpdate(user, "afterCredentials", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBridgeFinishLoginConnectedUser(t *testing.T) {
|
func TestUsersFinishLoginConnectedUser(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
@ -201,8 +201,8 @@ func TestBridgeFinishLoginConnectedUser(t *testing.T) {
|
|||||||
mockConnectedUser(m)
|
mockConnectedUser(m)
|
||||||
mockEventLoopNoAction(m)
|
mockEventLoopNoAction(m)
|
||||||
|
|
||||||
bridge := testNewBridge(t, m)
|
users := testNewUsers(t, m)
|
||||||
defer cleanUpBridgeUserData(bridge)
|
defer cleanUpUsersData(users)
|
||||||
|
|
||||||
// Then, try to log in again...
|
// Then, try to log in again...
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
@ -212,15 +212,15 @@ func TestBridgeFinishLoginConnectedUser(t *testing.T) {
|
|||||||
m.pmapiClient.EXPECT().Logout(),
|
m.pmapiClient.EXPECT().Logout(),
|
||||||
)
|
)
|
||||||
|
|
||||||
_, err := bridge.FinishLogin(m.pmapiClient, testAuth, testCredentials.MailboxPassword)
|
_, err := users.FinishLogin(m.pmapiClient, testAuth, testCredentials.MailboxPassword)
|
||||||
assert.Equal(t, "user is already connected", err.Error())
|
assert.Equal(t, "user is already connected", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkBridgeFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassword string, expectedUserID string, expectedErr error) *User {
|
func checkUsersFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassword string, expectedUserID string, expectedErr error) *User {
|
||||||
bridge := testNewBridge(t, m)
|
users := testNewUsers(t, m)
|
||||||
defer cleanUpBridgeUserData(bridge)
|
defer cleanUpUsersData(users)
|
||||||
|
|
||||||
user, err := bridge.FinishLogin(m.pmapiClient, auth, mailboxPassword)
|
user, err := users.FinishLogin(m.pmapiClient, auth, mailboxPassword)
|
||||||
|
|
||||||
waitForEvents()
|
waitForEvents()
|
||||||
|
|
||||||
@ -228,11 +228,11 @@ func checkBridgeFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPass
|
|||||||
|
|
||||||
if expectedUserID != "" {
|
if expectedUserID != "" {
|
||||||
assert.Equal(t, expectedUserID, user.ID())
|
assert.Equal(t, expectedUserID, user.ID())
|
||||||
assert.Equal(t, 1, len(bridge.users))
|
assert.Equal(t, 1, len(users.users))
|
||||||
assert.Equal(t, expectedUserID, bridge.users[0].ID())
|
assert.Equal(t, expectedUserID, users.users[0].ID())
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, (*User)(nil), user)
|
assert.Equal(t, (*User)(nil), user)
|
||||||
assert.Equal(t, 0, len(bridge.users))
|
assert.Equal(t, 0, len(users.users))
|
||||||
}
|
}
|
||||||
|
|
||||||
return user
|
return user
|
||||||
@ -15,40 +15,40 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package bridge
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
credentials "github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewBridgeNoKeychain(t *testing.T) {
|
func TestNewUsersNoKeychain(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
m.credentialsStore.EXPECT().List().Return([]string{}, errors.New("no keychain"))
|
m.credentialsStore.EXPECT().List().Return([]string{}, errors.New("no keychain"))
|
||||||
|
|
||||||
checkBridgeNew(t, m, []*credentials.Credentials{})
|
checkUsersNew(t, m, []*credentials.Credentials{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBridgeWithoutUsersInCredentialsStore(t *testing.T) {
|
func TestNewUsersWithoutUsersInCredentialsStore(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
|
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
|
||||||
|
|
||||||
checkBridgeNew(t, m, []*credentials.Credentials{})
|
checkUsersNew(t, m, []*credentials.Credentials{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBridgeWithDisconnectedUser(t *testing.T) {
|
func TestNewUsersWithDisconnectedUser(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
@ -63,10 +63,10 @@ func TestNewBridgeWithDisconnectedUser(t *testing.T) {
|
|||||||
m.pmapiClient.EXPECT().Addresses().Return(nil),
|
m.pmapiClient.EXPECT().Addresses().Return(nil),
|
||||||
)
|
)
|
||||||
|
|
||||||
checkBridgeNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
|
checkUsersNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBridgeWithConnectedUserWithBadToken(t *testing.T) {
|
func TestNewUsersWithConnectedUserWithBadToken(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ func TestNewBridgeWithConnectedUserWithBadToken(t *testing.T) {
|
|||||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
|
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
|
||||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||||
|
|
||||||
checkBridgeNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
|
checkUsersNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
|
||||||
}
|
}
|
||||||
|
|
||||||
func mockConnectedUser(m mocks) {
|
func mockConnectedUser(m mocks) {
|
||||||
@ -105,9 +105,9 @@ func mockConnectedUser(m mocks) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mockAuthUpdate simulates bridge calling UpdateAuthToken on the given user.
|
// mockAuthUpdate simulates users calling UpdateAuthToken on the given user.
|
||||||
// This would normally be done by Bridge when it receives an auth from the ClientManager,
|
// This would normally be done by users when it receives an auth from the ClientManager,
|
||||||
// but as we don't have a full bridge instance here, we do this manually.
|
// but as we don't have a full users instance here, we do this manually.
|
||||||
func mockAuthUpdate(user *User, token string, m mocks) {
|
func mockAuthUpdate(user *User, token string, m mocks) {
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
m.credentialsStore.EXPECT().UpdateToken("user", ":"+token).Return(nil),
|
m.credentialsStore.EXPECT().UpdateToken("user", ":"+token).Return(nil),
|
||||||
@ -119,7 +119,7 @@ func mockAuthUpdate(user *User, token string, m mocks) {
|
|||||||
waitForEvents()
|
waitForEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBridgeWithConnectedUser(t *testing.T) {
|
func TestNewUsersWithConnectedUser(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
@ -129,12 +129,12 @@ func TestNewBridgeWithConnectedUser(t *testing.T) {
|
|||||||
mockConnectedUser(m)
|
mockConnectedUser(m)
|
||||||
mockEventLoopNoAction(m)
|
mockEventLoopNoAction(m)
|
||||||
|
|
||||||
checkBridgeNew(t, m, []*credentials.Credentials{testCredentials})
|
checkUsersNew(t, m, []*credentials.Credentials{testCredentials})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests two users with different states and checks also the order from
|
// Tests two users with different states and checks also the order from
|
||||||
// credentials store is kept also in array of Bridge users.
|
// credentials store is kept also in array of users.
|
||||||
func TestNewBridgeWithUsers(t *testing.T) {
|
func TestNewUsersWithUsers(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
@ -155,10 +155,10 @@ func TestNewBridgeWithUsers(t *testing.T) {
|
|||||||
|
|
||||||
mockEventLoopNoAction(m)
|
mockEventLoopNoAction(m)
|
||||||
|
|
||||||
checkBridgeNew(t, m, []*credentials.Credentials{testCredentialsDisconnected, testCredentials})
|
checkUsersNew(t, m, []*credentials.Credentials{testCredentialsDisconnected, testCredentials})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewBridgeFirstStart(t *testing.T) {
|
func TestNewUsersFirstStart(t *testing.T) {
|
||||||
m := initMocks(t)
|
m := initMocks(t)
|
||||||
defer m.ctrl.Finish()
|
defer m.ctrl.Finish()
|
||||||
|
|
||||||
@ -170,17 +170,17 @@ func TestNewBridgeFirstStart(t *testing.T) {
|
|||||||
m.pmapiClient.EXPECT().Logout(),
|
m.pmapiClient.EXPECT().Logout(),
|
||||||
)
|
)
|
||||||
|
|
||||||
testNewBridge(t, m)
|
testNewUsers(t, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkBridgeNew(t *testing.T, m mocks, expectedCredentials []*credentials.Credentials) {
|
func checkUsersNew(t *testing.T, m mocks, expectedCredentials []*credentials.Credentials) {
|
||||||
bridge := testNewBridge(t, m)
|
users := testNewUsers(t, m)
|
||||||
defer cleanUpBridgeUserData(bridge)
|
defer cleanUpUsersData(users)
|
||||||
|
|
||||||
assert.Equal(m.t, len(expectedCredentials), len(bridge.GetUsers()))
|
assert.Equal(m.t, len(expectedCredentials), len(users.GetUsers()))
|
||||||
|
|
||||||
credentials := []*credentials.Credentials{}
|
credentials := []*credentials.Credentials{}
|
||||||
for _, user := range bridge.users {
|
for _, user := range users.users {
|
||||||
credentials = append(credentials, user.creds)
|
credentials = append(credentials, user.creds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,7 +15,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package bridge
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -25,12 +25,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
|
|
||||||
bridgemocks "github.com/ProtonMail/proton-bridge/internal/bridge/mocks"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/store"
|
"github.com/ProtonMail/proton-bridge/internal/store"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
|
usersmocks "github.com/ProtonMail/proton-bridge/internal/users/mocks"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
pmapimocks "github.com/ProtonMail/proton-bridge/pkg/pmapi/mocks"
|
pmapimocks "github.com/ProtonMail/proton-bridge/pkg/pmapi/mocks"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
@ -131,11 +131,11 @@ type mocks struct {
|
|||||||
t *testing.T
|
t *testing.T
|
||||||
|
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
config *bridgemocks.MockConfiger
|
config *usersmocks.MockConfiger
|
||||||
PanicHandler *bridgemocks.MockPanicHandler
|
PanicHandler *usersmocks.MockPanicHandler
|
||||||
prefProvider *bridgemocks.MockPreferenceProvider
|
prefProvider *usersmocks.MockPreferenceProvider
|
||||||
clientManager *bridgemocks.MockClientManager
|
clientManager *usersmocks.MockClientManager
|
||||||
credentialsStore *bridgemocks.MockCredentialsStorer
|
credentialsStore *usersmocks.MockCredentialsStorer
|
||||||
eventListener *MockListener
|
eventListener *MockListener
|
||||||
|
|
||||||
pmapiClient *pmapimocks.MockClient
|
pmapiClient *pmapimocks.MockClient
|
||||||
@ -172,11 +172,11 @@ func initMocks(t *testing.T) mocks {
|
|||||||
t: t,
|
t: t,
|
||||||
|
|
||||||
ctrl: mockCtrl,
|
ctrl: mockCtrl,
|
||||||
config: bridgemocks.NewMockConfiger(mockCtrl),
|
config: usersmocks.NewMockConfiger(mockCtrl),
|
||||||
PanicHandler: bridgemocks.NewMockPanicHandler(mockCtrl),
|
PanicHandler: usersmocks.NewMockPanicHandler(mockCtrl),
|
||||||
prefProvider: bridgemocks.NewMockPreferenceProvider(mockCtrl),
|
prefProvider: usersmocks.NewMockPreferenceProvider(mockCtrl),
|
||||||
clientManager: bridgemocks.NewMockClientManager(mockCtrl),
|
clientManager: usersmocks.NewMockClientManager(mockCtrl),
|
||||||
credentialsStore: bridgemocks.NewMockCredentialsStorer(mockCtrl),
|
credentialsStore: usersmocks.NewMockCredentialsStorer(mockCtrl),
|
||||||
eventListener: NewMockListener(mockCtrl),
|
eventListener: NewMockListener(mockCtrl),
|
||||||
|
|
||||||
pmapiClient: pmapimocks.NewMockClient(mockCtrl),
|
pmapiClient: pmapimocks.NewMockClient(mockCtrl),
|
||||||
@ -195,7 +195,7 @@ func initMocks(t *testing.T) mocks {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNewBridgeWithUsers(t *testing.T, m mocks) *Bridge {
|
func testNewUsersWithUsers(t *testing.T, m mocks) *Users {
|
||||||
// Events are asynchronous
|
// Events are asynchronous
|
||||||
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil).Times(2)
|
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil).Times(2)
|
||||||
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).Times(2)
|
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).Times(2)
|
||||||
@ -225,18 +225,18 @@ func testNewBridgeWithUsers(t *testing.T, m mocks) *Bridge {
|
|||||||
m.pmapiClient.EXPECT().Addresses().Return(testPMAPIAddresses),
|
m.pmapiClient.EXPECT().Addresses().Return(testPMAPIAddresses),
|
||||||
)
|
)
|
||||||
|
|
||||||
bridge := testNewBridge(t, m)
|
users := testNewUsers(t, m)
|
||||||
|
|
||||||
user, _ := bridge.GetUser("user")
|
user, _ := users.GetUser("user")
|
||||||
mockAuthUpdate(user, "reftok", m)
|
mockAuthUpdate(user, "reftok", m)
|
||||||
|
|
||||||
users, _ := bridge.GetUser("user")
|
user, _ = users.GetUser("user")
|
||||||
mockAuthUpdate(users, "reftok", m)
|
mockAuthUpdate(user, "reftok", m)
|
||||||
|
|
||||||
return bridge
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNewBridge(t *testing.T, m mocks) *Bridge {
|
func testNewUsers(t *testing.T, m mocks) *Users {
|
||||||
cacheFile, err := ioutil.TempFile("", "bridge-store-cache-*.db")
|
cacheFile, err := ioutil.TempFile("", "bridge-store-cache-*.db")
|
||||||
require.NoError(t, err, "could not get temporary file for store cache")
|
require.NoError(t, err, "could not get temporary file for store cache")
|
||||||
|
|
||||||
@ -248,14 +248,14 @@ func testNewBridge(t *testing.T, m mocks) *Bridge {
|
|||||||
m.eventListener.EXPECT().Add(events.UpgradeApplicationEvent, gomock.Any())
|
m.eventListener.EXPECT().Add(events.UpgradeApplicationEvent, gomock.Any())
|
||||||
m.clientManager.EXPECT().GetAuthUpdateChannel().Return(make(chan pmapi.ClientAuth))
|
m.clientManager.EXPECT().GetAuthUpdateChannel().Return(make(chan pmapi.ClientAuth))
|
||||||
|
|
||||||
bridge := New(m.config, m.prefProvider, m.PanicHandler, m.eventListener, m.clientManager, m.credentialsStore)
|
users := New(m.config, m.prefProvider, m.PanicHandler, m.eventListener, m.clientManager, m.credentialsStore)
|
||||||
|
|
||||||
waitForEvents()
|
waitForEvents()
|
||||||
|
|
||||||
return bridge
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanUpBridgeUserData(b *Bridge) {
|
func cleanUpUsersData(b *Users) {
|
||||||
for _, user := range b.users {
|
for _, user := range b.users {
|
||||||
_ = user.clearStore()
|
_ = user.clearStore()
|
||||||
}
|
}
|
||||||
@ -268,8 +268,8 @@ func TestClearData(t *testing.T) {
|
|||||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||||
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
||||||
|
|
||||||
bridge := testNewBridgeWithUsers(t, m)
|
users := testNewUsersWithUsers(t, m)
|
||||||
defer cleanUpBridgeUserData(bridge)
|
defer cleanUpUsersData(users)
|
||||||
|
|
||||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "users@pm.me")
|
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "users@pm.me")
|
||||||
@ -286,7 +286,7 @@ func TestClearData(t *testing.T) {
|
|||||||
|
|
||||||
m.config.EXPECT().ClearData().Return(nil)
|
m.config.EXPECT().ClearData().Return(nil)
|
||||||
|
|
||||||
require.NoError(t, bridge.ClearData())
|
require.NoError(t, users.ClearData())
|
||||||
|
|
||||||
waitForEvents()
|
waitForEvents()
|
||||||
}
|
}
|
||||||
@ -15,7 +15,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package bridge
|
package users
|
||||||
|
|
||||||
// IsAuthorized returns whether the user has received an Auth from the API yet.
|
// IsAuthorized returns whether the user has received an Auth from the API yet.
|
||||||
func (u *User) IsAuthorized() bool {
|
func (u *User) IsAuthorized() bool {
|
||||||
@ -20,6 +20,7 @@ package context
|
|||||||
import (
|
import (
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -57,9 +58,9 @@ func (ctx *TestContext) RestartBridge() error {
|
|||||||
func newBridgeInstance(
|
func newBridgeInstance(
|
||||||
t *bddT,
|
t *bddT,
|
||||||
cfg *fakeConfig,
|
cfg *fakeConfig,
|
||||||
credStore bridge.CredentialsStorer,
|
credStore users.CredentialsStorer,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
clientManager bridge.ClientManager,
|
clientManager users.ClientManager,
|
||||||
) *bridge.Bridge {
|
) *bridge.Bridge {
|
||||||
panicHandler := &panicHandler{t: t}
|
panicHandler := &panicHandler{t: t}
|
||||||
pref := preferences.New(cfg)
|
pref := preferences.New(cfg)
|
||||||
|
|||||||
@ -23,8 +23,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/store"
|
"github.com/ProtonMail/proton-bridge/internal/store"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/srp"
|
"github.com/ProtonMail/proton-bridge/pkg/srp"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -56,7 +56,7 @@ func (ctx *TestContext) LoginUser(username, password, mailboxPassword string) (e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetUser retrieves the bridge user matching the given query string.
|
// GetUser retrieves the bridge user matching the given query string.
|
||||||
func (ctx *TestContext) GetUser(username string) (*bridge.User, error) {
|
func (ctx *TestContext) GetUser(username string) (*users.User, error) {
|
||||||
return ctx.bridge.GetUser(username)
|
return ctx.bridge.GetUser(username)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ package context
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
"github.com/ProtonMail/proton-bridge/test/accounts"
|
"github.com/ProtonMail/proton-bridge/test/accounts"
|
||||||
@ -48,7 +49,7 @@ type TestContext struct {
|
|||||||
// Bridge core related variables.
|
// Bridge core related variables.
|
||||||
bridge *bridge.Bridge
|
bridge *bridge.Bridge
|
||||||
bridgeLastError error
|
bridgeLastError error
|
||||||
credStore bridge.CredentialsStorer
|
credStore users.CredentialsStorer
|
||||||
|
|
||||||
// IMAP related variables.
|
// IMAP related variables.
|
||||||
imapAddr string
|
imapAddr string
|
||||||
|
|||||||
@ -20,7 +20,7 @@ package context
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
|
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
// bridgePassword is password to be used for IMAP or SMTP under tests.
|
// bridgePassword is password to be used for IMAP or SMTP under tests.
|
||||||
|
|||||||
@ -12,7 +12,7 @@ Feature: IMAP auth
|
|||||||
Scenario: Authenticates with disconnected user
|
Scenario: Authenticates with disconnected user
|
||||||
Given there is disconnected user "user"
|
Given there is disconnected user "user"
|
||||||
When IMAP client authenticates "user"
|
When IMAP client authenticates "user"
|
||||||
Then IMAP response is "IMAP error: NO bridge account is logged out, use bridge to login again"
|
Then IMAP response is "IMAP error: NO account is logged out, use the app to login again"
|
||||||
|
|
||||||
Scenario: Authenticates with connected user that was loaded without internet
|
Scenario: Authenticates with connected user that was loaded without internet
|
||||||
Given there is connected user "user"
|
Given there is connected user "user"
|
||||||
@ -31,13 +31,13 @@ Feature: IMAP auth
|
|||||||
Given there is connected user "user"
|
Given there is connected user "user"
|
||||||
When "user" logs out from bridge
|
When "user" logs out from bridge
|
||||||
And IMAP client authenticates "user"
|
And IMAP client authenticates "user"
|
||||||
Then IMAP response is "IMAP error: NO bridge account is logged out, use bridge to login again"
|
Then IMAP response is "IMAP error: NO account is logged out, use the app to login again"
|
||||||
|
|
||||||
Scenario: Authenticates user which was re-logged in
|
Scenario: Authenticates user which was re-logged in
|
||||||
Given there is connected user "user"
|
Given there is connected user "user"
|
||||||
When "user" logs out from bridge
|
When "user" logs out from bridge
|
||||||
And IMAP client authenticates "user"
|
And IMAP client authenticates "user"
|
||||||
Then IMAP response is "IMAP error: NO bridge account is logged out, use bridge to login again"
|
Then IMAP response is "IMAP error: NO account is logged out, use the app to login again"
|
||||||
When "user" logs in to bridge
|
When "user" logs in to bridge
|
||||||
And IMAP client authenticates "user"
|
And IMAP client authenticates "user"
|
||||||
Then IMAP response is "OK"
|
Then IMAP response is "OK"
|
||||||
|
|||||||
@ -19,7 +19,7 @@ Feature: SMTP auth
|
|||||||
Scenario: Authenticates with disconnected user
|
Scenario: Authenticates with disconnected user
|
||||||
Given there is disconnected user "user"
|
Given there is disconnected user "user"
|
||||||
When SMTP client authenticates "user"
|
When SMTP client authenticates "user"
|
||||||
Then SMTP response is "SMTP error: 454 bridge account is logged out, use bridge to login again"
|
Then SMTP response is "SMTP error: 454 account is logged out, use the app to login again"
|
||||||
|
|
||||||
Scenario: Authenticates with no user
|
Scenario: Authenticates with no user
|
||||||
When SMTP client authenticates with username "user@pm.me" and password "bridgepassword"
|
When SMTP client authenticates with username "user@pm.me" and password "bridgepassword"
|
||||||
|
|||||||
Reference in New Issue
Block a user