We build too many walls and not enough bridges

This commit is contained in:
Jakub
2020-04-08 12:59:16 +02:00
commit 17f4d6097a
494 changed files with 62753 additions and 0 deletions

510
internal/bridge/bridge.go Normal file
View File

@ -0,0 +1,510 @@
// 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 bridge provides core business logic providing API over credentials store and PM API.
package bridge
import (
"errors"
"strconv"
"strings"
"sync"
"time"
"github.com/ProtonMail/proton-bridge/internal/events"
m "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/config"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/hashicorp/go-multierror"
logrus "github.com/sirupsen/logrus"
)
var (
log = config.GetLogEntry("bridge") //nolint[gochecknoglobals]
isApplicationOutdated = false //nolint[gochecknoglobals]
)
// Bridge is a struct handling users.
type Bridge struct {
config Configer
pref PreferenceProvider
panicHandler PanicHandler
events listener.Listener
version string
pmapiClientFactory PMAPIProviderFactory
credStorer CredentialsStorer
storeCache *store.Cache
// 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 interface{}
lock sync.RWMutex
userAgentClientName string
userAgentClientVersion string
userAgentOS string
}
func New(
config Configer,
pref PreferenceProvider,
panicHandler PanicHandler,
eventListener listener.Listener,
version string,
pmapiClientFactory PMAPIProviderFactory,
credStorer CredentialsStorer,
) *Bridge {
log.Trace("Creating new bridge")
b := &Bridge{
config: config,
pref: pref,
panicHandler: panicHandler,
events: eventListener,
version: version,
pmapiClientFactory: pmapiClientFactory,
credStorer: credStorer,
storeCache: store.NewCache(config.GetIMAPCachePath()),
idleUpdates: make(chan interface{}),
lock: sync.RWMutex{},
}
// 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) {
AllowDoH()
}
go func() {
defer panicHandler.HandlePanic()
b.watchBridgeOutdated()
}()
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(m.New(m.Setup, m.FirstStart, m.Label(version)))
}
go b.heartbeat()
return b
}
// heartbeat sends a heartbeat signal once a day.
func (b *Bridge) heartbeat() {
for range time.NewTicker(1 * time.Hour).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(m.New(m.Heartbeat, m.Daily, m.NoLabel))
nextTime = nextTime.Add(24 * time.Hour)
b.pref.Set(preferences.NextHeartbeatKey, strconv.FormatInt(nextTime.Unix(), 10))
}
}
}
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)
apiClient := b.pmapiClientFactory(userID)
user, newUserErr := newUser(b.panicHandler, userID, b.events, b.credStorer, apiClient, 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, apiClient); 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 range ch {
isApplicationOutdated = true
b.closeAllConnections()
}
}
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) (loginClient PMAPIProvider, auth *pmapi.Auth, err error) {
log.WithField("username", username).Trace("Logging in to bridge")
b.crashBandicoot(username)
// We need to use "login" client because we need userID to properly
// assign access tokens into token manager.
loginClient = b.pmapiClientFactory("login")
authInfo, err := loginClient.AuthInfo(username)
if err != nil {
log.WithField("username", username).WithError(err).Error("Could not get auth info for user")
return nil, nil, err
}
if auth, err = loginClient.Auth(username, password, authInfo); err != nil {
log.WithField("username", username).WithError(err).Error("Could not get auth for user")
return loginClient, auth, err
}
return loginClient, auth, nil
}
// 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(loginClient PMAPIProvider, auth *pmapi.Auth, mbPassword string) (user *User, err error) { //nolint[funlen]
log.Trace("Finishing bridge login")
defer func() {
if err == pmapi.ErrUpgradeApplication {
b.events.Emit(events.UpgradeApplicationEvent, "")
}
}()
b.lock.Lock()
defer b.lock.Unlock()
mbPassword, err = pmapi.HashMailboxPassword(mbPassword, auth.KeySalt)
if err != nil {
log.WithError(err).Error("Could not hash mailbox password")
if logoutErr := loginClient.Logout(); logoutErr != nil {
log.WithError(logoutErr).Error("Clean login session after hash password failed.")
}
return
}
if _, err = loginClient.Unlock(mbPassword); err != nil {
log.WithError(err).Error("Could not decrypt keyring")
if logoutErr := loginClient.Logout(); logoutErr != nil {
log.WithError(logoutErr).Error("Clean login session after unlock failed.")
}
return
}
apiUser, err := loginClient.CurrentUser()
if err != nil {
log.WithError(err).Error("Could not get login API user")
if logoutErr := loginClient.Logout(); logoutErr != nil {
log.WithError(logoutErr).Error("Clean login session after get current user failed.")
}
return
}
user, hasUser := b.hasUser(apiUser.ID)
// If the user exists and is logged in, we don't want to do anything.
if hasUser && user.IsConnected() {
err = errors.New("user is already logged in")
log.WithError(err).Warn("User is already logged in")
if logoutErr := loginClient.Logout(); logoutErr != nil {
log.WithError(logoutErr).Warn("Could not discard auth generated during second login")
}
return
}
apiToken := auth.UID() + ":" + auth.RefreshToken
apiClient := b.pmapiClientFactory(apiUser.ID)
auth, err = apiClient.AuthRefresh(apiToken)
if err != nil {
log.WithError(err).Error("Could refresh token in new client")
if logoutErr := loginClient.Logout(); logoutErr != nil {
log.WithError(logoutErr).Warn("Could not discard auth generated after auth refresh")
}
return
}
// We load the current user again because it should now have addresses loaded.
apiUser, err = apiClient.CurrentUser()
if err != nil {
log.WithError(err).Error("Could not get current API user")
if logoutErr := loginClient.Logout(); logoutErr != nil {
log.WithError(logoutErr).Error("Clean login session after get current user failed.")
}
return
}
apiToken = auth.UID() + ":" + auth.RefreshToken
activeEmails := apiClient.Addresses().ActiveEmails()
if _, err = b.credStorer.Add(apiUser.ID, apiUser.Name, apiToken, mbPassword, activeEmails); err != nil {
log.WithError(err).Error("Could not add user to credentials store")
return
}
// If it's a new user, generate the user object.
if !hasUser {
user, err = newUser(b.panicHandler, apiUser.ID, b.events, b.credStorer, apiClient, b.storeCache, b.config.GetDBDir())
if err != nil {
log.WithField("user", apiUser.ID).WithError(err).Error("Could not create user")
return
}
}
// Set up the user auth and store (which we do for both new and existing users).
if err = user.init(b.idleUpdates, apiClient); err != nil {
log.WithField("user", user.userID).WithError(err).Error("Could not initialise user")
return
}
if !hasUser {
b.users = append(b.users, user)
b.SendMetric(m.New(m.Setup, m.NewUser, m.NoLabel))
}
b.events.Emit(events.UserRefreshEvent, apiUser.ID)
return user, err
}
// 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 {
apiClient := b.pmapiClientFactory("bug_reporter")
title := "[Bridge] Bug"
err := apiClient.ReportBugWithEmailClient(
osType,
osVersion,
title,
description,
accountName,
address,
emailClient,
)
if 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 m.Metric) {
apiClient := b.pmapiClientFactory("metric_reporter")
cat, act, lab := m.Get()
err := apiClient.SendSimpleMetric(string(cat), string(act), string(lab))
if 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).
func (b *Bridge) GetCurrentClient() string {
res := b.userAgentClientName
if b.userAgentClientVersion != "" {
res = res + " " + b.userAgentClientVersion
}
return res
}
// SetCurrentClient updates client info (e.g. Thunderbird) and sets the user agent
// on pmapi. By default no client is used, IMAP has to detect it on first login.
func (b *Bridge) SetCurrentClient(clientName, clientVersion string) {
b.userAgentClientName = clientName
b.userAgentClientVersion = clientVersion
b.updateCurrentUserAgent()
}
// SetCurrentOS updates OS and sets the user agent on pmapi. By default we use
// `runtime.GOOS`, but this can be overridden in case of better detection.
func (b *Bridge) SetCurrentOS(os string) {
b.userAgentOS = os
b.updateCurrentUserAgent()
}
// GetIMAPUpdatesChannel sets the channel on which idle events should be sent.
func (b *Bridge) GetIMAPUpdatesChannel() chan interface{} {
if b.idleUpdates == nil {
log.Warn("Bridge updates channel is nil")
}
return b.idleUpdates
}
// AllowDoH 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 AllowDoH() {
pmapi.GlobalAllowDoH()
}
// DisallowDoH 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 DisallowDoH() {
pmapi.GlobalDisallowDoH()
}
func (b *Bridge) updateCurrentUserAgent() {
UpdateCurrentUserAgent(b.version, b.userAgentOS, b.userAgentClientName, b.userAgentClientVersion)
}
// 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!")
}
}

View File

@ -0,0 +1,233 @@
// 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 bridge
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/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
)
func TestBridgeFinishLoginBadPassword(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
// Init bridge with no user from keychain.
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
// Set up mocks for FinishLogin.
err := errors.New("bad password")
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, err)
m.pmapiClient.EXPECT().Logout().Return(nil)
checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "", err)
}
func TestBridgeFinishLoginUpgradeApplication(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
// Init bridge with no user from keychain.
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
// Set up mocks for FinishLogin.
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, pmapi.ErrUpgradeApplication)
m.eventListener.EXPECT().Emit(events.UpgradeApplicationEvent, "")
err := errors.New("Cannot logout when upgrade needed")
m.pmapiClient.EXPECT().Logout().Return(err)
checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "", pmapi.ErrUpgradeApplication)
}
func refreshWithToken(token string) *pmapi.Auth {
return &pmapi.Auth{
RefreshToken: token,
KeySalt: "", // No salting in tests.
}
}
func credentialsWithToken(token string) *credentials.Credentials {
tmp := &credentials.Credentials{}
*tmp = *testCredentials
tmp.APIToken = token
return tmp
}
func TestBridgeFinishLoginNewUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
// Bridge finds no users in the keychain.
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
// Get user to be able to setup new client with proper userID.
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil)
// Setup of new client.
m.pmapiClient.EXPECT().AuthRefresh(":tok").Return(refreshWithToken("afterLogin"), nil)
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
// Set up mocks for authorising the new user (in user.init).
m.credentialsStore.EXPECT().Add("user", "username", ":afterLogin", testCredentials.MailboxPassword, []string{testPMAPIAddress.Email})
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken(":afterLogin"), nil).Times(2)
m.pmapiClient.EXPECT().AuthRefresh(":afterLogin").Return(refreshWithToken("afterCredentials"), nil)
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken("afterCredentials"), nil)
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil)
m.credentialsStore.EXPECT().UpdateToken("user", ":afterCredentials").Return(nil)
// Set up mocks for creating the user's store (in store.New).
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
// Emit event for new user and send metrics.
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
m.pmapiClient.EXPECT().SendSimpleMetric(string(metrics.Setup), string(metrics.NewUser), string(metrics.NoLabel))
// Set up mocks for starting the store's event loop (in store.New).
// The event loop runs in another goroutine so this might happen at any time.
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
// Set up mocks for performing the initial store sync.
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil)
}
func TestBridgeFinishLoginExistingUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
loggedOutCreds := *testCredentials
loggedOutCreds.APIToken = ""
loggedOutCreds.MailboxPassword = ""
// Bridge finds one logged out user in the keychain.
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
// New user
m.credentialsStore.EXPECT().Get("user").Return(&loggedOutCreds, nil)
// Init user
m.credentialsStore.EXPECT().Get("user").Return(&loggedOutCreds, nil)
m.pmapiClient.EXPECT().ListLabels().Return(nil, pmapi.ErrInvalidToken)
m.pmapiClient.EXPECT().Addresses().Return(nil)
// Get user to be able to setup new client with proper userID.
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil)
// Setup of new client.
m.pmapiClient.EXPECT().AuthRefresh(":tok").Return(refreshWithToken("afterLogin"), nil)
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
// Set up mocks for authorising the new user (in user.init).
m.credentialsStore.EXPECT().Add("user", "username", ":afterLogin", testCredentials.MailboxPassword, []string{testPMAPIAddress.Email})
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken(":afterLogin"), nil)
m.pmapiClient.EXPECT().AuthRefresh(":afterLogin").Return(refreshWithToken("afterCredentials"), nil)
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken("afterCredentials"), nil)
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil)
m.credentialsStore.EXPECT().UpdateToken("user", ":afterCredentials").Return(nil)
// Set up mocks for creating the user's store (in store.New).
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
// Reload account list in GUI.
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
// Set up mocks for starting the store's event loop (in store.New)
// The event loop runs in another goroutine so this might happen at any time.
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
// Set up mocks for performing the initial store sync.
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
checkBridgeFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil)
}
func TestBridgeDoubleLogin(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
// Firstly, start bridge with existing user...
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil)
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
bridge := testNewBridge(t, m)
defer cleanUpBridgeUserData(bridge)
// Then, try to log in again...
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil)
m.pmapiClient.EXPECT().Logout()
_, err := bridge.FinishLogin(m.pmapiClient, testAuth, testCredentials.MailboxPassword)
assert.Equal(t, "user is already logged in", err.Error())
}
func checkBridgeFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassword string, expectedUserID string, expectedErr error) {
bridge := testNewBridge(t, m)
defer cleanUpBridgeUserData(bridge)
user, err := bridge.FinishLogin(m.pmapiClient, auth, mailboxPassword)
waitForEvents()
assert.Equal(t, expectedErr, err)
if expectedUserID != "" {
assert.Equal(t, expectedUserID, user.ID())
assert.Equal(t, 1, len(bridge.users))
assert.Equal(t, expectedUserID, bridge.users[0].ID())
} else {
assert.Equal(t, (*User)(nil), user)
assert.Equal(t, 0, len(bridge.users))
}
}

