diff --git a/Changelog.md b/Changelog.md index 12fb328e..71934666 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) ## unreleased ### 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-312 validate recipient emails in send before asking for their public keys diff --git a/Makefile b/Makefile index 3e43be82..313626af 100644 --- a/Makefile +++ b/Makefile @@ -165,9 +165,11 @@ test: gofiles ./internal/frontend/autoconfig/... \ ./internal/frontend/cli/... \ ./internal/imap/... \ + ./internal/metrics/... \ ./internal/preferences/... \ ./internal/smtp/... \ ./internal/store/... \ + ./internal/users/... \ ./pkg/... bench: @@ -179,7 +181,7 @@ coverage: test go tool cover -html=/tmp/coverage.out -o=coverage.html 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/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 diff --git a/cmd/Desktop-Bridge/main.go b/cmd/Desktop-Bridge/main.go index df49d67f..fafa6baa 100644 --- a/cmd/Desktop-Bridge/main.go +++ b/cmd/Desktop-Bridge/main.go @@ -47,12 +47,12 @@ import ( "github.com/ProtonMail/proton-bridge/internal/api" "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/frontend" "github.com/ProtonMail/proton-bridge/internal/imap" "github.com/ProtonMail/proton-bridge/internal/preferences" "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/config" "github.com/ProtonMail/proton-bridge/pkg/constants" @@ -261,7 +261,7 @@ func run(context *cli.Context) (contextError error) { // nolint[funlen] eventListener := listener.New() events.SetupEvents(eventListener) - credentialsStore, credentialsError := credentials.NewStore() + credentialsStore, credentialsError := credentials.NewStore("bridge") if credentialsError != nil { log.Error("Could not get credentials store: ", credentialsError) } diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index d137f1b7..2063747c 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -15,57 +15,24 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -// Package bridge provides core business logic providing API over credentials store and PM API. +// Package bridge provides core functionality of Bridge app. package bridge import ( - "strconv" - "strings" - "sync" - "time" + "github.com/ProtonMail/proton-bridge/internal/users" - "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", "bridge") //nolint[gochecknoglobals] - isApplicationOutdated = false //nolint[gochecknoglobals] + log = logrus.WithField("pkg", "bridge") //nolint[gochecknoglobals] ) -// Bridge is a struct handling users. type Bridge struct { - config Configer - pref PreferenceProvider - panicHandler PanicHandler - events listener.Listener - clientManager ClientManager - credStorer CredentialsStorer - storeCache *store.Cache + *users.Users - // users is a list of accounts that have been added to bridge. - // 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{} + clientManager users.ClientManager userAgentClientName string userAgentClientVersion string @@ -73,444 +40,19 @@ type Bridge struct { } func New( - config Configer, - pref PreferenceProvider, - panicHandler PanicHandler, + config users.Configer, + pref users.PreferenceProvider, + panicHandler users.PanicHandler, eventListener listener.Listener, - clientManager ClientManager, - credStorer CredentialsStorer, + clientManager users.ClientManager, + credStorer users.CredentialsStorer, ) *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, - 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). @@ -541,53 +83,26 @@ func (b *Bridge) updateUserAgent() { b.clientManager.SetUserAgent(b.userAgentClientName, b.userAgentClientVersion, b.userAgentOS) } -// GetIMAPUpdatesChannel sets the channel on which idle events should be sent. -func (b *Bridge) GetIMAPUpdatesChannel() chan imapBackend.Update { - if b.idleUpdates == nil { - log.Warn("Bridge updates channel is nil") +// 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 } - return b.idleUpdates -} + log.Info("Bug successfully reported") -// AllowProxy instructs bridge to 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) 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!") - } + return nil } diff --git a/internal/imap/bridge.go b/internal/imap/bridge.go index 63a1088e..0b23ca29 100644 --- a/internal/imap/bridge.go +++ b/internal/imap/bridge.go @@ -19,6 +19,7 @@ package imap import ( "github.com/ProtonMail/proton-bridge/internal/bridge" + "github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/pkg/pmapi" ) @@ -67,10 +68,10 @@ func (b *bridgeWrap) GetUser(query string) (bridgeUser, error) { } type bridgeUserWrap struct { - *bridge.User + *users.User } -func newBridgeUserWrap(bridgeUser *bridge.User) *bridgeUserWrap { +func newBridgeUserWrap(bridgeUser *users.User) *bridgeUserWrap { return &bridgeUserWrap{User: bridgeUser} } diff --git a/internal/smtp/bridge.go b/internal/smtp/bridge.go index 5629bdba..84536fa8 100644 --- a/internal/smtp/bridge.go +++ b/internal/smtp/bridge.go @@ -19,6 +19,7 @@ package smtp import ( "github.com/ProtonMail/proton-bridge/internal/bridge" + "github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/pkg/pmapi" ) @@ -54,10 +55,10 @@ func (b *bridgeWrap) GetUser(query string) (bridgeUser, error) { } type bridgeUserWrap struct { - *bridge.User + *users.User } -func newBridgeUserWrap(bridgeUser *bridge.User) *bridgeUserWrap { +func newBridgeUserWrap(bridgeUser *users.User) *bridgeUserWrap { return &bridgeUserWrap{User: bridgeUser} } diff --git a/internal/smtp/sending_info_test.go b/internal/smtp/sending_info_test.go index 64d81a99..4ef88c5e 100644 --- a/internal/smtp/sending_info_test.go +++ b/internal/smtp/sending_info_test.go @@ -22,8 +22,8 @@ import ( "testing" 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/users" "github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/golang/mock/gomock" @@ -32,14 +32,14 @@ import ( type mocks struct { t *testing.T - eventListener *bridge.MockListener + eventListener *users.MockListener } func initMocks(t *testing.T) mocks { mockCtrl := gomock.NewController(t) return mocks{ t: t, - eventListener: bridge.NewMockListener(mockCtrl), + eventListener: users.NewMockListener(mockCtrl), } } diff --git a/internal/bridge/credentials/credentials.go b/internal/users/credentials/credentials.go similarity index 97% rename from internal/bridge/credentials/credentials.go rename to internal/users/credentials/credentials.go index b281afad..b269238b 100644 --- a/internal/bridge/credentials/credentials.go +++ b/internal/users/credentials/credentials.go @@ -33,7 +33,7 @@ import ( const sep = "\x00" var ( - log = logrus.WithField("pkg", "bridge") //nolint[gochecknoglobals] + log = logrus.WithField("pkg", "credentials") //nolint[gochecknoglobals] ErrWrongFormat = errors.New("backend/creds: malformed password") ) diff --git a/internal/bridge/credentials/crypto.go b/internal/users/credentials/crypto.go similarity index 100% rename from internal/bridge/credentials/crypto.go rename to internal/users/credentials/crypto.go diff --git a/internal/bridge/credentials/store.go b/internal/users/credentials/store.go similarity index 98% rename from internal/bridge/credentials/store.go rename to internal/users/credentials/store.go index 3bfc795f..02aaba6f 100644 --- a/internal/bridge/credentials/store.go +++ b/internal/users/credentials/store.go @@ -36,8 +36,8 @@ type Store struct { } // NewStore creates a new encrypted credentials store. -func NewStore() (*Store, error) { - secrets, err := keychain.NewAccess("bridge") +func NewStore(appName string) (*Store, error) { + secrets, err := keychain.NewAccess(appName) return &Store{ secrets: secrets, }, err diff --git a/internal/bridge/credentials/store_test.go b/internal/users/credentials/store_test.go similarity index 100% rename from internal/bridge/credentials/store_test.go rename to internal/users/credentials/store_test.go diff --git a/internal/bridge/mock_listener.go b/internal/users/mock_listener.go similarity index 98% rename from internal/bridge/mock_listener.go rename to internal/users/mock_listener.go index f1c05e8a..ce5d6145 100644 --- a/internal/bridge/mock_listener.go +++ b/internal/users/mock_listener.go @@ -1,8 +1,8 @@ // Code generated by MockGen. DO NOT EDIT. // Source: ./listener/listener.go -// Package bridge is a generated GoMock package. -package bridge +// Package users is a generated GoMock package. +package users import ( reflect "reflect" diff --git a/internal/bridge/mocks/mocks.go b/internal/users/mocks/mocks.go similarity index 98% rename from internal/bridge/mocks/mocks.go rename to internal/users/mocks/mocks.go index a24b99b8..624883f6 100644 --- a/internal/bridge/mocks/mocks.go +++ b/internal/users/mocks/mocks.go @@ -1,5 +1,5 @@ // 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 @@ -7,7 +7,7 @@ package mocks import ( 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" gomock "github.com/golang/mock/gomock" ) diff --git a/internal/bridge/types.go b/internal/users/types.go similarity index 95% rename from internal/bridge/types.go rename to internal/users/types.go index d7533673..0e9a4e7c 100644 --- a/internal/bridge/types.go +++ b/internal/users/types.go @@ -15,10 +15,10 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -package bridge +package users import ( - "github.com/ProtonMail/proton-bridge/internal/bridge/credentials" + "github.com/ProtonMail/proton-bridge/internal/users/credentials" "github.com/ProtonMail/proton-bridge/pkg/pmapi" ) diff --git a/internal/bridge/user.go b/internal/users/user.go similarity index 97% rename from internal/bridge/user.go rename to internal/users/user.go index 8f2e17d3..f82ceb8c 100644 --- a/internal/bridge/user.go +++ b/internal/users/user.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -package bridge +package users import ( "fmt" @@ -24,9 +24,9 @@ import ( "strings" "sync" - "github.com/ProtonMail/proton-bridge/internal/bridge/credentials" "github.com/ProtonMail/proton-bridge/internal/events" "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/pmapi" imapBackend "github.com/emersion/go-imap/backend" @@ -34,8 +34,8 @@ import ( "github.com/sirupsen/logrus" ) -// ErrLoggedOutUser is sent to IMAP and SMTP if user exists, password is OK but user is logged out from bridge. -var ErrLoggedOutUser = errors.New("bridge account is logged out, use bridge to login again") +// 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("account is logged out, use the app to login again") // User is a struct on top of API client and credentials store. type User struct { @@ -61,7 +61,7 @@ type User struct { wasKeyringUnlocked bool } -// newUser creates a new bridge user. +// newUser creates a new user. func newUser( panicHandler PanicHandler, userID string, @@ -98,7 +98,7 @@ func (u *User) client() pmapi.Client { 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 // 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 @@ -119,7 +119,7 @@ func (u *User) init(idleUpdates chan imapBackend.Update) (err error) { u.creds = creds // 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 { switch errors.Cause(authErr) { case pmapi.ErrAPINotReachable, pmapi.ErrUpgradeApplication, ErrLoggedOutUser: @@ -245,7 +245,7 @@ func (u *User) authorizeAndUnlock() (err error) { } 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 { 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 -// 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 { u.lock.Lock() wasConnected := u.creds.IsConnected() diff --git a/internal/bridge/user_credentials_test.go b/internal/users/user_credentials_test.go similarity index 98% rename from internal/bridge/user_credentials_test.go rename to internal/users/user_credentials_test.go index 2da9904e..89415d3a 100644 --- a/internal/bridge/user_credentials_test.go +++ b/internal/users/user_credentials_test.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -package bridge +package users import ( "testing" @@ -213,7 +213,7 @@ func TestCheckBridgeLoginLoggedOut(t *testing.T) { err = user.CheckBridgeLogin(testCredentialsDisconnected.BridgePassword) 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) { diff --git a/internal/bridge/user_new_test.go b/internal/users/user_new_test.go similarity index 97% rename from internal/bridge/user_new_test.go rename to internal/users/user_new_test.go index 5606dacc..6a7331b7 100644 --- a/internal/bridge/user_new_test.go +++ b/internal/users/user_new_test.go @@ -15,14 +15,14 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -package bridge +package users import ( "errors" "testing" - "github.com/ProtonMail/proton-bridge/internal/bridge/credentials" "github.com/ProtonMail/proton-bridge/internal/events" + "github.com/ProtonMail/proton-bridge/internal/users/credentials" "github.com/ProtonMail/proton-bridge/pkg/pmapi" gomock "github.com/golang/mock/gomock" a "github.com/stretchr/testify/assert" @@ -38,7 +38,7 @@ func TestNewUserNoCredentialsStore(t *testing.T) { a.Error(t, err) } -func TestNewUserBridgeOutdated(t *testing.T) { +func TestNewUserAppOutdated(t *testing.T) { m := initMocks(t) defer m.ctrl.Finish() diff --git a/internal/bridge/user_test.go b/internal/users/user_test.go similarity index 99% rename from internal/bridge/user_test.go rename to internal/users/user_test.go index 1c17f529..0367b02e 100644 --- a/internal/bridge/user_test.go +++ b/internal/users/user_test.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -package bridge +package users import ( "testing" diff --git a/internal/users/users.go b/internal/users/users.go new file mode 100644 index 00000000..9bab233f --- /dev/null +++ b/internal/users/users.go @@ -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 . + +// 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!") + } +} diff --git a/internal/bridge/bridge_users_test.go b/internal/users/users_actions_test.go similarity index 77% rename from internal/bridge/bridge_users_test.go rename to internal/users/users_actions_test.go index 783dad26..4bd1a138 100644 --- a/internal/bridge/bridge_users_test.go +++ b/internal/users/users_actions_test.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -package bridge +package users import ( "errors" @@ -33,7 +33,7 @@ func TestGetNoUser(t *testing.T) { m.clientManager.EXPECT().GetClient("user").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) { @@ -43,8 +43,8 @@ func TestGetUserByID(t *testing.T) { m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1) m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1) - checkBridgeGetUser(t, m, "user", 0, "") - checkBridgeGetUser(t, m, "users", 1, "") + checkUsersGetUser(t, m, "user", 0, "") + checkUsersGetUser(t, m, "users", 1, "") } 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("users").Return(m.pmapiClient).MinTimes(1) - checkBridgeGetUser(t, m, "username", 0, "") - checkBridgeGetUser(t, m, "usersname", 1, "") + checkUsersGetUser(t, m, "username", 0, "") + checkUsersGetUser(t, m, "usersname", 1, "") } 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("users").Return(m.pmapiClient).MinTimes(1) - checkBridgeGetUser(t, m, "user@pm.me", 0, "") - checkBridgeGetUser(t, m, "users@pm.me", 1, "") - checkBridgeGetUser(t, m, "anotheruser@pm.me", 1, "") - checkBridgeGetUser(t, m, "alsouser@pm.me", 1, "") + checkUsersGetUser(t, m, "user@pm.me", 0, "") + checkUsersGetUser(t, m, "users@pm.me", 1, "") + checkUsersGetUser(t, m, "anotheruser@pm.me", 1, "") + checkUsersGetUser(t, m, "alsouser@pm.me", 1, "") } 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("users").Return(m.pmapiClient).MinTimes(1) - bridge := testNewBridgeWithUsers(t, m) - defer cleanUpBridgeUserData(bridge) + users := testNewUsersWithUsers(t, m) + defer cleanUpUsersData(users) gomock.InOrder( m.pmapiClient.EXPECT().Logout().Return(), @@ -90,9 +90,9 @@ func TestDeleteUser(t *testing.T) { m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me") - err := bridge.DeleteUser("user", true) + err := users.DeleteUser("user", true) 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. @@ -103,8 +103,8 @@ func TestDeleteUserWithFailingLogout(t *testing.T) { m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1) m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1) - bridge := testNewBridgeWithUsers(t, m) - defer cleanUpBridgeUserData(bridge) + users := testNewUsersWithUsers(t, m) + defer cleanUpUsersData(users) gomock.InOrder( m.pmapiClient.EXPECT().Logout().Return(), @@ -116,16 +116,16 @@ func TestDeleteUserWithFailingLogout(t *testing.T) { m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me") - err := bridge.DeleteUser("user", true) + err := users.DeleteUser("user", true) 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) { - bridge := testNewBridgeWithUsers(t, m) - defer cleanUpBridgeUserData(bridge) +func checkUsersGetUser(t *testing.T, m mocks, query string, index int, expectedError string) { + users := testNewUsersWithUsers(t, m) + defer cleanUpUsersData(users) - user, err := bridge.GetUser(query) + user, err := users.GetUser(query) waitForEvents() if expectedError != "" { @@ -136,7 +136,7 @@ func checkBridgeGetUser(t *testing.T, m mocks, query string, index int, expected var expectedUser *User if index >= 0 { - expectedUser = bridge.users[index] + expectedUser = users.users[index] } assert.Equal(m.t, expectedUser, user) diff --git a/internal/bridge/bridge_login_test.go b/internal/users/users_login_test.go similarity index 82% rename from internal/bridge/bridge_login_test.go rename to internal/users/users_login_test.go index 65f73c3e..b16d50d1 100644 --- a/internal/bridge/bridge_login_test.go +++ b/internal/users/users_login_test.go @@ -15,27 +15,27 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -package bridge +package users import ( "errors" "testing" - "github.com/ProtonMail/proton-bridge/internal/bridge/credentials" "github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/internal/metrics" + "github.com/ProtonMail/proton-bridge/internal/users/credentials" "github.com/ProtonMail/proton-bridge/pkg/pmapi" gomock "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) -func TestBridgeFinishLoginBadMailboxPassword(t *testing.T) { +func TestUsersFinishLoginBadMailboxPassword(t *testing.T) { m := initMocks(t) defer m.ctrl.Finish() err := errors.New("bad password") gomock.InOrder( - // Init bridge with no user from keychain. + // Init users with no user from keychain. m.credentialsStore.EXPECT().List().Return([]string{}, nil), // Set up mocks for FinishLogin. @@ -44,16 +44,16 @@ func TestBridgeFinishLoginBadMailboxPassword(t *testing.T) { 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) defer m.ctrl.Finish() err := errors.New("Cannot logout when upgrade needed") gomock.InOrder( - // Init bridge with no user from keychain. + // Init users with no user from keychain. m.credentialsStore.EXPECT().List().Return([]string{}, nil), // Set up mocks for FinishLogin. @@ -64,7 +64,7 @@ func TestBridgeFinishLoginUpgradeApplication(t *testing.T) { 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 { @@ -81,7 +81,7 @@ func credentialsWithToken(token string) *credentials.Credentials { return tmp } -func TestBridgeFinishLoginNewUser(t *testing.T) { +func TestUsersFinishLoginNewUser(t *testing.T) { m := initMocks(t) defer m.ctrl.Finish() @@ -89,7 +89,7 @@ func TestBridgeFinishLoginNewUser(t *testing.T) { m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1) gomock.InOrder( - // bridge.New() finds no users in keychain. + // users.New() finds no users in keychain. m.credentialsStore.EXPECT().List().Return([]string{}, nil), // getAPIUser() loads user info from API (e.g. userID). @@ -128,12 +128,12 @@ func TestBridgeFinishLoginNewUser(t *testing.T) { mockEventLoopNoAction(m) - user := checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil) + user := checkUsersFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil) mockAuthUpdate(user, "afterCredentials", m) } -func TestBridgeFinishLoginExistingDisconnectedUser(t *testing.T) { +func TestUsersFinishLoginExistingDisconnectedUser(t *testing.T) { m := initMocks(t) defer m.ctrl.Finish() @@ -144,7 +144,7 @@ func TestBridgeFinishLoginExistingDisconnectedUser(t *testing.T) { m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1) 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), // newUser() @@ -186,12 +186,12 @@ func TestBridgeFinishLoginExistingDisconnectedUser(t *testing.T) { mockEventLoopNoAction(m) - user := checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil) + user := checkUsersFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil) mockAuthUpdate(user, "afterCredentials", m) } -func TestBridgeFinishLoginConnectedUser(t *testing.T) { +func TestUsersFinishLoginConnectedUser(t *testing.T) { m := initMocks(t) defer m.ctrl.Finish() @@ -201,8 +201,8 @@ func TestBridgeFinishLoginConnectedUser(t *testing.T) { mockConnectedUser(m) mockEventLoopNoAction(m) - bridge := testNewBridge(t, m) - defer cleanUpBridgeUserData(bridge) + users := testNewUsers(t, m) + defer cleanUpUsersData(users) // Then, try to log in again... gomock.InOrder( @@ -212,15 +212,15 @@ func TestBridgeFinishLoginConnectedUser(t *testing.T) { 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()) } -func checkBridgeFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassword string, expectedUserID string, expectedErr error) *User { - bridge := testNewBridge(t, m) - defer cleanUpBridgeUserData(bridge) +func checkUsersFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassword string, expectedUserID string, expectedErr error) *User { + users := testNewUsers(t, m) + defer cleanUpUsersData(users) - user, err := bridge.FinishLogin(m.pmapiClient, auth, mailboxPassword) + user, err := users.FinishLogin(m.pmapiClient, auth, mailboxPassword) waitForEvents() @@ -228,11 +228,11 @@ func checkBridgeFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPass if expectedUserID != "" { assert.Equal(t, expectedUserID, user.ID()) - assert.Equal(t, 1, len(bridge.users)) - assert.Equal(t, expectedUserID, bridge.users[0].ID()) + assert.Equal(t, 1, len(users.users)) + assert.Equal(t, expectedUserID, users.users[0].ID()) } else { assert.Equal(t, (*User)(nil), user) - assert.Equal(t, 0, len(bridge.users)) + assert.Equal(t, 0, len(users.users)) } return user diff --git a/internal/bridge/bridge_new_test.go b/internal/users/users_new_test.go similarity index 78% rename from internal/bridge/bridge_new_test.go rename to internal/users/users_new_test.go index 67aae3f9..2dceb5ba 100644 --- a/internal/bridge/bridge_new_test.go +++ b/internal/users/users_new_test.go @@ -15,40 +15,40 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -package bridge +package users import ( "errors" "testing" - credentials "github.com/ProtonMail/proton-bridge/internal/bridge/credentials" "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/users/credentials" "github.com/ProtonMail/proton-bridge/pkg/pmapi" gomock "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) -func TestNewBridgeNoKeychain(t *testing.T) { +func TestNewUsersNoKeychain(t *testing.T) { m := initMocks(t) defer m.ctrl.Finish() 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) defer m.ctrl.Finish() 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) defer m.ctrl.Finish() @@ -63,10 +63,10 @@ func TestNewBridgeWithDisconnectedUser(t *testing.T) { 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) defer m.ctrl.Finish() @@ -85,7 +85,7 @@ func TestNewBridgeWithConnectedUserWithBadToken(t *testing.T) { m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil) 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) { @@ -105,9 +105,9 @@ func mockConnectedUser(m mocks) { ) } -// mockAuthUpdate simulates bridge calling UpdateAuthToken on the given user. -// This would normally be done by Bridge when it receives an auth from the ClientManager, -// but as we don't have a full bridge instance here, we do this manually. +// mockAuthUpdate simulates users calling UpdateAuthToken on the given user. +// This would normally be done by users when it receives an auth from the ClientManager, +// but as we don't have a full users instance here, we do this manually. func mockAuthUpdate(user *User, token string, m mocks) { gomock.InOrder( m.credentialsStore.EXPECT().UpdateToken("user", ":"+token).Return(nil), @@ -119,7 +119,7 @@ func mockAuthUpdate(user *User, token string, m mocks) { waitForEvents() } -func TestNewBridgeWithConnectedUser(t *testing.T) { +func TestNewUsersWithConnectedUser(t *testing.T) { m := initMocks(t) defer m.ctrl.Finish() @@ -129,12 +129,12 @@ func TestNewBridgeWithConnectedUser(t *testing.T) { mockConnectedUser(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 -// credentials store is kept also in array of Bridge users. -func TestNewBridgeWithUsers(t *testing.T) { +// credentials store is kept also in array of users. +func TestNewUsersWithUsers(t *testing.T) { m := initMocks(t) defer m.ctrl.Finish() @@ -155,10 +155,10 @@ func TestNewBridgeWithUsers(t *testing.T) { 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) defer m.ctrl.Finish() @@ -170,17 +170,17 @@ func TestNewBridgeFirstStart(t *testing.T) { m.pmapiClient.EXPECT().Logout(), ) - testNewBridge(t, m) + testNewUsers(t, m) } -func checkBridgeNew(t *testing.T, m mocks, expectedCredentials []*credentials.Credentials) { - bridge := testNewBridge(t, m) - defer cleanUpBridgeUserData(bridge) +func checkUsersNew(t *testing.T, m mocks, expectedCredentials []*credentials.Credentials) { + users := testNewUsers(t, m) + defer cleanUpUsersData(users) - assert.Equal(m.t, len(expectedCredentials), len(bridge.GetUsers())) + assert.Equal(m.t, len(expectedCredentials), len(users.GetUsers())) credentials := []*credentials.Credentials{} - for _, user := range bridge.users { + for _, user := range users.users { credentials = append(credentials, user.creds) } diff --git a/internal/bridge/bridge_test.go b/internal/users/users_test.go similarity index 88% rename from internal/bridge/bridge_test.go rename to internal/users/users_test.go index b23700ae..6709d79e 100644 --- a/internal/bridge/bridge_test.go +++ b/internal/users/users_test.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -package bridge +package users import ( "fmt" @@ -25,12 +25,12 @@ import ( "testing" "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/metrics" "github.com/ProtonMail/proton-bridge/internal/preferences" "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" pmapimocks "github.com/ProtonMail/proton-bridge/pkg/pmapi/mocks" gomock "github.com/golang/mock/gomock" @@ -131,11 +131,11 @@ type mocks struct { t *testing.T ctrl *gomock.Controller - config *bridgemocks.MockConfiger - PanicHandler *bridgemocks.MockPanicHandler - prefProvider *bridgemocks.MockPreferenceProvider - clientManager *bridgemocks.MockClientManager - credentialsStore *bridgemocks.MockCredentialsStorer + config *usersmocks.MockConfiger + PanicHandler *usersmocks.MockPanicHandler + prefProvider *usersmocks.MockPreferenceProvider + clientManager *usersmocks.MockClientManager + credentialsStore *usersmocks.MockCredentialsStorer eventListener *MockListener pmapiClient *pmapimocks.MockClient @@ -172,11 +172,11 @@ func initMocks(t *testing.T) mocks { t: t, ctrl: mockCtrl, - config: bridgemocks.NewMockConfiger(mockCtrl), - PanicHandler: bridgemocks.NewMockPanicHandler(mockCtrl), - prefProvider: bridgemocks.NewMockPreferenceProvider(mockCtrl), - clientManager: bridgemocks.NewMockClientManager(mockCtrl), - credentialsStore: bridgemocks.NewMockCredentialsStorer(mockCtrl), + config: usersmocks.NewMockConfiger(mockCtrl), + PanicHandler: usersmocks.NewMockPanicHandler(mockCtrl), + prefProvider: usersmocks.NewMockPreferenceProvider(mockCtrl), + clientManager: usersmocks.NewMockClientManager(mockCtrl), + credentialsStore: usersmocks.NewMockCredentialsStorer(mockCtrl), eventListener: NewMockListener(mockCtrl), pmapiClient: pmapimocks.NewMockClient(mockCtrl), @@ -195,7 +195,7 @@ func initMocks(t *testing.T) mocks { return m } -func testNewBridgeWithUsers(t *testing.T, m mocks) *Bridge { +func testNewUsersWithUsers(t *testing.T, m mocks) *Users { // Events are asynchronous m.pmapiClient.EXPECT().GetEvent("").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), ) - bridge := testNewBridge(t, m) + users := testNewUsers(t, m) - user, _ := bridge.GetUser("user") + user, _ := users.GetUser("user") mockAuthUpdate(user, "reftok", m) - users, _ := bridge.GetUser("user") - mockAuthUpdate(users, "reftok", m) + user, _ = users.GetUser("user") + 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") 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.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() - return bridge + return users } -func cleanUpBridgeUserData(b *Bridge) { +func cleanUpUsersData(b *Users) { for _, user := range b.users { _ = 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("users").Return(m.pmapiClient).MinTimes(1) - bridge := testNewBridgeWithUsers(t, m) - defer cleanUpBridgeUserData(bridge) + users := testNewUsersWithUsers(t, m) + defer cleanUpUsersData(users) m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@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) - require.NoError(t, bridge.ClearData()) + require.NoError(t, users.ClearData()) waitForEvents() } diff --git a/internal/bridge/bridge_test_exports.go b/internal/users/users_test_exports.go similarity index 98% rename from internal/bridge/bridge_test_exports.go rename to internal/users/users_test_exports.go index 9a81b887..d0d865a1 100644 --- a/internal/bridge/bridge_test_exports.go +++ b/internal/users/users_test_exports.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -package bridge +package users // IsAuthorized returns whether the user has received an Auth from the API yet. func (u *User) IsAuthorized() bool { diff --git a/test/context/bridge.go b/test/context/bridge.go index 8c9214dd..a444b28f 100644 --- a/test/context/bridge.go +++ b/test/context/bridge.go @@ -20,6 +20,7 @@ package context import ( "github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/preferences" + "github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/pkg/listener" ) @@ -57,9 +58,9 @@ func (ctx *TestContext) RestartBridge() error { func newBridgeInstance( t *bddT, cfg *fakeConfig, - credStore bridge.CredentialsStorer, + credStore users.CredentialsStorer, eventListener listener.Listener, - clientManager bridge.ClientManager, + clientManager users.ClientManager, ) *bridge.Bridge { panicHandler := &panicHandler{t: t} pref := preferences.New(cfg) diff --git a/test/context/bridge_user.go b/test/context/bridge_user.go index e07e1d2b..cdfcbd02 100644 --- a/test/context/bridge_user.go +++ b/test/context/bridge_user.go @@ -23,8 +23,8 @@ import ( "path/filepath" "time" - "github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/store" + "github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/pkg/srp" "github.com/pkg/errors" "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. -func (ctx *TestContext) GetUser(username string) (*bridge.User, error) { +func (ctx *TestContext) GetUser(username string) (*users.User, error) { return ctx.bridge.GetUser(username) } diff --git a/test/context/context.go b/test/context/context.go index 32530724..e6db35c7 100644 --- a/test/context/context.go +++ b/test/context/context.go @@ -20,6 +20,7 @@ package context import ( "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/pmapi" "github.com/ProtonMail/proton-bridge/test/accounts" @@ -48,7 +49,7 @@ type TestContext struct { // Bridge core related variables. bridge *bridge.Bridge bridgeLastError error - credStore bridge.CredentialsStorer + credStore users.CredentialsStorer // IMAP related variables. imapAddr string diff --git a/test/context/credentials.go b/test/context/credentials.go index b9527d92..78fb8c72 100644 --- a/test/context/credentials.go +++ b/test/context/credentials.go @@ -20,7 +20,7 @@ package context import ( "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. diff --git a/test/features/imap/auth.feature b/test/features/imap/auth.feature index 4ea83b8b..94ebdc2f 100644 --- a/test/features/imap/auth.feature +++ b/test/features/imap/auth.feature @@ -12,7 +12,7 @@ Feature: IMAP auth Scenario: Authenticates with disconnected user Given there is disconnected user "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 Given there is connected user "user" @@ -31,13 +31,13 @@ Feature: IMAP auth Given there is connected user "user" When "user" logs out from bridge 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 Given there is connected user "user" When "user" logs out from bridge 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 And IMAP client authenticates "user" Then IMAP response is "OK" diff --git a/test/features/smtp/auth.feature b/test/features/smtp/auth.feature index b580e9e6..3933b4c1 100644 --- a/test/features/smtp/auth.feature +++ b/test/features/smtp/auth.feature @@ -19,7 +19,7 @@ Feature: SMTP auth Scenario: Authenticates with disconnected user Given there is disconnected user "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 When SMTP client authenticates with username "user@pm.me" and password "bridgepassword"