View File

@ -0,0 +1,162 @@
// 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 bridge
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/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
)
func TestNewBridgeNoKeychain(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{})
}
func TestNewBridgeWithoutUsersInCredentialsStore(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
checkBridgeNew(t, m, []*credentials.Credentials{})
}
func TestNewBridgeWithDisconnectedUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil).Times(2)
m.pmapiClient.EXPECT().ListLabels().Return(nil, errors.New("ErrUnauthorized"))
m.pmapiClient.EXPECT().Addresses().Return(nil)
checkBridgeNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
}
func TestNewBridgeWithConnectedUserWithBadToken(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.pmapiClient.EXPECT().AuthRefresh("token").Return(nil, errors.New("bad token"))
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
m.pmapiClient.EXPECT().Logout().Return(nil)
m.pmapiClient.EXPECT().SetAuths(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
checkBridgeNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
}
func TestNewBridgeWithConnectedUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil)
// Set up mocks for store initialisation for the authorized user.
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
checkBridgeNew(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) {
m := initMocks(t)
defer m.ctrl.Finish()
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.pmapiClient.EXPECT().Unlock(testCredentials.MailboxPassword).Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil)
m.credentialsStore.EXPECT().List().Return([]string{"user", "user"}, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil).Times(2)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
// Set up mocks for store initialisation for the unauth user.
m.pmapiClient.EXPECT().ListLabels().Return(nil, errors.New("ErrUnauthorized"))
m.pmapiClient.EXPECT().Addresses().Return(nil)
// Set up mocks for store initialisation for the authorized user.
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
checkBridgeNew(t, m, []*credentials.Credentials{testCredentialsDisconnected, testCredentials})
}
func TestNewBridgeFirstStart(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.prefProvider.EXPECT().GetBool(preferences.FirstStartKey).Return(true)
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
m.pmapiClient.EXPECT().SendSimpleMetric(string(metrics.Setup), string(metrics.FirstStart), gomock.Any())
testNewBridge(t, m)
}
func checkBridgeNew(t *testing.T, m mocks, expectedCredentials []*credentials.Credentials) {
bridge := testNewBridge(t, m)
defer cleanUpBridgeUserData(bridge)
assert.Equal(m.t, len(expectedCredentials), len(bridge.GetUsers()))
credentials := []*credentials.Credentials{}
for _, user := range bridge.users {
credentials = append(credentials, user.creds)
}
assert.Equal(m.t, expectedCredentials, credentials)
}

View File

@ -0,0 +1,256 @@
// 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 bridge
import (
"io/ioutil"
"os"
"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/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
if os.Getenv("VERBOSITY") == "trace" {
logrus.SetLevel(logrus.TraceLevel)
}
os.Exit(m.Run())
}
var (
testAuth = &pmapi.Auth{ //nolint[gochecknoglobals]
RefreshToken: "tok",
KeySalt: "", // No salting in tests.
}
testAuthRefresh = &pmapi.Auth{ //nolint[gochecknoglobals]
RefreshToken: "reftok",
KeySalt: "", // No salting in tests.
}
testCredentials = &credentials.Credentials{ //nolint[gochecknoglobals]
UserID: "user",
Name: "username",
Emails: "user@pm.me",
APIToken: "token",
MailboxPassword: "pass",
BridgePassword: "0123456789abcdef",
Version: "v1",
Timestamp: 123456789,
IsHidden: false,
IsCombinedAddressMode: true,
}
testCredentialsSplit = &credentials.Credentials{ //nolint[gochecknoglobals]
UserID: "users",
Name: "usersname",
Emails: "users@pm.me;anotheruser@pm.me;alsouser@pm.me",
APIToken: "token",
MailboxPassword: "pass",
BridgePassword: "0123456789abcdef",
Version: "v1",
Timestamp: 123456789,
IsHidden: false,
IsCombinedAddressMode: false,
}
testCredentialsDisconnected = &credentials.Credentials{ //nolint[gochecknoglobals]
UserID: "user",
Name: "username",
Emails: "user@pm.me",
APIToken: "",
MailboxPassword: "",
BridgePassword: "0123456789abcdef",
Version: "v1",
Timestamp: 123456789,
IsHidden: false,
IsCombinedAddressMode: true,
}
testPMAPIUser = &pmapi.User{ //nolint[gochecknoglobals]
ID: "user",
Name: "username",
}
testPMAPIAddress = &pmapi.Address{ //nolint[gochecknoglobals]
ID: "testAddressID",
Type: pmapi.OriginalAddress,
Email: "user@pm.me",
Receive: pmapi.CanReceive,
}
testPMAPIAddresses = []*pmapi.Address{ //nolint[gochecknoglobals]
{ID: "usersAddress1ID", Email: "users@pm.me", Receive: pmapi.CanReceive, Type: pmapi.OriginalAddress},
{ID: "usersAddress2ID", Email: "anotheruser@pm.me", Receive: pmapi.CanReceive, Type: pmapi.AliasAddress},
{ID: "usersAddress3ID", Email: "alsouser@pm.me", Receive: pmapi.CanReceive, Type: pmapi.AliasAddress},
}
testPMAPIEvent = &pmapi.Event{ // nolint[gochecknoglobals]
EventID: "ACXDmTaBub14w==",
}
)
func waitForEvents() {
// Wait for goroutine to add listener.
// E.g. calling login to invoke firstsync event. Functions can end sooner than
// goroutines call the listener mock. We need to wait a little bit before the end of
// the test to capture all event calls. This allows us to detect whether there were
// missing calls, or perhaps whether something was called too many times.
time.Sleep(100 * time.Millisecond)
}
type mocks struct {
t *testing.T
ctrl *gomock.Controller
config *bridgemocks.MockConfiger
PanicHandler *bridgemocks.MockPanicHandler
prefProvider *bridgemocks.MockPreferenceProvider
pmapiClient *bridgemocks.MockPMAPIProvider
credentialsStore *bridgemocks.MockCredentialsStorer
eventListener *MockListener
storeCache *store.Cache
}
func initMocks(t *testing.T) mocks {
mockCtrl := gomock.NewController(t)
cacheFile, err := ioutil.TempFile("", "bridge-store-cache-*.db")
require.NoError(t, err, "could not get temporary file for store cache")
m := mocks{
t: t,
ctrl: mockCtrl,
config: bridgemocks.NewMockConfiger(mockCtrl),
PanicHandler: bridgemocks.NewMockPanicHandler(mockCtrl),
pmapiClient: bridgemocks.NewMockPMAPIProvider(mockCtrl),
prefProvider: bridgemocks.NewMockPreferenceProvider(mockCtrl),
credentialsStore: bridgemocks.NewMockCredentialsStorer(mockCtrl),
eventListener: NewMockListener(mockCtrl),
storeCache: store.NewCache(cacheFile.Name()),
}
// Ignore heartbeat calls because they always happen.
m.pmapiClient.EXPECT().SendSimpleMetric(string(metrics.Heartbeat), gomock.Any(), gomock.Any()).AnyTimes()
m.prefProvider.EXPECT().Get(preferences.NextHeartbeatKey).AnyTimes()
m.prefProvider.EXPECT().Set(preferences.NextHeartbeatKey, gomock.Any()).AnyTimes()
// Called during clean-up.
m.PanicHandler.EXPECT().HandlePanic().AnyTimes()
return m
}
func testNewBridgeWithUsers(t *testing.T, m mocks) *Bridge {
// Init for user.
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
// Init for users.
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return(testPMAPIAddresses)
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.credentialsStore.EXPECT().Get("users").Return(testCredentialsSplit, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("users", ":reftok").Return(nil)
m.credentialsStore.EXPECT().Get("users").Return(testCredentialsSplit, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
m.credentialsStore.EXPECT().List().Return([]string{"user", "users"}, nil)
return testNewBridge(t, m)
}
func testNewBridge(t *testing.T, m mocks) *Bridge {
cacheFile, err := ioutil.TempFile("", "bridge-store-cache-*.db")
require.NoError(t, err, "could not get temporary file for store cache")
m.prefProvider.EXPECT().GetBool(preferences.FirstStartKey).Return(false).AnyTimes()
m.prefProvider.EXPECT().GetBool(preferences.AllowProxyKey).Return(false).AnyTimes()
m.config.EXPECT().GetDBDir().Return("/tmp").AnyTimes()
m.config.EXPECT().GetIMAPCachePath().Return(cacheFile.Name()).AnyTimes()
m.pmapiClient.EXPECT().SetAuths(gomock.Any()).AnyTimes()
m.eventListener.EXPECT().Add(events.UpgradeApplicationEvent, gomock.Any())
pmapiClientFactory := func(userID string) PMAPIProvider {
log.WithField("userID", userID).Info("Creating new pmclient")
return m.pmapiClient
}
bridge := New(m.config, m.prefProvider, m.PanicHandler, m.eventListener, "ver", pmapiClientFactory, m.credentialsStore)
waitForEvents()
return bridge
}
func cleanUpBridgeUserData(b *Bridge) {
for _, user := range b.users {
_ = user.clearStore()
}
}
func TestClearData(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
bridge := testNewBridgeWithUsers(t, m)
defer cleanUpBridgeUserData(bridge)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "users@pm.me")
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "anotheruser@pm.me")
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "alsouser@pm.me")
m.pmapiClient.EXPECT().Logout()
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Logout()
m.credentialsStore.EXPECT().Logout("users").Return(nil)
m.credentialsStore.EXPECT().Get("users").Return(testCredentialsSplit, nil)
m.config.EXPECT().ClearData().Return(nil)
require.NoError(t, bridge.ClearData())
waitForEvents()
}

View File

@ -0,0 +1,121 @@
// 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 bridge
import (
"errors"
"testing"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/stretchr/testify/assert"
)
func TestGetNoUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
checkBridgeGetUser(t, m, "nouser", -1, "user nouser not found")
}
func TestGetUserByID(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
checkBridgeGetUser(t, m, "user", 0, "")
checkBridgeGetUser(t, m, "users", 1, "")
}
func TestGetUserByName(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
checkBridgeGetUser(t, m, "username", 0, "")
checkBridgeGetUser(t, m, "usersname", 1, "")
}
func TestGetUserByEmail(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
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, "")
}
func TestDeleteUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
bridge := testNewBridgeWithUsers(t, m)
defer cleanUpBridgeUserData(bridge)
m.pmapiClient.EXPECT().Logout().Return(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Delete("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
err := bridge.DeleteUser("user", true)
assert.NoError(t, err)
assert.Equal(t, 1, len(bridge.users))
}
// Even when logout fails, delete is done.
func TestDeleteUserWithFailingLogout(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
bridge := testNewBridgeWithUsers(t, m)
defer cleanUpBridgeUserData(bridge)
m.pmapiClient.EXPECT().Logout().Return(nil)
m.credentialsStore.EXPECT().Logout("user").Return(errors.New("logout failed"))
m.credentialsStore.EXPECT().Delete("user").Return(nil).Times(2)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
err := bridge.DeleteUser("user", true)
assert.NoError(t, err)
assert.Equal(t, 1, len(bridge.users))
}
func checkBridgeGetUser(t *testing.T, m mocks, query string, index int, expectedError string) {
bridge := testNewBridgeWithUsers(t, m)
defer cleanUpBridgeUserData(bridge)
user, err := bridge.GetUser(query)
waitForEvents()
if expectedError != "" {
assert.Equal(m.t, expectedError, err.Error())
} else {
assert.NoError(m.t, err)
}
var expectedUser *User
if index >= 0 {
expectedUser = bridge.users[index]
}
assert.Equal(m.t, expectedUser, user)
}

View File

@ -0,0 +1,23 @@
// 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 bridge
// Host settings.
const (
Host = "127.0.0.1"
)

View File

@ -0,0 +1,137 @@
// 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 credentials implements our struct stored in keychain.
// Store struct is kind of like a database client.
// Credentials struct is kind of like one record from the database.
package credentials
import (
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"strings"
"github.com/ProtonMail/proton-bridge/pkg/config"
"github.com/sirupsen/logrus"
)
const sep = "\x00"
var (
log = config.GetLogEntry("bridge") //nolint[gochecknoglobals]
ErrWrongFormat = errors.New("backend/creds: malformed password")
)
type Credentials struct {
UserID, // Do not marshal; used as a key.
Name,
Emails,
APIToken,
MailboxPassword,
BridgePassword,
Version string
Timestamp int64
IsHidden, // Deprecated.
IsCombinedAddressMode bool
}
func (s *Credentials) Marshal() string {
items := []string{
s.Name, // 0
s.Emails, // 1
s.APIToken, // 2
s.MailboxPassword, // 3
s.BridgePassword, // 4
s.Version, // 5
"", // 6
"", // 7
"", // 8
}
items[6] = fmt.Sprint(s.Timestamp)
if s.IsHidden {
items[7] = "1"
}
if s.IsCombinedAddressMode {
items[8] = "1"
}
str := strings.Join(items, sep)
return base64.StdEncoding.EncodeToString([]byte(str))
}
func (s *Credentials) Unmarshal(secret string) error {
b, err := base64.StdEncoding.DecodeString(secret)
if err != nil {
return err
}
items := strings.Split(string(b), sep)
if len(items) != 9 {
return ErrWrongFormat
}
s.Name = items[0]
s.Emails = items[1]
s.APIToken = items[2]
s.MailboxPassword = items[3]
s.BridgePassword = items[4]
s.Version = items[5]
if _, err = fmt.Sscan(items[6], &s.Timestamp); err != nil {
s.Timestamp = 0
}
if s.IsHidden = false; items[7] == "1" {
s.IsHidden = true
}
if s.IsCombinedAddressMode = false; items[8] == "1" {
s.IsCombinedAddressMode = true
}
return nil
}
func (s *Credentials) SetEmailList(list []string) {
s.Emails = strings.Join(list, ";")
}
func (s *Credentials) EmailList() []string {
return strings.Split(s.Emails, ";")
}
func (s *Credentials) CheckPassword(password string) error {
if subtle.ConstantTimeCompare([]byte(s.BridgePassword), []byte(password)) != 1 {
log.WithFields(logrus.Fields{
"userID": s.UserID,
}).Debug("Incorrect bridge password")
return fmt.Errorf("backend/credentials: incorrect password")
}
return nil
}
func (s *Credentials) Logout() {
s.APIToken = ""
s.MailboxPassword = ""
}
func (s *Credentials) IsConnected() bool {
return s.APIToken != "" && s.MailboxPassword != ""
}

View File

@ -0,0 +1,39 @@
// 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 credentials
import (
"crypto/rand"
"encoding/base64"
"io"
)
const keySize = 16
// generateKey generates a new random key.
func generateKey() []byte {
key := make([]byte, keySize)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
panic(err)
}
return key
}
func generatePassword() string {
return base64.RawURLEncoding.EncodeToString(generateKey())
}

View File

@ -0,0 +1,316 @@
// 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 credentials
import (
"errors"
"fmt"
"sort"
"sync"
"time"
"github.com/ProtonMail/proton-bridge/pkg/keychain"
"github.com/sirupsen/logrus"
)
var storeLocker = sync.RWMutex{} //nolint[gochecknoglobals]
// Store is an encrypted credentials store.
type Store struct {
secrets *keychain.Access
}
// NewStore creates a new encrypted credentials store.
func NewStore() (*Store, error) {
secrets, err := keychain.NewAccess("bridge")
return &Store{
secrets: secrets,
}, err
}
func (s *Store) Add(userID, userName, apiToken, mailboxPassword string, emails []string) (creds *Credentials, err error) {
storeLocker.Lock()
defer storeLocker.Unlock()
log.WithFields(logrus.Fields{
"user": userID,
"username": userName,
"emails": emails,
}).Trace("Adding new credentials")
if err = s.checkKeychain(); err != nil {
return
}
creds = &Credentials{
UserID: userID,
Name: userName,
APIToken: apiToken,
MailboxPassword: mailboxPassword,
IsHidden: false,
}
creds.SetEmailList(emails)
var has bool
if has, err = s.has(userID); err != nil {
log.WithField("userID", userID).WithError(err).Error("Could not check if user credentials already exist")
return
}
if has {
log.Info("Updating credentials of existing user")
currentCredentials, err := s.get(userID)
if err != nil {
return nil, err
}
creds.BridgePassword = currentCredentials.BridgePassword
creds.IsCombinedAddressMode = currentCredentials.IsCombinedAddressMode
creds.Timestamp = currentCredentials.Timestamp
} else {
log.Info("Generating credentials for new user")
creds.BridgePassword = generatePassword()
creds.IsCombinedAddressMode = true
creds.Timestamp = time.Now().Unix()
}
if err = s.saveCredentials(creds); err != nil {
return
}
return creds, err
}
func (s *Store) SwitchAddressMode(userID string) error {
storeLocker.Lock()
defer storeLocker.Unlock()
credentials, err := s.get(userID)
if err != nil {
return err
}
credentials.IsCombinedAddressMode = !credentials.IsCombinedAddressMode
credentials.BridgePassword = generatePassword()
return s.saveCredentials(credentials)
}
func (s *Store) UpdateEmails(userID string, emails []string) error {
storeLocker.Lock()
defer storeLocker.Unlock()
credentials, err := s.get(userID)
if err != nil {
return err
}
credentials.SetEmailList(emails)
return s.saveCredentials(credentials)
}
func (s *Store) UpdateToken(userID, apiToken string) error {
storeLocker.Lock()
defer storeLocker.Unlock()
credentials, err := s.get(userID)
if err != nil {
return err
}
credentials.APIToken = apiToken
return s.saveCredentials(credentials)
}
func (s *Store) Logout(userID string) error {
storeLocker.Lock()
defer storeLocker.Unlock()
credentials, err := s.get(userID)
if err != nil {
return err
}
credentials.Logout()
return s.saveCredentials(credentials)
}
// List returns a list of usernames that have credentials stored.
func (s *Store) List() (userIDs []string, err error) {
storeLocker.RLock()
defer storeLocker.RUnlock()
log.Trace("Listing credentials in credentials store")
if err = s.checkKeychain(); err != nil {
return
}
var allUserIDs []string
if allUserIDs, err = s.secrets.List(); err != nil {
log.WithError(err).Error("Could not list credentials")
return
}
credentialList := []*Credentials{}
for _, userID := range allUserIDs {
creds, getErr := s.get(userID)
if getErr != nil {
log.WithField("userID", userID).WithError(getErr).Warn("Failed to get credentials")
continue
}
if creds.Timestamp == 0 {
continue
}
credentialList = append(credentialList, creds)
}
sort.Slice(credentialList, func(i, j int) bool {
return credentialList[i].Timestamp < credentialList[j].Timestamp
})
for _, credentials := range credentialList {
userIDs = append(userIDs, credentials.UserID)
}
return userIDs, err
}
func (s *Store) GetAndCheckPassword(userID, password string) (creds *Credentials, err error) {
storeLocker.RLock()
defer storeLocker.RUnlock()
log.WithFields(logrus.Fields{
"userID": userID,
}).Debug("Checking bridge password")
credentials, err := s.Get(userID)
if err != nil {
return nil, err
}
if err := credentials.CheckPassword(password); err != nil {
log.WithFields(logrus.Fields{
"userID": userID,
"err": err,
}).Debug("Incorrect bridge password")
return nil, err
}
return credentials, nil
}
func (s *Store) Get(userID string) (creds *Credentials, err error) {
storeLocker.RLock()
defer storeLocker.RUnlock()
var has bool
if has, err = s.has(userID); err != nil {
log.WithError(err).Error("Could not check for credentials")
return
}
if !has {
err = errors.New("no credentials found for given userID")
return
}
return s.get(userID)
}
func (s *Store) has(userID string) (has bool, err error) {
if err = s.checkKeychain(); err != nil {
return
}
var ids []string
if ids, err = s.secrets.List(); err != nil {
log.WithError(err).Error("Could not list credentials")
return
}
for _, id := range ids {
if id == userID {
has = true
}
}
return
}
func (s *Store) get(userID string) (creds *Credentials, err error) {
log := log.WithField("user", userID)
if err = s.checkKeychain(); err != nil {
return
}
secret, err := s.secrets.Get(userID)
if err != nil {
log.WithError(err).Error("Could not get credentials from native keychain")
return
}
credentials := &Credentials{UserID: userID}
if err = credentials.Unmarshal(secret); err != nil {
err = fmt.Errorf("backend/credentials: malformed secret: %v", err)
_ = s.secrets.Delete(userID)
log.WithError(err).Error("Could not unmarshal secret")
return
}
return credentials, nil
}
// saveCredentials encrypts and saves password to the keychain store.
func (s *Store) saveCredentials(credentials *Credentials) (err error) {
if err = s.checkKeychain(); err != nil {
return
}
credentials.Version = keychain.KeychainVersion
return s.secrets.Put(credentials.UserID, credentials.Marshal())
}
func (s *Store) checkKeychain() (err error) {
if s.secrets == nil {
err = keychain.ErrNoKeychainInstalled
log.WithError(err).Error("Store is unusable")
}
return
}
// Delete removes credentials from the store.
func (s *Store) Delete(userID string) (err error) {
storeLocker.Lock()
defer storeLocker.Unlock()
if err = s.checkKeychain(); err != nil {
return
}
return s.secrets.Delete(userID)
}

View File

@ -0,0 +1,297 @@
// 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 credentials
import (
"bytes"
"encoding/base64"
"encoding/gob"
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const testSep = "\n"
const secretFormat = "%v" + testSep + // UserID,
"%v" + testSep + // Name,
"%v" + testSep + // Emails,
"%v" + testSep + // APIToken,
"%v" + testSep + // Mailbox,
"%v" + testSep + // BridgePassword,
"%v" + testSep + // Version string
"%v" + testSep + // Timestamp,
"%v" + testSep + // IsHidden,
"%v" // IsCombinedAddressMode
// the best would be to run this test on mac, win, and linux separately
type testCredentials struct {
UserID,
Name,
Emails,
APIToken,
Mailbox,
BridgePassword,
Version string
Timestamp int64
IsHidden,
IsCombinedAddressMode bool
}
func init() { //nolint[gochecknoinits]
gob.Register(testCredentials{})
}
func (s *testCredentials) MarshalGob() string {
buf := bytes.Buffer{}
enc := gob.NewEncoder(&buf)
if err := enc.Encode(s); err != nil {
return ""
}
fmt.Printf("MarshalGob: %#v\n", buf.String())
return base64.StdEncoding.EncodeToString(buf.Bytes())
}
func (s *testCredentials) Clear() {
s.UserID = ""
s.Name = ""
s.Emails = ""
s.APIToken = ""
s.Mailbox = ""
s.BridgePassword = ""
s.Version = ""
s.Timestamp = 0
s.IsHidden = false
s.IsCombinedAddressMode = false
}
func (s *testCredentials) UnmarshalGob(secret string) error {
s.Clear()
b, err := base64.StdEncoding.DecodeString(secret)
if err != nil {
fmt.Println("decode base64", b)
return err
}
buf := bytes.NewBuffer(b)
dec := gob.NewDecoder(buf)
if err = dec.Decode(s); err != nil {
fmt.Println("decode gob", b, buf.Bytes())
return err
}
return nil
}
func (s *testCredentials) ToJSON() string {
if b, err := json.Marshal(s); err == nil {
fmt.Printf("MarshalJSON: %#v\n", string(b))
return base64.StdEncoding.EncodeToString(b)
}
return ""
}
func (s *testCredentials) FromJSON(secret string) error {
b, err := base64.StdEncoding.DecodeString(secret)
if err != nil {
return err
}
if err = json.Unmarshal(b, s); err == nil {
return nil
}
return err
}
func (s *testCredentials) MarshalFmt() string {
buf := bytes.Buffer{}
fmt.Fprintf(
&buf, secretFormat,
s.UserID,
s.Name,
s.Emails,
s.APIToken,
s.Mailbox,
s.BridgePassword,
s.Version,
s.Timestamp,
s.IsHidden,
s.IsCombinedAddressMode,
)
fmt.Printf("MarshalFmt: %#v\n", buf.String())
return base64.StdEncoding.EncodeToString(buf.Bytes())
}
func (s *testCredentials) UnmarshalFmt(secret string) error {
b, err := base64.StdEncoding.DecodeString(secret)
if err != nil {
return err
}
buf := bytes.NewBuffer(b)
fmt.Println("decode fmt", b, buf.Bytes())
_, err = fmt.Fscanf(
buf, secretFormat,
&s.UserID,
&s.Name,
&s.Emails,
&s.APIToken,
&s.Mailbox,
&s.BridgePassword,
&s.Version,
&s.Timestamp,
&s.IsHidden,
&s.IsCombinedAddressMode,
)
if err != nil {
return err
}
return nil
}
func (s *testCredentials) MarshalStrings() string { // this is the most space efficient
items := []string{
s.UserID, // 0
s.Name, // 1
s.Emails, // 2
s.APIToken, // 3
s.Mailbox, // 4
s.BridgePassword, // 5
s.Version, // 6
}
items = append(items, fmt.Sprint(s.Timestamp)) // 7
if s.IsHidden { // 8
items = append(items, "1")
} else {
items = append(items, "")
}
if s.IsCombinedAddressMode { // 9
items = append(items, "1")
} else {
items = append(items, "")
}
str := strings.Join(items, sep)
fmt.Printf("MarshalJoin: %#v\n", str)
return base64.StdEncoding.EncodeToString([]byte(str))
}
func (s *testCredentials) UnmarshalStrings(secret string) error {
b, err := base64.StdEncoding.DecodeString(secret)
if err != nil {
return err
}
items := strings.Split(string(b), sep)
if len(items) != 10 {
return ErrWrongFormat
}
s.UserID = items[0]
s.Name = items[1]
s.Emails = items[2]
s.APIToken = items[3]
s.Mailbox = items[4]
s.BridgePassword = items[5]
s.Version = items[6]
if _, err = fmt.Sscanf(items[7], "%d", &s.Timestamp); err != nil {
s.Timestamp = 0
}
if s.IsHidden = false; items[8] == "1" {
s.IsHidden = true
}
if s.IsCombinedAddressMode = false; items[9] == "1" {
s.IsCombinedAddressMode = true
}
return nil
}
func (s *testCredentials) IsSame(rhs *testCredentials) bool {
return s.Name == rhs.Name &&
s.Emails == rhs.Emails &&
s.APIToken == rhs.APIToken &&
s.Mailbox == rhs.Mailbox &&
s.BridgePassword == rhs.BridgePassword &&
s.Version == rhs.Version &&
s.Timestamp == rhs.Timestamp &&
s.IsHidden == rhs.IsHidden &&
s.IsCombinedAddressMode == rhs.IsCombinedAddressMode
}
func TestMarshalFormats(t *testing.T) {
input := testCredentials{UserID: "007", Emails: "ja@pm.me;jakub@cu.th", Timestamp: 152469263742, IsHidden: true}
fmt.Printf("input %#v\n", input)
secretStrings := input.MarshalStrings()
fmt.Printf("secretStrings %#v %d\n", secretStrings, len(secretStrings))
secretGob := input.MarshalGob()
fmt.Printf("secretGob %#v %d\n", secretGob, len(secretGob))
secretJSON := input.ToJSON()
fmt.Printf("secretJSON %#v %d\n", secretJSON, len(secretJSON))
secretFmt := input.MarshalFmt()
fmt.Printf("secretFmt %#v %d\n", secretFmt, len(secretFmt))
output := testCredentials{APIToken: "refresh"}
require.NoError(t, output.UnmarshalStrings(secretStrings))
fmt.Printf("strings out %#v \n", output)
require.True(t, input.IsSame(&output), "strings out not same")
output = testCredentials{APIToken: "refresh"}
require.NoError(t, output.UnmarshalGob(secretGob))
fmt.Printf("gob out %#v\n \n", output)
assert.Equal(t, input, output)
output = testCredentials{APIToken: "refresh"}
require.NoError(t, output.FromJSON(secretJSON))
fmt.Printf("json out %#v \n", output)
require.True(t, input.IsSame(&output), "json out not same")
/*
// Simple Fscanf not working!
output = testCredentials{APIToken: "refresh"}
require.NoError(t, output.UnmarshalFmt(secretFmt))
fmt.Printf("fmt out %#v \n", output)
require.True(t, input.IsSame(&output), "fmt out not same")
*/
}
func TestMarshal(t *testing.T) {
input := Credentials{
UserID: "",
Name: "007",
Emails: "ja@pm.me;aj@cus.tom",
APIToken: "sdfdsfsdfsdfsdf",
MailboxPassword: "cdcdcdcd",
BridgePassword: "wew123",
Version: "k11",
Timestamp: 152469263742,
IsHidden: true,
IsCombinedAddressMode: false,
}
fmt.Printf("input %#v\n", input)
secret := input.Marshal()
fmt.Printf("secret %#v %d\n", secret, len(secret))
output := Credentials{APIToken: "refresh"}
require.NoError(t, output.Unmarshal(secret))
fmt.Printf("output %#v\n", output)
assert.Equal(t, input, output)
}

View File

@ -0,0 +1,22 @@
// 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/>.
// Code generated by ./credits.sh at Mon Apr 6 08:14:14 CEST 2020. DO NOT EDIT.
package bridge
const Credits = "github.com/0xAX/notificator;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-imap-quota;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/andybalholm/cascadia;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/danieljoos/wincred;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/jhillyerd/enmime;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"

View File

@ -0,0 +1,107 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./listener/listener.go
// Package bridge is a generated GoMock package.
package bridge
import (
reflect "reflect"
time "time"
gomock "github.com/golang/mock/gomock"
)
// MockListener is a mock of Listener interface
type MockListener struct {
ctrl *gomock.Controller
recorder *MockListenerMockRecorder
}
// MockListenerMockRecorder is the mock recorder for MockListener
type MockListenerMockRecorder struct {
mock *MockListener
}
// NewMockListener creates a new mock instance
func NewMockListener(ctrl *gomock.Controller) *MockListener {
mock := &MockListener{ctrl: ctrl}
mock.recorder = &MockListenerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockListener) EXPECT() *MockListenerMockRecorder {
return m.recorder
}
// SetLimit mocks base method
func (m *MockListener) SetLimit(eventName string, limit time.Duration) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetLimit", eventName, limit)
}
// SetLimit indicates an expected call of SetLimit
func (mr *MockListenerMockRecorder) SetLimit(eventName, limit interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLimit", reflect.TypeOf((*MockListener)(nil).SetLimit), eventName, limit)
}
// Add mocks base method
func (m *MockListener) Add(eventName string, channel chan<- string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Add", eventName, channel)
}
// Add indicates an expected call of Add
func (mr *MockListenerMockRecorder) Add(eventName, channel interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), eventName, channel)
}
// Remove mocks base method
func (m *MockListener) Remove(eventName string, channel chan<- string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Remove", eventName, channel)
}
// Remove indicates an expected call of Remove
func (mr *MockListenerMockRecorder) Remove(eventName, channel interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockListener)(nil).Remove), eventName, channel)
}
// Emit mocks base method
func (m *MockListener) Emit(eventName, data string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Emit", eventName, data)
}
// Emit indicates an expected call of Emit
func (mr *MockListenerMockRecorder) Emit(eventName, data interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Emit", reflect.TypeOf((*MockListener)(nil).Emit), eventName, data)
}
// SetBuffer mocks base method
func (m *MockListener) SetBuffer(eventName string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetBuffer", eventName)
}
// SetBuffer indicates an expected call of SetBuffer
func (mr *MockListenerMockRecorder) SetBuffer(eventName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBuffer", reflect.TypeOf((*MockListener)(nil).SetBuffer), eventName)
}
// RetryEmit mocks base method
func (m *MockListener) RetryEmit(eventName string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "RetryEmit", eventName)
}
// RetryEmit indicates an expected call of RetryEmit
func (mr *MockListenerMockRecorder) RetryEmit(eventName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryEmit", reflect.TypeOf((*MockListener)(nil).RetryEmit), eventName)
}

View File

@ -0,0 +1,923 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/ProtonMail/proton-bridge/internal/bridge (interfaces: Configer,PreferenceProvider,PanicHandler,PMAPIProvider,CredentialsStorer)
// Package mocks is a generated GoMock package.
package mocks
import (
credentials "github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
crypto "github.com/ProtonMail/gopenpgp/crypto"
gomock "github.com/golang/mock/gomock"
io "io"
reflect "reflect"
)
// MockConfiger is a mock of Configer interface
type MockConfiger struct {
ctrl *gomock.Controller
recorder *MockConfigerMockRecorder
}
// MockConfigerMockRecorder is the mock recorder for MockConfiger
type MockConfigerMockRecorder struct {
mock *MockConfiger
}
// NewMockConfiger creates a new mock instance
func NewMockConfiger(ctrl *gomock.Controller) *MockConfiger {
mock := &MockConfiger{ctrl: ctrl}
mock.recorder = &MockConfigerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockConfiger) EXPECT() *MockConfigerMockRecorder {
return m.recorder
}
// ClearData mocks base method
func (m *MockConfiger) ClearData() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ClearData")
ret0, _ := ret[0].(error)
return ret0
}
// ClearData indicates an expected call of ClearData
func (mr *MockConfigerMockRecorder) ClearData() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearData", reflect.TypeOf((*MockConfiger)(nil).ClearData))
}
// GetAPIConfig mocks base method
func (m *MockConfiger) GetAPIConfig() *pmapi.ClientConfig {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAPIConfig")
ret0, _ := ret[0].(*pmapi.ClientConfig)
return ret0
}
// GetAPIConfig indicates an expected call of GetAPIConfig
func (mr *MockConfigerMockRecorder) GetAPIConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAPIConfig", reflect.TypeOf((*MockConfiger)(nil).GetAPIConfig))
}
// GetDBDir mocks base method
func (m *MockConfiger) GetDBDir() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetDBDir")
ret0, _ := ret[0].(string)
return ret0
}
// GetDBDir indicates an expected call of GetDBDir
func (mr *MockConfigerMockRecorder) GetDBDir() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDBDir", reflect.TypeOf((*MockConfiger)(nil).GetDBDir))
}
// GetIMAPCachePath mocks base method
func (m *MockConfiger) GetIMAPCachePath() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIMAPCachePath")
ret0, _ := ret[0].(string)
return ret0
}
// GetIMAPCachePath indicates an expected call of GetIMAPCachePath
func (mr *MockConfigerMockRecorder) GetIMAPCachePath() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIMAPCachePath", reflect.TypeOf((*MockConfiger)(nil).GetIMAPCachePath))
}
// MockPreferenceProvider is a mock of PreferenceProvider interface
type MockPreferenceProvider struct {
ctrl *gomock.Controller
recorder *MockPreferenceProviderMockRecorder
}
// MockPreferenceProviderMockRecorder is the mock recorder for MockPreferenceProvider
type MockPreferenceProviderMockRecorder struct {
mock *MockPreferenceProvider
}
// NewMockPreferenceProvider creates a new mock instance
func NewMockPreferenceProvider(ctrl *gomock.Controller) *MockPreferenceProvider {
mock := &MockPreferenceProvider{ctrl: ctrl}
mock.recorder = &MockPreferenceProviderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockPreferenceProvider) EXPECT() *MockPreferenceProviderMockRecorder {
return m.recorder
}
// Get mocks base method
func (m *MockPreferenceProvider) Get(arg0 string) string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", arg0)
ret0, _ := ret[0].(string)
return ret0
}
// Get indicates an expected call of Get
func (mr *MockPreferenceProviderMockRecorder) Get(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockPreferenceProvider)(nil).Get), arg0)
}
// GetBool mocks base method
func (m *MockPreferenceProvider) GetBool(arg0 string) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBool", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// GetBool indicates an expected call of GetBool
func (mr *MockPreferenceProviderMockRecorder) GetBool(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockPreferenceProvider)(nil).GetBool), arg0)
}
// GetInt mocks base method
func (m *MockPreferenceProvider) GetInt(arg0 string) int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetInt", arg0)
ret0, _ := ret[0].(int)
return ret0
}
// GetInt indicates an expected call of GetInt
func (mr *MockPreferenceProviderMockRecorder) GetInt(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockPreferenceProvider)(nil).GetInt), arg0)
}
// Set mocks base method
func (m *MockPreferenceProvider) Set(arg0, arg1 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Set", arg0, arg1)
}
// Set indicates an expected call of Set
func (mr *MockPreferenceProviderMockRecorder) Set(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockPreferenceProvider)(nil).Set), arg0, arg1)
}
// MockPanicHandler is a mock of PanicHandler interface
type MockPanicHandler struct {
ctrl *gomock.Controller
recorder *MockPanicHandlerMockRecorder
}
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler
type MockPanicHandlerMockRecorder struct {
mock *MockPanicHandler
}
// NewMockPanicHandler creates a new mock instance
func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler {
mock := &MockPanicHandler{ctrl: ctrl}
mock.recorder = &MockPanicHandlerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder {
return m.recorder
}
// HandlePanic mocks base method
func (m *MockPanicHandler) HandlePanic() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "HandlePanic")
}
// HandlePanic indicates an expected call of HandlePanic
func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
}
// MockPMAPIProvider is a mock of PMAPIProvider interface
type MockPMAPIProvider struct {
ctrl *gomock.Controller
recorder *MockPMAPIProviderMockRecorder
}
// MockPMAPIProviderMockRecorder is the mock recorder for MockPMAPIProvider
type MockPMAPIProviderMockRecorder struct {
mock *MockPMAPIProvider
}
// NewMockPMAPIProvider creates a new mock instance
func NewMockPMAPIProvider(ctrl *gomock.Controller) *MockPMAPIProvider {
mock := &MockPMAPIProvider{ctrl: ctrl}
mock.recorder = &MockPMAPIProviderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockPMAPIProvider) EXPECT() *MockPMAPIProviderMockRecorder {
return m.recorder
}
// Addresses mocks base method
func (m *MockPMAPIProvider) Addresses() pmapi.AddressList {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Addresses")
ret0, _ := ret[0].(pmapi.AddressList)
return ret0
}
// Addresses indicates an expected call of Addresses
func (mr *MockPMAPIProviderMockRecorder) Addresses() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addresses", reflect.TypeOf((*MockPMAPIProvider)(nil).Addresses))
}
// Auth mocks base method
func (m *MockPMAPIProvider) Auth(arg0, arg1 string, arg2 *pmapi.AuthInfo) (*pmapi.Auth, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Auth", arg0, arg1, arg2)
ret0, _ := ret[0].(*pmapi.Auth)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Auth indicates an expected call of Auth
func (mr *MockPMAPIProviderMockRecorder) Auth(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Auth", reflect.TypeOf((*MockPMAPIProvider)(nil).Auth), arg0, arg1, arg2)
}
// Auth2FA mocks base method
func (m *MockPMAPIProvider) Auth2FA(arg0 string, arg1 *pmapi.Auth) (*pmapi.Auth2FA, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Auth2FA", arg0, arg1)
ret0, _ := ret[0].(*pmapi.Auth2FA)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Auth2FA indicates an expected call of Auth2FA
func (mr *MockPMAPIProviderMockRecorder) Auth2FA(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Auth2FA", reflect.TypeOf((*MockPMAPIProvider)(nil).Auth2FA), arg0, arg1)
}
// AuthInfo mocks base method
func (m *MockPMAPIProvider) AuthInfo(arg0 string) (*pmapi.AuthInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthInfo", arg0)
ret0, _ := ret[0].(*pmapi.AuthInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AuthInfo indicates an expected call of AuthInfo
func (mr *MockPMAPIProviderMockRecorder) AuthInfo(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthInfo", reflect.TypeOf((*MockPMAPIProvider)(nil).AuthInfo), arg0)
}
// AuthRefresh mocks base method
func (m *MockPMAPIProvider) AuthRefresh(arg0 string) (*pmapi.Auth, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthRefresh", arg0)
ret0, _ := ret[0].(*pmapi.Auth)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AuthRefresh indicates an expected call of AuthRefresh
func (mr *MockPMAPIProviderMockRecorder) AuthRefresh(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthRefresh", reflect.TypeOf((*MockPMAPIProvider)(nil).AuthRefresh), arg0)
}
// CountMessages mocks base method
func (m *MockPMAPIProvider) CountMessages(arg0 string) ([]*pmapi.MessagesCount, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CountMessages", arg0)
ret0, _ := ret[0].([]*pmapi.MessagesCount)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CountMessages indicates an expected call of CountMessages
func (mr *MockPMAPIProviderMockRecorder) CountMessages(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountMessages", reflect.TypeOf((*MockPMAPIProvider)(nil).CountMessages), arg0)
}
// CreateAttachment mocks base method
func (m *MockPMAPIProvider) CreateAttachment(arg0 *pmapi.Attachment, arg1, arg2 io.Reader) (*pmapi.Attachment, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateAttachment", arg0, arg1, arg2)
ret0, _ := ret[0].(*pmapi.Attachment)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateAttachment indicates an expected call of CreateAttachment
func (mr *MockPMAPIProviderMockRecorder) CreateAttachment(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAttachment", reflect.TypeOf((*MockPMAPIProvider)(nil).CreateAttachment), arg0, arg1, arg2)
}
// CreateDraft mocks base method
func (m *MockPMAPIProvider) CreateDraft(arg0 *pmapi.Message, arg1 string, arg2 int) (*pmapi.Message, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateDraft", arg0, arg1, arg2)
ret0, _ := ret[0].(*pmapi.Message)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateDraft indicates an expected call of CreateDraft
func (mr *MockPMAPIProviderMockRecorder) CreateDraft(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateDraft", reflect.TypeOf((*MockPMAPIProvider)(nil).CreateDraft), arg0, arg1, arg2)
}
// CreateLabel mocks base method
func (m *MockPMAPIProvider) CreateLabel(arg0 *pmapi.Label) (*pmapi.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateLabel", arg0)
ret0, _ := ret[0].(*pmapi.Label)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateLabel indicates an expected call of CreateLabel
func (mr *MockPMAPIProviderMockRecorder) CreateLabel(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLabel", reflect.TypeOf((*MockPMAPIProvider)(nil).CreateLabel), arg0)
}
// CurrentUser mocks base method
func (m *MockPMAPIProvider) CurrentUser() (*pmapi.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CurrentUser")
ret0, _ := ret[0].(*pmapi.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CurrentUser indicates an expected call of CurrentUser
func (mr *MockPMAPIProviderMockRecorder) CurrentUser() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentUser", reflect.TypeOf((*MockPMAPIProvider)(nil).CurrentUser))
}
// DecryptAndVerifyCards mocks base method
func (m *MockPMAPIProvider) DecryptAndVerifyCards(arg0 []pmapi.Card) ([]pmapi.Card, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecryptAndVerifyCards", arg0)
ret0, _ := ret[0].([]pmapi.Card)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecryptAndVerifyCards indicates an expected call of DecryptAndVerifyCards
func (mr *MockPMAPIProviderMockRecorder) DecryptAndVerifyCards(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptAndVerifyCards", reflect.TypeOf((*MockPMAPIProvider)(nil).DecryptAndVerifyCards), arg0)
}
// DeleteLabel mocks base method
func (m *MockPMAPIProvider) DeleteLabel(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteLabel", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteLabel indicates an expected call of DeleteLabel
func (mr *MockPMAPIProviderMockRecorder) DeleteLabel(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLabel", reflect.TypeOf((*MockPMAPIProvider)(nil).DeleteLabel), arg0)
}
// DeleteMessages mocks base method
func (m *MockPMAPIProvider) DeleteMessages(arg0 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteMessages", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteMessages indicates an expected call of DeleteMessages
func (mr *MockPMAPIProviderMockRecorder) DeleteMessages(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessages", reflect.TypeOf((*MockPMAPIProvider)(nil).DeleteMessages), arg0)
}
// EmptyFolder mocks base method
func (m *MockPMAPIProvider) EmptyFolder(arg0, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EmptyFolder", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// EmptyFolder indicates an expected call of EmptyFolder
func (mr *MockPMAPIProviderMockRecorder) EmptyFolder(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EmptyFolder", reflect.TypeOf((*MockPMAPIProvider)(nil).EmptyFolder), arg0, arg1)
}
// GetAttachment mocks base method
func (m *MockPMAPIProvider) GetAttachment(arg0 string) (io.ReadCloser, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAttachment", arg0)
ret0, _ := ret[0].(io.ReadCloser)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAttachment indicates an expected call of GetAttachment
func (mr *MockPMAPIProviderMockRecorder) GetAttachment(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttachment", reflect.TypeOf((*MockPMAPIProvider)(nil).GetAttachment), arg0)
}
// GetContactByID mocks base method
func (m *MockPMAPIProvider) GetContactByID(arg0 string) (pmapi.Contact, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetContactByID", arg0)
ret0, _ := ret[0].(pmapi.Contact)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetContactByID indicates an expected call of GetContactByID
func (mr *MockPMAPIProviderMockRecorder) GetContactByID(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContactByID", reflect.TypeOf((*MockPMAPIProvider)(nil).GetContactByID), arg0)
}
// GetContactEmailByEmail mocks base method
func (m *MockPMAPIProvider) GetContactEmailByEmail(arg0 string, arg1, arg2 int) ([]pmapi.ContactEmail, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetContactEmailByEmail", arg0, arg1, arg2)
ret0, _ := ret[0].([]pmapi.ContactEmail)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetContactEmailByEmail indicates an expected call of GetContactEmailByEmail
func (mr *MockPMAPIProviderMockRecorder) GetContactEmailByEmail(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContactEmailByEmail", reflect.TypeOf((*MockPMAPIProvider)(nil).GetContactEmailByEmail), arg0, arg1, arg2)
}
// GetEvent mocks base method
func (m *MockPMAPIProvider) GetEvent(arg0 string) (*pmapi.Event, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetEvent", arg0)
ret0, _ := ret[0].(*pmapi.Event)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetEvent indicates an expected call of GetEvent
func (mr *MockPMAPIProviderMockRecorder) GetEvent(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEvent", reflect.TypeOf((*MockPMAPIProvider)(nil).GetEvent), arg0)
}
// GetMailSettings mocks base method
func (m *MockPMAPIProvider) GetMailSettings() (pmapi.MailSettings, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetMailSettings")
ret0, _ := ret[0].(pmapi.MailSettings)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetMailSettings indicates an expected call of GetMailSettings
func (mr *MockPMAPIProviderMockRecorder) GetMailSettings() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMailSettings", reflect.TypeOf((*MockPMAPIProvider)(nil).GetMailSettings))
}
// GetMessage mocks base method
func (m *MockPMAPIProvider) GetMessage(arg0 string) (*pmapi.Message, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetMessage", arg0)
ret0, _ := ret[0].(*pmapi.Message)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetMessage indicates an expected call of GetMessage
func (mr *MockPMAPIProviderMockRecorder) GetMessage(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockPMAPIProvider)(nil).GetMessage), arg0)
}
// GetPublicKeysForEmail mocks base method
func (m *MockPMAPIProvider) GetPublicKeysForEmail(arg0 string) ([]pmapi.PublicKey, bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPublicKeysForEmail", arg0)
ret0, _ := ret[0].([]pmapi.PublicKey)
ret1, _ := ret[1].(bool)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// GetPublicKeysForEmail indicates an expected call of GetPublicKeysForEmail
func (mr *MockPMAPIProviderMockRecorder) GetPublicKeysForEmail(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicKeysForEmail", reflect.TypeOf((*MockPMAPIProvider)(nil).GetPublicKeysForEmail), arg0)
}
// Import mocks base method
func (m *MockPMAPIProvider) Import(arg0 []*pmapi.ImportMsgReq) ([]*pmapi.ImportMsgRes, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Import", arg0)
ret0, _ := ret[0].([]*pmapi.ImportMsgRes)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Import indicates an expected call of Import
func (mr *MockPMAPIProviderMockRecorder) Import(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Import", reflect.TypeOf((*MockPMAPIProvider)(nil).Import), arg0)
}
// KeyRingForAddressID mocks base method
func (m *MockPMAPIProvider) KeyRingForAddressID(arg0 string) *crypto.KeyRing {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "KeyRingForAddressID", arg0)
ret0, _ := ret[0].(*crypto.KeyRing)
return ret0
}
// KeyRingForAddressID indicates an expected call of KeyRingForAddressID
func (mr *MockPMAPIProviderMockRecorder) KeyRingForAddressID(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyRingForAddressID", reflect.TypeOf((*MockPMAPIProvider)(nil).KeyRingForAddressID), arg0)
}
// LabelMessages mocks base method
func (m *MockPMAPIProvider) LabelMessages(arg0 []string, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LabelMessages", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// LabelMessages indicates an expected call of LabelMessages
func (mr *MockPMAPIProviderMockRecorder) LabelMessages(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LabelMessages", reflect.TypeOf((*MockPMAPIProvider)(nil).LabelMessages), arg0, arg1)
}
// ListLabels mocks base method
func (m *MockPMAPIProvider) ListLabels() ([]*pmapi.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListLabels")
ret0, _ := ret[0].([]*pmapi.Label)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListLabels indicates an expected call of ListLabels
func (mr *MockPMAPIProviderMockRecorder) ListLabels() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLabels", reflect.TypeOf((*MockPMAPIProvider)(nil).ListLabels))
}
// ListMessages mocks base method
func (m *MockPMAPIProvider) ListMessages(arg0 *pmapi.MessagesFilter) ([]*pmapi.Message, int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListMessages", arg0)
ret0, _ := ret[0].([]*pmapi.Message)
ret1, _ := ret[1].(int)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ListMessages indicates an expected call of ListMessages
func (mr *MockPMAPIProviderMockRecorder) ListMessages(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMessages", reflect.TypeOf((*MockPMAPIProvider)(nil).ListMessages), arg0)
}
// Logout mocks base method
func (m *MockPMAPIProvider) Logout() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Logout")
ret0, _ := ret[0].(error)
return ret0
}
// Logout indicates an expected call of Logout
func (mr *MockPMAPIProviderMockRecorder) Logout() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockPMAPIProvider)(nil).Logout))
}
// MarkMessagesRead mocks base method
func (m *MockPMAPIProvider) MarkMessagesRead(arg0 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MarkMessagesRead", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// MarkMessagesRead indicates an expected call of MarkMessagesRead
func (mr *MockPMAPIProviderMockRecorder) MarkMessagesRead(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkMessagesRead", reflect.TypeOf((*MockPMAPIProvider)(nil).MarkMessagesRead), arg0)
}
// MarkMessagesUnread mocks base method
func (m *MockPMAPIProvider) MarkMessagesUnread(arg0 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MarkMessagesUnread", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// MarkMessagesUnread indicates an expected call of MarkMessagesUnread
func (mr *MockPMAPIProviderMockRecorder) MarkMessagesUnread(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkMessagesUnread", reflect.TypeOf((*MockPMAPIProvider)(nil).MarkMessagesUnread), arg0)
}
// ReportBugWithEmailClient mocks base method
func (m *MockPMAPIProvider) ReportBugWithEmailClient(arg0, arg1, arg2, arg3, arg4, arg5, arg6 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReportBugWithEmailClient", arg0, arg1, arg2, arg3, arg4, arg5, arg6)
ret0, _ := ret[0].(error)
return ret0
}
// ReportBugWithEmailClient indicates an expected call of ReportBugWithEmailClient
func (mr *MockPMAPIProviderMockRecorder) ReportBugWithEmailClient(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportBugWithEmailClient", reflect.TypeOf((*MockPMAPIProvider)(nil).ReportBugWithEmailClient), arg0, arg1, arg2, arg3, arg4, arg5, arg6)
}
// SendMessage mocks base method
func (m *MockPMAPIProvider) SendMessage(arg0 string, arg1 *pmapi.SendMessageReq) (*pmapi.Message, *pmapi.Message, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendMessage", arg0, arg1)
ret0, _ := ret[0].(*pmapi.Message)
ret1, _ := ret[1].(*pmapi.Message)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// SendMessage indicates an expected call of SendMessage
func (mr *MockPMAPIProviderMockRecorder) SendMessage(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockPMAPIProvider)(nil).SendMessage), arg0, arg1)
}
// SendSimpleMetric mocks base method
func (m *MockPMAPIProvider) SendSimpleMetric(arg0, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendSimpleMetric", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// SendSimpleMetric indicates an expected call of SendSimpleMetric
func (mr *MockPMAPIProviderMockRecorder) SendSimpleMetric(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendSimpleMetric", reflect.TypeOf((*MockPMAPIProvider)(nil).SendSimpleMetric), arg0, arg1, arg2)
}
// SetAuths mocks base method
func (m *MockPMAPIProvider) SetAuths(arg0 chan<- *pmapi.Auth) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetAuths", arg0)
}
// SetAuths indicates an expected call of SetAuths
func (mr *MockPMAPIProviderMockRecorder) SetAuths(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAuths", reflect.TypeOf((*MockPMAPIProvider)(nil).SetAuths), arg0)
}
// UnlabelMessages mocks base method
func (m *MockPMAPIProvider) UnlabelMessages(arg0 []string, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UnlabelMessages", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UnlabelMessages indicates an expected call of UnlabelMessages
func (mr *MockPMAPIProviderMockRecorder) UnlabelMessages(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlabelMessages", reflect.TypeOf((*MockPMAPIProvider)(nil).UnlabelMessages), arg0, arg1)
}
// Unlock mocks base method
func (m *MockPMAPIProvider) Unlock(arg0 string) (*crypto.KeyRing, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Unlock", arg0)
ret0, _ := ret[0].(*crypto.KeyRing)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Unlock indicates an expected call of Unlock
func (mr *MockPMAPIProviderMockRecorder) Unlock(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockPMAPIProvider)(nil).Unlock), arg0)
}
// UnlockAddresses mocks base method
func (m *MockPMAPIProvider) UnlockAddresses(arg0 []byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UnlockAddresses", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// UnlockAddresses indicates an expected call of UnlockAddresses
func (mr *MockPMAPIProviderMockRecorder) UnlockAddresses(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlockAddresses", reflect.TypeOf((*MockPMAPIProvider)(nil).UnlockAddresses), arg0)
}
// UpdateLabel mocks base method
func (m *MockPMAPIProvider) UpdateLabel(arg0 *pmapi.Label) (*pmapi.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateLabel", arg0)
ret0, _ := ret[0].(*pmapi.Label)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateLabel indicates an expected call of UpdateLabel
func (mr *MockPMAPIProviderMockRecorder) UpdateLabel(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLabel", reflect.TypeOf((*MockPMAPIProvider)(nil).UpdateLabel), arg0)
}
// UpdateUser mocks base method
func (m *MockPMAPIProvider) UpdateUser() (*pmapi.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateUser")
ret0, _ := ret[0].(*pmapi.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateUser indicates an expected call of UpdateUser
func (mr *MockPMAPIProviderMockRecorder) UpdateUser() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockPMAPIProvider)(nil).UpdateUser))
}
// MockCredentialsStorer is a mock of CredentialsStorer interface
type MockCredentialsStorer struct {
ctrl *gomock.Controller
recorder *MockCredentialsStorerMockRecorder
}
// MockCredentialsStorerMockRecorder is the mock recorder for MockCredentialsStorer
type MockCredentialsStorerMockRecorder struct {
mock *MockCredentialsStorer
}
// NewMockCredentialsStorer creates a new mock instance
func NewMockCredentialsStorer(ctrl *gomock.Controller) *MockCredentialsStorer {
mock := &MockCredentialsStorer{ctrl: ctrl}
mock.recorder = &MockCredentialsStorerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockCredentialsStorer) EXPECT() *MockCredentialsStorerMockRecorder {
return m.recorder
}
// Add mocks base method
func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3 string, arg4 []string) (*credentials.Credentials, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Add", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(*credentials.Credentials)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Add indicates an expected call of Add
func (mr *MockCredentialsStorerMockRecorder) Add(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockCredentialsStorer)(nil).Add), arg0, arg1, arg2, arg3, arg4)
}
// Delete mocks base method
func (m *MockCredentialsStorer) Delete(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete
func (mr *MockCredentialsStorerMockRecorder) Delete(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockCredentialsStorer)(nil).Delete), arg0)
}
// Get mocks base method
func (m *MockCredentialsStorer) Get(arg0 string) (*credentials.Credentials, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", arg0)
ret0, _ := ret[0].(*credentials.Credentials)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get indicates an expected call of Get
func (mr *MockCredentialsStorerMockRecorder) Get(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockCredentialsStorer)(nil).Get), arg0)
}
// List mocks base method
func (m *MockCredentialsStorer) List() ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List")
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// List indicates an expected call of List
func (mr *MockCredentialsStorerMockRecorder) List() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockCredentialsStorer)(nil).List))
}
// Logout mocks base method
func (m *MockCredentialsStorer) Logout(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Logout", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Logout indicates an expected call of Logout
func (mr *MockCredentialsStorerMockRecorder) Logout(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockCredentialsStorer)(nil).Logout), arg0)
}
// SwitchAddressMode mocks base method
func (m *MockCredentialsStorer) SwitchAddressMode(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SwitchAddressMode", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SwitchAddressMode indicates an expected call of SwitchAddressMode
func (mr *MockCredentialsStorerMockRecorder) SwitchAddressMode(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SwitchAddressMode", reflect.TypeOf((*MockCredentialsStorer)(nil).SwitchAddressMode), arg0)
}
// UpdateEmails mocks base method
func (m *MockCredentialsStorer) UpdateEmails(arg0 string, arg1 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateEmails", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateEmails indicates an expected call of UpdateEmails
func (mr *MockCredentialsStorerMockRecorder) UpdateEmails(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEmails", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateEmails), arg0, arg1)
}
// UpdateToken mocks base method
func (m *MockCredentialsStorer) UpdateToken(arg0, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateToken", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateToken indicates an expected call of UpdateToken
func (mr *MockCredentialsStorerMockRecorder) UpdateToken(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateToken", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateToken), arg0, arg1)
}

View File

@ -0,0 +1,34 @@
// 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/>.
// Code generated by ./release-notes.sh at Mon Apr 6 08:14:14 CEST 2020. DO NOT EDIT.
package bridge
const ReleaseNotes = `NOTE: We recommend to reconfigure your email client after upgrading to ensure the best results with the new draft folder support
• Faster and more resilient mail synchronization process, especially for large mailboxes
• Added "Alternate Routing" feature to mitigate blocking of Proton Servers
• Added synchronization of draft folder
• Improved event handling when there are frequent changes
• Security improvements for loading dependent libraries
• Minor UI & API communication tweaks
`
const ReleaseFixedBugs = `• Fixed rare case of sending the same message multiple times in Outlook
• Fixed bug in macOS update process; available from next update
`

105
internal/bridge/types.go Normal file
View File

@ -0,0 +1,105 @@
// 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 bridge
import (
"io"
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/proton-bridge/internal/bridge/credentials"
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi" // mockgen needs this to be given an explicit import name
)
type Configer interface {
ClearData() error
GetDBDir() string
GetIMAPCachePath() string
GetAPIConfig() *pmapi.ClientConfig
}
type PreferenceProvider interface {
Get(key string) string
GetBool(key string) bool
GetInt(key string) int
Set(key string, value string)
}
type PanicHandler interface {
HandlePanic()
}
type PMAPIProviderFactory func(string) PMAPIProvider
type PMAPIProvider interface {
SetAuths(auths chan<- *pmapi.Auth)
Auth(username, password string, info *pmapi.AuthInfo) (*pmapi.Auth, error)
AuthInfo(username string) (*pmapi.AuthInfo, error)
AuthRefresh(token string) (*pmapi.Auth, error)
Unlock(mailboxPassword string) (kr *pmcrypto.KeyRing, err error)
UnlockAddresses(passphrase []byte) error
CurrentUser() (*pmapi.User, error)
UpdateUser() (*pmapi.User, error)
Addresses() pmapi.AddressList
Logout() error
GetEvent(eventID string) (*pmapi.Event, error)
CountMessages(addressID string) ([]*pmapi.MessagesCount, error)
ListMessages(filter *pmapi.MessagesFilter) ([]*pmapi.Message, int, error)
GetMessage(apiID string) (*pmapi.Message, error)
Import([]*pmapi.ImportMsgReq) ([]*pmapi.ImportMsgRes, error)
DeleteMessages(apiIDs []string) error
LabelMessages(apiIDs []string, labelID string) error
UnlabelMessages(apiIDs []string, labelID string) error
MarkMessagesRead(apiIDs []string) error
MarkMessagesUnread(apiIDs []string) error
ListLabels() ([]*pmapi.Label, error)
CreateLabel(label *pmapi.Label) (*pmapi.Label, error)
UpdateLabel(label *pmapi.Label) (*pmapi.Label, error)
DeleteLabel(labelID string) error
EmptyFolder(labelID string, addressID string) error
ReportBugWithEmailClient(os, osVersion, title, description, username, email, emailClient string) error
SendSimpleMetric(category, action, label string) error
Auth2FA(twoFactorCode string, auth *pmapi.Auth) (*pmapi.Auth2FA, error)
GetMailSettings() (pmapi.MailSettings, error)
GetContactEmailByEmail(string, int, int) ([]pmapi.ContactEmail, error)
GetContactByID(string) (pmapi.Contact, error)
DecryptAndVerifyCards([]pmapi.Card) ([]pmapi.Card, error)
GetPublicKeysForEmail(string) ([]pmapi.PublicKey, bool, error)
SendMessage(string, *pmapi.SendMessageReq) (sent, parent *pmapi.Message, err error)
CreateDraft(m *pmapi.Message, parent string, action int) (created *pmapi.Message, err error)
CreateAttachment(att *pmapi.Attachment, r io.Reader, sig io.Reader) (created *pmapi.Attachment, err error)
KeyRingForAddressID(string) (kr *pmcrypto.KeyRing)
GetAttachment(id string) (att io.ReadCloser, err error)
}
type CredentialsStorer interface {
List() (userIDs []string, err error)
Add(userID, userName, apiToken, mailboxPassword string, emails []string) (*credentials.Credentials, error)
Get(userID string) (*credentials.Credentials, error)
SwitchAddressMode(userID string) error
UpdateEmails(userID string, emails []string) error
UpdateToken(userID, apiToken string) error
Logout(userID string) error
Delete(userID string) error
}

621
internal/bridge/user.go Normal file
View File

@ -0,0 +1,621 @@
// 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 bridge
import (
"fmt"
"path/filepath"
"runtime"
"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/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/pkg/errors"
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.
var ErrLoggedOutUser = errors.New("bridge account is logged out, use bridge to login again")
// User is a struct on top of API client and credentials store.
type User struct {
log *logrus.Entry
panicHandler PanicHandler
listener listener.Listener
apiClient PMAPIProvider
credStorer CredentialsStorer
imapUpdatesChannel chan interface{}
store *store.Store
storeCache *store.Cache
storePath string
userID string
creds *credentials.Credentials
lock sync.RWMutex
authChannel chan *pmapi.Auth
hasAPIAuth bool
unlockingKeyringLock sync.Mutex
wasKeyringUnlocked bool
}
// newUser creates a new bridge user.
func newUser(
panicHandler PanicHandler,
userID string,
eventListener listener.Listener,
credStorer CredentialsStorer,
apiClient PMAPIProvider,
storeCache *store.Cache,
storeDir string,
) (u *User, err error) {
log := log.WithField("user", userID)
log.Debug("Creating or loading user")
creds, err := credStorer.Get(userID)
if err != nil {
return nil, errors.Wrap(err, "failed to load user credentials")
}
u = &User{
log: log,
panicHandler: panicHandler,
listener: eventListener,
credStorer: credStorer,
apiClient: apiClient,
storeCache: storeCache,
storePath: getUserStorePath(storeDir, userID),
userID: userID,
creds: creds,
}
return
}
// init initialises a bridge 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
// something in the store changed).
func (u *User) init(idleUpdates chan interface{}, apiClient PMAPIProvider) (err error) {
// If this is an existing user, we still need a new api client to get a new refresh token.
// If it's a new user, doesn't matter really; this is basically a noop in this case.
u.apiClient = apiClient
u.unlockingKeyringLock.Lock()
u.wasKeyringUnlocked = false
u.unlockingKeyringLock.Unlock()
// Reload the user's credentials (if they log out and back in we need the new
// version with the apitoken and mailbox password).
creds, err := u.credStorer.Get(u.userID)
if err != nil {
return errors.Wrap(err, "failed to load user credentials")
}
u.creds = creds
// Set up the auth channel on which auths from the api client are sent.
u.authChannel = make(chan *pmapi.Auth)
u.apiClient.SetAuths(u.authChannel)
u.hasAPIAuth = false
go func() {
defer u.panicHandler.HandlePanic()
u.watchAPIClientAuths()
}()
// 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.
if authErr := u.authorizeIfNecessary(false); authErr != nil {
switch errors.Cause(authErr) {
case pmapi.ErrAPINotReachable, pmapi.ErrUpgradeApplication, ErrLoggedOutUser:
u.log.WithError(authErr).Warn("Could not authorize user")
default:
if logoutErr := u.logout(); logoutErr != nil {
u.log.WithError(logoutErr).Warn("Could not logout user")
}
return errors.Wrap(authErr, "failed to authorize user")
}
}
// Logged-out user keeps store running to access offline data.
// Therefore it is necessary to close it before re-init.
if u.store != nil {
if err := u.store.Close(); err != nil {
log.WithError(err).Error("Not able to close store")
}
u.store = nil
}
store, err := store.New(u.panicHandler, u, u.apiClient, u.listener, u.storePath, u.storeCache)
if err != nil {
return errors.Wrap(err, "failed to create store")
}
u.store = store
// Save the imap updates channel here so it can be set later when imap connects.
u.imapUpdatesChannel = idleUpdates
return err
}
func (u *User) SetIMAPIdleUpdateChannel() {
if u.store == nil {
return
}
u.store.SetIMAPUpdateChannel(u.imapUpdatesChannel)
}
// authorizeIfNecessary checks whether user is logged in and is connected to api auth channel.
// If user is not already connected to the api auth channel (for example there was no internet during start),
// it tries to connect it. See `connectToAuthChannel` for more info.
func (u *User) authorizeIfNecessary(emitEvent bool) (err error) {
// If user is connected and has an auth channel, then perfect, nothing to do here.
if u.creds.IsConnected() && u.HasAPIAuth() {
// The keyring unlock is triggered here to resolve state where apiClient
// is authenticated (we have auth token) but it was not possible to download
// and unlock the keys (internet not reachable).
return u.unlockIfNecessary()
}
if !u.creds.IsConnected() {
err = ErrLoggedOutUser
} else if err = u.authorizeAndUnlock(); err != nil {
u.log.WithError(err).Error("Could not authorize and unlock user")
switch errors.Cause(err) {
case pmapi.ErrUpgradeApplication:
u.listener.Emit(events.UpgradeApplicationEvent, "")
case pmapi.ErrAPINotReachable:
u.listener.Emit(events.InternetOffEvent, "")
default:
if errLogout := u.credStorer.Logout(u.userID); errLogout != nil {
u.log.WithField("err", errLogout).Error("Could not log user out from credentials store")
}
}
}
if emitEvent && err != nil &&
errors.Cause(err) != pmapi.ErrUpgradeApplication &&
errors.Cause(err) != pmapi.ErrAPINotReachable {
u.listener.Emit(events.LogoutEvent, u.userID)
}
return err
}
// unlockIfNecessary will not trigger keyring unlocking if it was already successfully unlocked.
func (u *User) unlockIfNecessary() error {
u.unlockingKeyringLock.Lock()
defer u.unlockingKeyringLock.Unlock()
if u.wasKeyringUnlocked {
return nil
}
if _, err := u.apiClient.Unlock(u.creds.MailboxPassword); err != nil {
return errors.Wrap(err, "failed to unlock user")
}
if err := u.apiClient.UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
return errors.Wrap(err, "failed to unlock user addresses")
}
u.wasKeyringUnlocked = true
return nil
}
// authorizeAndUnlock tries to authorize the user with the API using the the user's APIToken.
// If that succeeds, it tries to unlock the user's keys and addresses.
func (u *User) authorizeAndUnlock() (err error) {
if u.creds.APIToken == "" {
u.log.Warn("Could not connect to API auth channel, have no API token")
return nil
}
auth, err := u.apiClient.AuthRefresh(u.creds.APIToken)
if err != nil {
return errors.Wrap(err, "failed to refresh API auth")
}
u.authChannel <- auth
if _, err = u.apiClient.Unlock(u.creds.MailboxPassword); err != nil {
return errors.Wrap(err, "failed to unlock user")
}
if err = u.apiClient.UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
return errors.Wrap(err, "failed to unlock user addresses")
}
return nil
}
// See `connectToAPIClientAuthChannel` for more info.
func (u *User) watchAPIClientAuths() {
for auth := range u.authChannel {
if auth != nil {
newRefreshToken := auth.UID() + ":" + auth.RefreshToken
u.updateAPIToken(newRefreshToken)
u.hasAPIAuth = true
} else if err := u.logout(); err != nil {
u.log.WithError(err).Error("Cannot logout user after receiving empty auth from API")
}
}
}
// updateAPIToken is helper for updating the token in keychain. It's not supposed to be
// called directly from other parts of the code--only from `watchAPIClientAuths`.
func (u *User) updateAPIToken(newRefreshToken string) {
u.lock.Lock()
defer u.lock.Unlock()
u.log.Info("Saving refresh token")
if err := u.credStorer.UpdateToken(u.userID, newRefreshToken); err != nil {
u.log.WithError(err).Error("Cannot update refresh token in credentials store")
} else {
u.refreshFromCredentials()
}
}
// clearStore removes the database.
func (u *User) clearStore() error {
u.log.Trace("Clearing user store")
if u.store != nil {
if err := u.store.Remove(); err != nil {
return errors.Wrap(err, "failed to remove store")
}
} else {
u.log.Warn("Store is not initialized: cleaning up store files manually")
if err := store.RemoveStore(u.storeCache, u.storePath, u.userID); err != nil {
return errors.Wrap(err, "failed to remove store manually")
}
}
return nil
}
// closeStore just closes the store without deleting it.
func (u *User) closeStore() error {
u.log.Trace("Closing user store")
if u.store != nil {
if err := u.store.Close(); err != nil {
return errors.Wrap(err, "failed to close store")
}
}
return nil
}
// getUserStorePath returns the file path of the store database for the given userID.
func getUserStorePath(storeDir string, userID string) (path string) {
fileName := fmt.Sprintf("mailbox-%v.db", userID)
return filepath.Join(storeDir, fileName)
}
// GetTemporaryPMAPIClient returns an authorised PMAPI client.
// Do not use! It's only for backward compatibility of old SMTP and IMAP implementations.
// After proper refactor of SMTP and IMAP remove this method.
func (u *User) GetTemporaryPMAPIClient() PMAPIProvider {
return u.apiClient
}
// ID returns the user's userID.
func (u *User) ID() string {
return u.userID
}
// Username returns the user's username as found in the user's credentials.
func (u *User) Username() string {
u.lock.RLock()
defer u.lock.RUnlock()
return u.creds.Name
}
// IsConnected returns whether user is logged in.
func (u *User) IsConnected() bool {
u.lock.RLock()
defer u.lock.RUnlock()
return u.creds.IsConnected()
}
// IsCombinedAddressMode returns whether user is set in combined or split mode.
// Combined mode is the default mode and is what users typically need.
// Split mode is mostly for outlook as it cannot handle sending e-mails from an
// address other than the primary one.
func (u *User) IsCombinedAddressMode() bool {
if u.store != nil {
return u.store.IsCombinedMode()
}
return u.creds.IsCombinedAddressMode
}
// GetPrimaryAddress returns the user's original address (which is
// not necessarily the same as the primary address, because a primary address
// might be an alias and be in position one).
func (u *User) GetPrimaryAddress() string {
u.lock.RLock()
defer u.lock.RUnlock()
return u.creds.EmailList()[0]
}
// GetStoreAddresses returns all addresses used by the store (so in combined mode,
// that's just the original address, but in split mode, that's all active addresses).
func (u *User) GetStoreAddresses() []string {
u.lock.RLock()
defer u.lock.RUnlock()
if u.IsCombinedAddressMode() {
return u.creds.EmailList()[:1]
}
return u.creds.EmailList()
}
// getStoreAddresses returns a user's used addresses (with the original address in first place).
func (u *User) getStoreAddresses() []string { // nolint[unused]
addrInfo, err := u.store.GetAddressInfo()
if err != nil {
u.log.WithError(err).Error("Failed getting address info from store")
return nil
}
addresses := []string{}
for _, addr := range addrInfo {
addresses = append(addresses, addr.Address)
}
if u.IsCombinedAddressMode() {
return addresses[:1]
}
return addresses
}
// GetAddresses returns list of all addresses.
func (u *User) GetAddresses() []string {
u.lock.RLock()
defer u.lock.RUnlock()
return u.creds.EmailList()
}
// GetAddressID returns the API ID of the given address.
func (u *User) GetAddressID(address string) (id string, err error) {
u.lock.RLock()
defer u.lock.RUnlock()
address = strings.ToLower(address)
if u.store == nil {
err = errors.New("store is not initialised")
return
}
return u.store.GetAddressID(address)
}
// GetBridgePassword returns bridge password. This is not a password of the PM
// account, but generated password for local purposes to not use a PM account
// in the clients (such as Thunderbird).
func (u *User) GetBridgePassword() string {
u.lock.RLock()
defer u.lock.RUnlock()
return u.creds.BridgePassword
}
// CheckBridgeLogin checks whether the user is logged in and the bridge
// password is correct.
func (u *User) CheckBridgeLogin(password string) error {
if isApplicationOutdated {
u.listener.Emit(events.UpgradeApplicationEvent, "")
return pmapi.ErrUpgradeApplication
}
u.lock.RLock()
defer u.lock.RUnlock()
// True here because users should be notified by popup of auth failure.
if err := u.authorizeIfNecessary(true); err != nil {
u.log.WithError(err).Error("Failed to authorize user")
return err
}
return u.creds.CheckPassword(password)
}
// UpdateUser updates user details from API and saves to the credentials.
func (u *User) UpdateUser() error {
u.lock.Lock()
defer u.lock.Unlock()
if err := u.authorizeIfNecessary(true); err != nil {
return errors.Wrap(err, "cannot update user")
}
_, err := u.apiClient.UpdateUser()
if err != nil {
return err
}
if _, err = u.apiClient.Unlock(u.creds.MailboxPassword); err != nil {
return err
}
if err := u.apiClient.UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
return err
}
emails := u.apiClient.Addresses().ActiveEmails()
if err := u.credStorer.UpdateEmails(u.userID, emails); err != nil {
return err
}
u.refreshFromCredentials()
return nil
}
// SwitchAddressMode changes mode from combined to split and vice versa. The mode to switch to is determined by the
// state of the user's credentials in the credentials store. See `IsCombinedAddressMode` for more details.
func (u *User) SwitchAddressMode() (err error) {
u.log.Trace("Switching user address mode")
u.lock.Lock()
defer u.lock.Unlock()
u.closeAllConnections()
if u.store == nil {
err = errors.New("store is not initialised")
return
}
newAddressModeState := !u.IsCombinedAddressMode()
if err = u.store.UseCombinedMode(newAddressModeState); err != nil {
u.log.WithError(err).Error("Could not switch store address mode")
return
}
if u.creds.IsCombinedAddressMode != newAddressModeState {
if err = u.credStorer.SwitchAddressMode(u.userID); err != nil {
u.log.WithError(err).Error("Could not switch credentials store address mode")
return
}
}
u.refreshFromCredentials()
return err
}
// 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.
func (u *User) logout() error {
u.lock.Lock()
wasConnected := u.creds.IsConnected()
u.lock.Unlock()
err := u.Logout()
if wasConnected {
u.listener.Emit(events.LogoutEvent, u.userID)
u.listener.Emit(events.UserRefreshEvent, u.userID)
}
return err
}
// Logout logs out the user from pmapi, the credentials store, the mail store, and tries to remove as much
// sensitive data as possible.
func (u *User) Logout() (err error) {
u.lock.Lock()
defer u.lock.Unlock()
u.log.Debug("Logging out user")
if !u.creds.IsConnected() {
return
}
u.unlockingKeyringLock.Lock()
u.wasKeyringUnlocked = false
u.unlockingKeyringLock.Unlock()
if err = u.apiClient.Logout(); err != nil {
u.log.WithError(err).Warn("Could not log user out from API client")
}
u.apiClient.SetAuths(nil)
// Logout needs to stop auth channel so when user logs back in
// it can register again with new client.
// Note: be careful to not close channel twice.
if u.authChannel != nil {
close(u.authChannel)
u.authChannel = nil
}
if err = u.credStorer.Logout(u.userID); err != nil {
u.log.WithError(err).Warn("Could not log user out from credentials store")
if err = u.credStorer.Delete(u.userID); err != nil {
u.log.WithError(err).Error("Could not delete user from credentials store")
}
}
u.refreshFromCredentials()
// Do not close whole store, just event loop. Some information might be needed offline (e.g. addressID)
u.closeEventLoop()
u.closeAllConnections()
runtime.GC()
return err
}
func (u *User) refreshFromCredentials() {
if credentials, err := u.credStorer.Get(u.userID); err != nil {
log.Error("Cannot update credentials: ", err)
} else {
u.creds = credentials
}
}
func (u *User) closeEventLoop() {
if u.store == nil {
return
}
u.store.CloseEventLoop()
}
// closeAllConnections calls CloseConnection for all users addresses.
func (u *User) closeAllConnections() {
for _, address := range u.creds.EmailList() {
u.CloseConnection(address)
}
if u.store != nil {
u.store.SetIMAPUpdateChannel(nil)
}
}
// CloseConnection emits closeConnection event on `address` which should close all active connection.
func (u *User) CloseConnection(address string) {
u.listener.Emit(events.CloseConnectionEvent, address)
}
func (u *User) GetStore() *store.Store {
return u.store
}
func (u *User) HasAPIAuth() bool {
return u.hasAPIAuth
}

View File

@ -0,0 +1,209 @@
// 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 bridge
import (
"testing"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestUpdateUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUser(m)
defer cleanUpUserData(user)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
m.pmapiClient.EXPECT().UpdateUser().Return(nil, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte(testCredentials.MailboxPassword)).Return(nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.credentialsStore.EXPECT().UpdateEmails("user", []string{testPMAPIAddress.Email})
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
assert.NoError(t, user.UpdateUser())
waitForEvents()
}
func TestUserSwitchAddressMode(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUser(m)
defer cleanUpUserData(user)
assert.True(t, user.store.IsCombinedMode())
assert.True(t, user.creds.IsCombinedAddressMode)
assert.True(t, user.IsCombinedAddressMode())
waitForEvents()
gomock.InOrder(
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me"),
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
m.credentialsStore.EXPECT().SwitchAddressMode("user").Return(nil),
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsSplit, nil),
)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
assert.NoError(t, user.SwitchAddressMode())
assert.False(t, user.store.IsCombinedMode())
assert.False(t, user.creds.IsCombinedAddressMode)
assert.False(t, user.IsCombinedAddressMode())
gomock.InOrder(
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "users@pm.me"),
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "anotheruser@pm.me"),
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "alsouser@pm.me"),
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
m.credentialsStore.EXPECT().SwitchAddressMode("user").Return(nil),
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
assert.NoError(t, user.SwitchAddressMode())
assert.True(t, user.store.IsCombinedMode())
assert.True(t, user.creds.IsCombinedAddressMode)
assert.True(t, user.IsCombinedAddressMode())
waitForEvents()
}
func TestLogoutUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUserForLogout(m)
defer cleanUpUserData(user)
m.pmapiClient.EXPECT().Logout().Return(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
err := user.Logout()
waitForEvents()
assert.NoError(t, err)
}
func TestLogoutUserFailsLogout(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUserForLogout(m)
defer cleanUpUserData(user)
m.pmapiClient.EXPECT().Logout().Return(nil)
m.credentialsStore.EXPECT().Logout("user").Return(errors.New("logout failed"))
m.credentialsStore.EXPECT().Delete("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
err := user.Logout()
waitForEvents()
assert.NoError(t, err)
}
func TestCheckBridgeLogin(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUser(m)
defer cleanUpUserData(user)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
err := user.CheckBridgeLogin(testCredentials.BridgePassword)
waitForEvents()
assert.NoError(t, err)
}
func TestCheckBridgeLoginUpgradeApplication(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUser(m)
defer cleanUpUserData(user)
m.eventListener.EXPECT().Emit(events.UpgradeApplicationEvent, "")
isApplicationOutdated = true
err := user.CheckBridgeLogin("any-pass")
waitForEvents()
isApplicationOutdated = false
assert.Equal(t, pmapi.ErrUpgradeApplication, err)
}
func TestCheckBridgeLoginLoggedOut(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
user, _ := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.pmapiClient, m.storeCache, "/tmp")
m.pmapiClient.EXPECT().ListLabels().Return(nil, errors.New("ErrUnauthorized"))
m.pmapiClient.EXPECT().Addresses().Return(nil)
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
_ = user.init(nil, m.pmapiClient)
defer cleanUpUserData(user)
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
err := user.CheckBridgeLogin(testCredentialsDisconnected.BridgePassword)
waitForEvents()
assert.Equal(t, "bridge account is logged out, use bridge to login again", err.Error())
}
func TestCheckBridgeLoginBadPassword(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUser(m)
defer cleanUpUserData(user)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
err := user.CheckBridgeLogin("wrong!")
waitForEvents()
assert.Equal(t, "backend/credentials: incorrect password", err.Error())
}

View File

@ -0,0 +1,188 @@
// 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 bridge
import (
"errors"
"testing"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
a "github.com/stretchr/testify/assert"
)
func TestNewUserNoCredentialsStore(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(nil, errors.New("fail"))
_, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.pmapiClient, m.storeCache, "/tmp")
a.Error(t, err)
}
func TestNewUserBridgeOutdated(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().Logout("user").Return(nil).AnyTimes()
m.pmapiClient.EXPECT().AuthRefresh("token").Return(nil, pmapi.ErrUpgradeApplication).AnyTimes()
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.eventListener.EXPECT().Emit(events.UpgradeApplicationEvent, "").AnyTimes()
m.pmapiClient.EXPECT().ListLabels().Return(nil, pmapi.ErrUpgradeApplication)
m.pmapiClient.EXPECT().Addresses().Return(nil)
checkNewUser(m)
}
func TestNewUserNoInternetConnection(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.pmapiClient.EXPECT().AuthRefresh("token").Return(nil, pmapi.ErrAPINotReachable).AnyTimes()
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.eventListener.EXPECT().Emit(events.InternetOffEvent, "").AnyTimes()
m.pmapiClient.EXPECT().Addresses().Return(nil)
m.pmapiClient.EXPECT().ListLabels().Return(nil, pmapi.ErrAPINotReachable)
m.pmapiClient.EXPECT().GetEvent("").Return(nil, pmapi.ErrAPINotReachable).AnyTimes()
checkNewUser(m)
}
func TestNewUserAuthRefreshFails(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.pmapiClient.EXPECT().AuthRefresh("token").Return(nil, errors.New("bad token")).AnyTimes()
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
m.pmapiClient.EXPECT().Logout().Return(nil)
m.pmapiClient.EXPECT().SetAuths(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
checkNewUserDisconnected(m)
}
func TestNewUserUnlockFails(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, errors.New("bad password"))
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
m.pmapiClient.EXPECT().Logout().Return(nil)
m.pmapiClient.EXPECT().SetAuths(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
checkNewUserDisconnected(m)
}
func TestNewUserUnlockAddressesFails(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(errors.New("bad password"))
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
m.pmapiClient.EXPECT().Logout().Return(nil)
m.pmapiClient.EXPECT().SetAuths(nil)
m.credentialsStore.EXPECT().Logout("user").Return(nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
checkNewUserDisconnected(m)
}
func TestNewUser(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil)
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil)
checkNewUser(m)
}
func checkNewUser(m mocks) {
user, _ := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.pmapiClient, m.storeCache, "/tmp")
defer cleanUpUserData(user)
_ = user.init(nil, m.pmapiClient)
waitForEvents()
a.Equal(m.t, testCredentials, user.creds)
}
func checkNewUserDisconnected(m mocks) {
user, _ := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.pmapiClient, m.storeCache, "/tmp")
defer cleanUpUserData(user)
_ = user.init(nil, m.pmapiClient)
waitForEvents()
a.Equal(m.t, testCredentialsDisconnected, user.creds)
}
func _TestUserEventRefreshUpdatesAddresses(t *testing.T) { // nolint[funlen]
a.Fail(t, "not implemented")
}

View File

@ -0,0 +1,113 @@
// 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 bridge
import (
"testing"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
gomock "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func testNewUser(m mocks) *User {
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
// Expectations for initial sync (when loading existing user from credentials store).
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil)
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress})
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil).AnyTimes()
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil)
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).AnyTimes()
user, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.pmapiClient, m.storeCache, "/tmp")
assert.NoError(m.t, err)
err = user.init(nil, m.pmapiClient)
assert.NoError(m.t, err)
return user
}
func testNewUserForLogout(m mocks) *User {
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil).Times(2)
m.credentialsStore.EXPECT().UpdateToken("user", ":reftok").Return(nil)
m.pmapiClient.EXPECT().SetAuths(gomock.Any())
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil)
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
m.pmapiClient.EXPECT().Unlock("pass").Return(nil, nil)
m.pmapiClient.EXPECT().UnlockAddresses([]byte("pass")).Return(nil)
// These may or may not be hit depending on how fast the log out happens.
m.pmapiClient.EXPECT().SetAuths(nil).AnyTimes()
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil).AnyTimes()
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}).AnyTimes()
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil)
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil).AnyTimes()
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).AnyTimes()
user, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.pmapiClient, m.storeCache, "/tmp")
assert.NoError(m.t, err)
err = user.init(nil, m.pmapiClient)
assert.NoError(m.t, err)
return user
}
func cleanUpUserData(u *User) {
_ = u.clearStore()
}
func _TestNeverLongStorePath(t *testing.T) { // nolint[unused]
assert.Fail(t, "not implemented")
}
func TestClearStoreWithStore(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUserForLogout(m)
defer cleanUpUserData(user)
require.Nil(t, user.store.Close())
user.store = nil
assert.Nil(t, user.clearStore())
}
func TestClearStoreWithoutStore(t *testing.T) {
m := initMocks(t)
defer m.ctrl.Finish()
user := testNewUserForLogout(m)
defer cleanUpUserData(user)
assert.NotNil(t, user.store)
assert.Nil(t, user.clearStore())
}

View File

@ -0,0 +1,41 @@
// 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 bridge
import (
"fmt"
"runtime"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
)
// UpdateCurrentUserAgent updates user agent on pmapi so each request has this
// information in headers for statistic purposes.
func UpdateCurrentUserAgent(bridgeVersion, os, clientName, clientVersion string) {
if os == "" {
os = runtime.GOOS
}
mailClient := "unknown client"
if clientName != "" {
mailClient = clientName
if clientVersion != "" {
mailClient += "/" + clientVersion
}
}
pmapi.CurrentUserAgent = fmt.Sprintf("Bridge/%s (%s; %s)", bridgeVersion, os, mailClient)
}

View File

@ -0,0 +1,51 @@
// 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 bridge
import (
"runtime"
"testing"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/stretchr/testify/assert"
)
func TestUpdateCurrentUserAgentGOOS(t *testing.T) {
UpdateCurrentUserAgent("ver", "", "", "")
assert.Equal(t, "Bridge/ver ("+runtime.GOOS+"; unknown client)", pmapi.CurrentUserAgent)
}
func TestUpdateCurrentUserAgentOS(t *testing.T) {
UpdateCurrentUserAgent("ver", "os", "", "")
assert.Equal(t, "Bridge/ver (os; unknown client)", pmapi.CurrentUserAgent)
}
func TestUpdateCurrentUserAgentClientVer(t *testing.T) {
UpdateCurrentUserAgent("ver", "os", "", "cver")
assert.Equal(t, "Bridge/ver (os; unknown client)", pmapi.CurrentUserAgent)
}
func TestUpdateCurrentUserAgentClientName(t *testing.T) {
UpdateCurrentUserAgent("ver", "os", "mail", "")
assert.Equal(t, "Bridge/ver (os; mail)", pmapi.CurrentUserAgent)
}
func TestUpdateCurrentUserAgentClientNameAndVersion(t *testing.T) {
UpdateCurrentUserAgent("ver", "os", "mail", "cver")
assert.Equal(t, "Bridge/ver (os; mail/cver)", pmapi.CurrentUserAgent)
}