mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-15 14:56:42 +00:00
GODT-35: New pmapi client and manager using resty
This commit is contained in:
@ -133,7 +133,7 @@ func TestUsersFinishLoginExistingDisconnectedUser(t *testing.T) {
|
||||
m.credentialsStore.EXPECT().Get("user").Return(&loggedOutCreds, nil),
|
||||
|
||||
// store.New() in user.init
|
||||
m.pmapiClient.EXPECT().ListLabels().Return(nil, pmapi.ErrInvalidToken),
|
||||
m.pmapiClient.EXPECT().ListLabels().Return(nil, pmapi.ErrUnauthorized),
|
||||
m.pmapiClient.EXPECT().Addresses().Return(nil),
|
||||
|
||||
// getAPIUser() loads user info from API (e.g. userID).
|
||||
@ -149,3 +149,13 @@ func (s *Credentials) Logout() {
|
||||
func (s *Credentials) IsConnected() bool {
|
||||
return s.APIToken != "" && s.MailboxPassword != ""
|
||||
}
|
||||
|
||||
func (s *Credentials) SplitAPIToken() (string, string, error) {
|
||||
split := strings.Split(s.APIToken, ":")
|
||||
|
||||
if len(split) != 2 {
|
||||
return "", "", errors.New("malformed API token")
|
||||
}
|
||||
|
||||
return split[0], split[1], nil
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ func NewStore(keychain *keychain.Keychain) *Store {
|
||||
return &Store{secrets: keychain}
|
||||
}
|
||||
|
||||
func (s *Store) Add(userID, userName, apiToken, mailboxPassword string, emails []string) (creds *Credentials, err error) {
|
||||
func (s *Store) Add(userID, userName, uid, ref, mailboxPassword string, emails []string) (*Credentials, error) {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
@ -49,10 +49,10 @@ func (s *Store) Add(userID, userName, apiToken, mailboxPassword string, emails [
|
||||
"emails": emails,
|
||||
}).Trace("Adding new credentials")
|
||||
|
||||
creds = &Credentials{
|
||||
creds := &Credentials{
|
||||
UserID: userID,
|
||||
Name: userName,
|
||||
APIToken: apiToken,
|
||||
APIToken: uid + ":" + ref,
|
||||
MailboxPassword: mailboxPassword,
|
||||
IsHidden: false,
|
||||
}
|
||||
@ -72,82 +72,82 @@ func (s *Store) Add(userID, userName, apiToken, mailboxPassword string, emails [
|
||||
creds.Timestamp = time.Now().Unix()
|
||||
}
|
||||
|
||||
if err = s.saveCredentials(creds); err != nil {
|
||||
return
|
||||
if err := s.saveCredentials(creds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return creds, err
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
func (s *Store) SwitchAddressMode(userID string) error {
|
||||
func (s *Store) SwitchAddressMode(userID string) (*Credentials, error) {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
credentials, err := s.get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credentials.IsCombinedAddressMode = !credentials.IsCombinedAddressMode
|
||||
credentials.BridgePassword = generatePassword()
|
||||
|
||||
return s.saveCredentials(credentials)
|
||||
return credentials, s.saveCredentials(credentials)
|
||||
}
|
||||
|
||||
func (s *Store) UpdateEmails(userID string, emails []string) error {
|
||||
func (s *Store) UpdateEmails(userID string, emails []string) (*Credentials, error) {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
credentials, err := s.get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credentials.SetEmailList(emails)
|
||||
|
||||
return s.saveCredentials(credentials)
|
||||
return credentials, s.saveCredentials(credentials)
|
||||
}
|
||||
|
||||
func (s *Store) UpdatePassword(userID, password string) error {
|
||||
func (s *Store) UpdatePassword(userID, password string) (*Credentials, error) {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
credentials, err := s.get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credentials.MailboxPassword = password
|
||||
|
||||
return s.saveCredentials(credentials)
|
||||
return credentials, s.saveCredentials(credentials)
|
||||
}
|
||||
|
||||
func (s *Store) UpdateToken(userID, apiToken string) error {
|
||||
func (s *Store) UpdateToken(userID, uid, ref string) (*Credentials, error) {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
credentials, err := s.get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credentials.APIToken = apiToken
|
||||
credentials.APIToken = uid + ":" + ref
|
||||
|
||||
return s.saveCredentials(credentials)
|
||||
return credentials, s.saveCredentials(credentials)
|
||||
}
|
||||
|
||||
func (s *Store) Logout(userID string) error {
|
||||
func (s *Store) Logout(userID string) (*Credentials, error) {
|
||||
storeLocker.Lock()
|
||||
defer storeLocker.Unlock()
|
||||
|
||||
credentials, err := s.get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credentials.Logout()
|
||||
|
||||
return s.saveCredentials(credentials)
|
||||
return credentials, s.saveCredentials(credentials)
|
||||
}
|
||||
|
||||
// List returns a list of usernames that have credentials stored.
|
||||
@ -249,7 +249,7 @@ func (s *Store) get(userID string) (creds *Credentials, err error) {
|
||||
}
|
||||
|
||||
// saveCredentials encrypts and saves password to the keychain store.
|
||||
func (s *Store) saveCredentials(credentials *Credentials) (err error) {
|
||||
func (s *Store) saveCredentials(credentials *Credentials) error {
|
||||
credentials.Version = keychain.Version
|
||||
|
||||
return s.secrets.Put(credentials.UserID, credentials.Marshal())
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/ProtonMail/proton-bridge/internal/users (interfaces: Locator,PanicHandler,ClientManager,CredentialsStorer,StoreMaker)
|
||||
// Source: github.com/ProtonMail/proton-bridge/internal/users (interfaces: Locator,PanicHandler,CredentialsStorer,StoreMaker)
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
@ -9,7 +9,6 @@ import (
|
||||
|
||||
store "github.com/ProtonMail/proton-bridge/internal/store"
|
||||
credentials "github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
@ -85,109 +84,6 @@ func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
|
||||
}
|
||||
|
||||
// MockClientManager is a mock of ClientManager interface
|
||||
type MockClientManager struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockClientManagerMockRecorder
|
||||
}
|
||||
|
||||
// MockClientManagerMockRecorder is the mock recorder for MockClientManager
|
||||
type MockClientManagerMockRecorder struct {
|
||||
mock *MockClientManager
|
||||
}
|
||||
|
||||
// NewMockClientManager creates a new mock instance
|
||||
func NewMockClientManager(ctrl *gomock.Controller) *MockClientManager {
|
||||
mock := &MockClientManager{ctrl: ctrl}
|
||||
mock.recorder = &MockClientManagerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockClientManager) EXPECT() *MockClientManagerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AllowProxy mocks base method
|
||||
func (m *MockClientManager) AllowProxy() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "AllowProxy")
|
||||
}
|
||||
|
||||
// AllowProxy indicates an expected call of AllowProxy
|
||||
func (mr *MockClientManagerMockRecorder) AllowProxy() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllowProxy", reflect.TypeOf((*MockClientManager)(nil).AllowProxy))
|
||||
}
|
||||
|
||||
// CheckConnection mocks base method
|
||||
func (m *MockClientManager) CheckConnection() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CheckConnection")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CheckConnection indicates an expected call of CheckConnection
|
||||
func (mr *MockClientManagerMockRecorder) CheckConnection() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckConnection", reflect.TypeOf((*MockClientManager)(nil).CheckConnection))
|
||||
}
|
||||
|
||||
// DisallowProxy mocks base method
|
||||
func (m *MockClientManager) DisallowProxy() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "DisallowProxy")
|
||||
}
|
||||
|
||||
// DisallowProxy indicates an expected call of DisallowProxy
|
||||
func (mr *MockClientManagerMockRecorder) DisallowProxy() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisallowProxy", reflect.TypeOf((*MockClientManager)(nil).DisallowProxy))
|
||||
}
|
||||
|
||||
// GetAnonymousClient mocks base method
|
||||
func (m *MockClientManager) GetAnonymousClient() pmapi.Client {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAnonymousClient")
|
||||
ret0, _ := ret[0].(pmapi.Client)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetAnonymousClient indicates an expected call of GetAnonymousClient
|
||||
func (mr *MockClientManagerMockRecorder) GetAnonymousClient() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAnonymousClient", reflect.TypeOf((*MockClientManager)(nil).GetAnonymousClient))
|
||||
}
|
||||
|
||||
// GetAuthUpdateChannel mocks base method
|
||||
func (m *MockClientManager) GetAuthUpdateChannel() chan pmapi.ClientAuth {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAuthUpdateChannel")
|
||||
ret0, _ := ret[0].(chan pmapi.ClientAuth)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetAuthUpdateChannel indicates an expected call of GetAuthUpdateChannel
|
||||
func (mr *MockClientManagerMockRecorder) GetAuthUpdateChannel() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthUpdateChannel", reflect.TypeOf((*MockClientManager)(nil).GetAuthUpdateChannel))
|
||||
}
|
||||
|
||||
// GetClient mocks base method
|
||||
func (m *MockClientManager) GetClient(arg0 string) pmapi.Client {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetClient", arg0)
|
||||
ret0, _ := ret[0].(pmapi.Client)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetClient indicates an expected call of GetClient
|
||||
func (mr *MockClientManagerMockRecorder) GetClient(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClient", reflect.TypeOf((*MockClientManager)(nil).GetClient), arg0)
|
||||
}
|
||||
|
||||
// MockCredentialsStorer is a mock of CredentialsStorer interface
|
||||
type MockCredentialsStorer struct {
|
||||
ctrl *gomock.Controller
|
||||
@ -212,18 +108,18 @@ func (m *MockCredentialsStorer) EXPECT() *MockCredentialsStorerMockRecorder {
|
||||
}
|
||||
|
||||
// Add mocks base method
|
||||
func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3 string, arg4 []string) (*credentials.Credentials, error) {
|
||||
func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3, arg4 string, arg5 []string) (*credentials.Credentials, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Add", arg0, arg1, arg2, arg3, arg4)
|
||||
ret := m.ctrl.Call(m, "Add", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
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 {
|
||||
func (mr *MockCredentialsStorerMockRecorder) Add(arg0, arg1, arg2, arg3, arg4, arg5 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)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockCredentialsStorer)(nil).Add), arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
}
|
||||
|
||||
// Delete mocks base method
|
||||
@ -271,11 +167,12 @@ func (mr *MockCredentialsStorerMockRecorder) List() *gomock.Call {
|
||||
}
|
||||
|
||||
// Logout mocks base method
|
||||
func (m *MockCredentialsStorer) Logout(arg0 string) error {
|
||||
func (m *MockCredentialsStorer) Logout(arg0 string) (*credentials.Credentials, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Logout", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
ret0, _ := ret[0].(*credentials.Credentials)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Logout indicates an expected call of Logout
|
||||
@ -285,11 +182,12 @@ func (mr *MockCredentialsStorerMockRecorder) Logout(arg0 interface{}) *gomock.Ca
|
||||
}
|
||||
|
||||
// SwitchAddressMode mocks base method
|
||||
func (m *MockCredentialsStorer) SwitchAddressMode(arg0 string) error {
|
||||
func (m *MockCredentialsStorer) SwitchAddressMode(arg0 string) (*credentials.Credentials, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SwitchAddressMode", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
ret0, _ := ret[0].(*credentials.Credentials)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SwitchAddressMode indicates an expected call of SwitchAddressMode
|
||||
@ -299,11 +197,12 @@ func (mr *MockCredentialsStorerMockRecorder) SwitchAddressMode(arg0 interface{})
|
||||
}
|
||||
|
||||
// UpdateEmails mocks base method
|
||||
func (m *MockCredentialsStorer) UpdateEmails(arg0 string, arg1 []string) error {
|
||||
func (m *MockCredentialsStorer) UpdateEmails(arg0 string, arg1 []string) (*credentials.Credentials, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateEmails", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
ret0, _ := ret[0].(*credentials.Credentials)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateEmails indicates an expected call of UpdateEmails
|
||||
@ -313,11 +212,12 @@ func (mr *MockCredentialsStorerMockRecorder) UpdateEmails(arg0, arg1 interface{}
|
||||
}
|
||||
|
||||
// UpdatePassword mocks base method
|
||||
func (m *MockCredentialsStorer) UpdatePassword(arg0, arg1 string) error {
|
||||
func (m *MockCredentialsStorer) UpdatePassword(arg0, arg1 string) (*credentials.Credentials, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdatePassword", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
ret0, _ := ret[0].(*credentials.Credentials)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdatePassword indicates an expected call of UpdatePassword
|
||||
@ -327,17 +227,18 @@ func (mr *MockCredentialsStorerMockRecorder) UpdatePassword(arg0, arg1 interface
|
||||
}
|
||||
|
||||
// UpdateToken mocks base method
|
||||
func (m *MockCredentialsStorer) UpdateToken(arg0, arg1 string) error {
|
||||
func (m *MockCredentialsStorer) UpdateToken(arg0, arg1, arg2 string) (*credentials.Credentials, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateToken", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
ret := m.ctrl.Call(m, "UpdateToken", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*credentials.Credentials)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateToken indicates an expected call of UpdateToken
|
||||
func (mr *MockCredentialsStorerMockRecorder) UpdateToken(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockCredentialsStorerMockRecorder) UpdateToken(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateToken", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateToken), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateToken", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateToken), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// MockStoreMaker is a mock of StoreMaker interface
|
||||
|
||||
@ -20,14 +20,8 @@ package users
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/internal/store"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
type Configer interface {
|
||||
GetAppVersion() string
|
||||
GetAPIConfig() *pmapi.ClientConfig
|
||||
}
|
||||
|
||||
type Locator interface {
|
||||
Clear() error
|
||||
}
|
||||
@ -38,25 +32,16 @@ type PanicHandler interface {
|
||||
|
||||
type CredentialsStorer interface {
|
||||
List() (userIDs []string, err error)
|
||||
Add(userID, userName, apiToken, mailboxPassword string, emails []string) (*credentials.Credentials, error)
|
||||
Add(userID, userName, uid, ref, mailboxPassword string, emails []string) (*credentials.Credentials, error)
|
||||
Get(userID string) (*credentials.Credentials, error)
|
||||
SwitchAddressMode(userID string) error
|
||||
UpdateEmails(userID string, emails []string) error
|
||||
UpdatePassword(userID, password string) error
|
||||
UpdateToken(userID, apiToken string) error
|
||||
Logout(userID string) error
|
||||
SwitchAddressMode(userID string) (*credentials.Credentials, error)
|
||||
UpdateEmails(userID string, emails []string) (*credentials.Credentials, error)
|
||||
UpdatePassword(userID, password string) (*credentials.Credentials, error)
|
||||
UpdateToken(userID, uid, ref string) (*credentials.Credentials, error)
|
||||
Logout(userID string) (*credentials.Credentials, error)
|
||||
Delete(userID string) error
|
||||
}
|
||||
|
||||
type ClientManager interface {
|
||||
GetClient(userID string) pmapi.Client
|
||||
GetAnonymousClient() pmapi.Client
|
||||
AllowProxy()
|
||||
DisallowProxy()
|
||||
GetAuthUpdateChannel() chan pmapi.ClientAuth
|
||||
CheckConnection() error
|
||||
}
|
||||
|
||||
type StoreMaker interface {
|
||||
New(user store.BridgeUser) (*store.Store, error)
|
||||
Remove(userID string) error
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -36,11 +37,11 @@ var ErrLoggedOutUser = errors.New("account is logged out, use the app to login a
|
||||
|
||||
// User is a struct on top of API client and credentials store.
|
||||
type User struct {
|
||||
log *logrus.Entry
|
||||
panicHandler PanicHandler
|
||||
listener listener.Listener
|
||||
clientManager ClientManager
|
||||
credStorer CredentialsStorer
|
||||
log *logrus.Entry
|
||||
panicHandler PanicHandler
|
||||
listener listener.Listener
|
||||
client pmapi.Client
|
||||
credStorer CredentialsStorer
|
||||
|
||||
storeFactory StoreMaker
|
||||
store *store.Store
|
||||
@ -48,75 +49,76 @@ type User struct {
|
||||
userID string
|
||||
creds *credentials.Credentials
|
||||
|
||||
lock sync.RWMutex
|
||||
isAuthorized bool
|
||||
lock sync.RWMutex
|
||||
|
||||
useOnlyActiveAddresses bool
|
||||
}
|
||||
|
||||
// newUser creates a new user.
|
||||
// The user is initially disconnected and must be connected by calling connect().
|
||||
func newUser(
|
||||
panicHandler PanicHandler,
|
||||
userID string,
|
||||
eventListener listener.Listener,
|
||||
credStorer CredentialsStorer,
|
||||
clientManager ClientManager,
|
||||
storeFactory StoreMaker,
|
||||
) (u *User, err error) {
|
||||
useOnlyActiveAddresses bool,
|
||||
) (*User, *credentials.Credentials, 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")
|
||||
return nil, nil, errors.Wrap(err, "failed to load user credentials")
|
||||
}
|
||||
|
||||
u = &User{
|
||||
log: log,
|
||||
panicHandler: panicHandler,
|
||||
listener: eventListener,
|
||||
credStorer: credStorer,
|
||||
clientManager: clientManager,
|
||||
storeFactory: storeFactory,
|
||||
userID: userID,
|
||||
creds: creds,
|
||||
}
|
||||
|
||||
return
|
||||
return &User{
|
||||
log: log,
|
||||
panicHandler: panicHandler,
|
||||
listener: eventListener,
|
||||
credStorer: credStorer,
|
||||
storeFactory: storeFactory,
|
||||
userID: userID,
|
||||
creds: creds,
|
||||
useOnlyActiveAddresses: useOnlyActiveAddresses,
|
||||
}, creds, nil
|
||||
}
|
||||
|
||||
func (u *User) client() pmapi.Client {
|
||||
return u.clientManager.GetClient(u.userID)
|
||||
}
|
||||
// connect connects a user. This includes
|
||||
// - providing it with an authorised API client
|
||||
// - loading its credentials from the credentials store
|
||||
// - loading and unlocking its PGP keys
|
||||
// - loading its store
|
||||
func (u *User) connect(ctx context.Context, client pmapi.Client, creds *credentials.Credentials) error {
|
||||
u.log.Info("Connecting user")
|
||||
|
||||
// init initialises a user. This includes reloading its credentials from the credentials store
|
||||
// (such as when logging out and back in, you need to reload the credentials because the new credentials will
|
||||
// have the apitoken and password), authorising the user against the api, loading the user store (creating a new one
|
||||
// if necessary), and setting the imap idle updates channel (used to send imap idle updates to the imap backend if
|
||||
// something in the store changed).
|
||||
func (u *User) init() (err error) {
|
||||
u.log.Info("Initialising user")
|
||||
// Connected users have an API client.
|
||||
u.client = client
|
||||
|
||||
// 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")
|
||||
}
|
||||
// FIXME(conman): How to remove this auth handler when user is disconnected?
|
||||
u.client.AddAuthHandler(u.handleAuth)
|
||||
|
||||
// Save the latest credentials for the user.
|
||||
u.creds = creds
|
||||
|
||||
// Try to authorise the user if they aren't already authorised.
|
||||
// Note: we still allow users to set up accounts if the internet is off.
|
||||
if authErr := u.authorizeIfNecessary(false); authErr != nil {
|
||||
switch errors.Cause(authErr) {
|
||||
case pmapi.ErrAPINotReachable, pmapi.ErrUpgradeApplication, ErrLoggedOutUser:
|
||||
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")
|
||||
// Connected users have unlocked keys.
|
||||
// FIXME(conman): clients should always be authorized! This is a workaround to avoid a major refactor :(
|
||||
if u.creds.IsConnected() {
|
||||
if err := u.client.Unlock(ctx, []byte(u.creds.MailboxPassword)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Connected users have a store.
|
||||
if err := u.loadStore(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) loadStore() error {
|
||||
// Logged-out user keeps store running to access offline data.
|
||||
// Therefore it is necessary to close it before re-init.
|
||||
if u.store != nil {
|
||||
@ -125,93 +127,28 @@ func (u *User) init() (err error) {
|
||||
}
|
||||
u.store = nil
|
||||
}
|
||||
|
||||
store, err := u.storeFactory.New(u)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create store")
|
||||
}
|
||||
|
||||
u.store = store
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 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.
|
||||
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.isAuthorized {
|
||||
// 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, pmapi.ErrAPINotReachable: // Ignore these errors.
|
||||
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 {
|
||||
if u.client().IsUnlocked() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := u.client().Unlock([]byte(u.creds.MailboxPassword)); err != nil {
|
||||
return errors.Wrap(err, "failed to unlock user")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if _, err := u.client().AuthRefresh(u.creds.APIToken); err != nil {
|
||||
return errors.Wrap(err, "failed to refresh API auth")
|
||||
}
|
||||
|
||||
if err := u.client().Unlock([]byte(u.creds.MailboxPassword)); err != nil {
|
||||
return errors.Wrap(err, "failed to unlock user")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) updateAuthToken(auth *pmapi.Auth) {
|
||||
func (u *User) handleAuth(auth *pmapi.Auth) error {
|
||||
u.log.Debug("User received auth")
|
||||
|
||||
if err := u.credStorer.UpdateToken(u.userID, auth.GenToken()); err != nil {
|
||||
u.log.WithError(err).Error("Failed to update refresh token in credentials store")
|
||||
return
|
||||
creds, err := u.credStorer.UpdateToken(u.userID, auth.UID, auth.RefreshToken)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to update refresh token in credentials store")
|
||||
}
|
||||
|
||||
u.refreshFromCredentials()
|
||||
u.creds = creds
|
||||
|
||||
u.isAuthorized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// clearStore removes the database.
|
||||
@ -248,7 +185,7 @@ func (u *User) closeStore() error {
|
||||
// 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() pmapi.Client {
|
||||
return u.client()
|
||||
return u.client
|
||||
}
|
||||
|
||||
// ID returns the user's userID.
|
||||
@ -272,6 +209,10 @@ func (u *User) IsConnected() bool {
|
||||
return u.creds.IsConnected()
|
||||
}
|
||||
|
||||
func (u *User) GetClient() pmapi.Client {
|
||||
return u.client
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -345,7 +286,7 @@ func (u *User) GetAddressID(address string) (id string, err error) {
|
||||
return u.store.GetAddressID(address)
|
||||
}
|
||||
|
||||
addresses := u.client().Addresses()
|
||||
addresses := u.client.Addresses()
|
||||
pmapiAddress := addresses.ByEmail(address)
|
||||
if pmapiAddress != nil {
|
||||
return pmapiAddress.ID, nil
|
||||
@ -366,18 +307,21 @@ func (u *User) GetBridgePassword() string {
|
||||
// CheckBridgeLogin checks whether the user is logged in and the bridge
|
||||
// IMAP/SMTP password is correct.
|
||||
func (u *User) CheckBridgeLogin(password string) error {
|
||||
if isApplicationOutdated {
|
||||
u.listener.Emit(events.UpgradeApplicationEvent, "")
|
||||
return pmapi.ErrUpgradeApplication
|
||||
}
|
||||
// FIXME(conman): Handle force upgrade?
|
||||
|
||||
/*
|
||||
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
|
||||
if !u.creds.IsConnected() {
|
||||
u.listener.Emit(events.LogoutEvent, u.userID)
|
||||
return ErrLoggedOutUser
|
||||
}
|
||||
|
||||
return u.creds.CheckPassword(password)
|
||||
@ -388,60 +332,57 @@ 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.client().UpdateUser()
|
||||
_, err := u.client.UpdateUser(context.TODO())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = u.client().ReloadKeys([]byte(u.creds.MailboxPassword)); err != nil {
|
||||
if err := u.client.ReloadKeys(context.TODO(), []byte(u.creds.MailboxPassword)); err != nil {
|
||||
return errors.Wrap(err, "failed to reload keys")
|
||||
}
|
||||
|
||||
emails := u.client().Addresses().ActiveEmails()
|
||||
if err := u.credStorer.UpdateEmails(u.userID, emails); err != nil {
|
||||
creds, err := u.credStorer.UpdateEmails(u.userID, u.client.Addresses().ActiveEmails())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.refreshFromCredentials()
|
||||
u.creds = creds
|
||||
|
||||
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) {
|
||||
func (u *User) SwitchAddressMode() 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
|
||||
return errors.New("store is not initialised")
|
||||
}
|
||||
|
||||
newAddressModeState := !u.IsCombinedAddressMode()
|
||||
|
||||
if err = u.store.UseCombinedMode(newAddressModeState); err != nil {
|
||||
u.log.WithError(err).Error("Could not switch store address mode")
|
||||
return
|
||||
if err := u.store.UseCombinedMode(newAddressModeState); err != nil {
|
||||
return errors.Wrap(err, "could not switch store address mode")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if u.creds.IsCombinedAddressMode == newAddressModeState {
|
||||
return nil
|
||||
}
|
||||
|
||||
u.refreshFromCredentials()
|
||||
creds, err := u.credStorer.SwitchAddressMode(u.userID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not switch credentials store address mode")
|
||||
}
|
||||
|
||||
return err
|
||||
u.creds = creds
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// logout is the same as Logout, but for internal purposes (logged out from
|
||||
@ -458,35 +399,37 @@ func (u *User) logout() error {
|
||||
u.listener.Emit(events.UserRefreshEvent, u.userID)
|
||||
}
|
||||
|
||||
u.isAuthorized = false
|
||||
|
||||
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) {
|
||||
func (u *User) Logout() error {
|
||||
u.lock.Lock()
|
||||
defer u.lock.Unlock()
|
||||
|
||||
u.log.Debug("Logging out user")
|
||||
|
||||
if !u.creds.IsConnected() {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
u.client().Logout()
|
||||
// FIXME(conman): Do we delete API client now? Who cleans up? What about registered handlers?
|
||||
if err := u.client.AuthDelete(context.TODO()); err != nil {
|
||||
u.log.WithError(err).Warn("Failed to delete auth")
|
||||
}
|
||||
|
||||
if err = u.credStorer.Logout(u.userID); err != nil {
|
||||
creds, err := u.credStorer.Logout(u.userID)
|
||||
if err != nil {
|
||||
u.log.WithError(err).Warn("Could not log user out from credentials store")
|
||||
|
||||
if err = u.credStorer.Delete(u.userID); err != nil {
|
||||
if err := u.credStorer.Delete(u.userID); err != nil {
|
||||
u.log.WithError(err).Error("Could not delete user from credentials store")
|
||||
}
|
||||
} else {
|
||||
u.creds = creds
|
||||
}
|
||||
|
||||
u.refreshFromCredentials()
|
||||
|
||||
// Do not close whole store, just event loop. Some information might be needed offline (e.g. addressID)
|
||||
u.closeEventLoop()
|
||||
|
||||
@ -494,15 +437,7 @@ func (u *User) Logout() (err error) {
|
||||
|
||||
runtime.GC()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *User) refreshFromCredentials() {
|
||||
if credentials, err := u.credStorer.Get(u.userID); err != nil {
|
||||
log.WithError(err).Error("Cannot refresh user credentials")
|
||||
} else {
|
||||
u.creds = credentials
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) closeEventLoop() {
|
||||
|
||||
@ -19,12 +19,15 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
imapcache "github.com/ProtonMail/proton-bridge/internal/imap/cache"
|
||||
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
@ -45,7 +48,7 @@ type Users struct {
|
||||
locations Locator
|
||||
panicHandler PanicHandler
|
||||
events listener.Listener
|
||||
clientManager ClientManager
|
||||
clientManager pmapi.Manager
|
||||
credStorer CredentialsStorer
|
||||
storeFactory StoreMaker
|
||||
|
||||
@ -62,16 +65,13 @@ type Users struct {
|
||||
useOnlyActiveAddresses bool
|
||||
|
||||
lock sync.RWMutex
|
||||
|
||||
// stopAll can be closed to stop all goroutines from looping (watchAppOutdated, watchAPIAuths, heartbeat etc).
|
||||
stopAll chan struct{}
|
||||
}
|
||||
|
||||
func New(
|
||||
locations Locator,
|
||||
panicHandler PanicHandler,
|
||||
eventListener listener.Listener,
|
||||
clientManager ClientManager,
|
||||
clientManager pmapi.Manager,
|
||||
credStorer CredentialsStorer,
|
||||
storeFactory StoreMaker,
|
||||
useOnlyActiveAddresses bool,
|
||||
@ -87,98 +87,104 @@ func New(
|
||||
storeFactory: storeFactory,
|
||||
useOnlyActiveAddresses: useOnlyActiveAddresses,
|
||||
lock: sync.RWMutex{},
|
||||
stopAll: make(chan struct{}),
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer panicHandler.HandlePanic()
|
||||
u.watchAppOutdated()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer panicHandler.HandlePanic()
|
||||
u.watchAPIAuths()
|
||||
}()
|
||||
// FIXME(conman): Handle force upgrade events.
|
||||
/*
|
||||
go func() {
|
||||
defer panicHandler.HandlePanic()
|
||||
u.watchAppOutdated()
|
||||
}()
|
||||
*/
|
||||
|
||||
if u.credStorer == nil {
|
||||
log.Error("No credentials store is available")
|
||||
} else if err := u.loadUsersFromCredentialsStore(); err != nil {
|
||||
} else if err := u.loadUsersFromCredentialsStore(context.TODO()); err != nil {
|
||||
log.WithError(err).Error("Could not load all users from credentials store")
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *Users) loadUsersFromCredentialsStore() (err error) {
|
||||
func (u *Users) loadUsersFromCredentialsStore(ctx context.Context) error {
|
||||
u.lock.Lock()
|
||||
defer u.lock.Unlock()
|
||||
|
||||
userIDs, err := u.credStorer.List()
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
for _, userID := range userIDs {
|
||||
l := log.WithField("user", userID)
|
||||
|
||||
user, newUserErr := newUser(u.panicHandler, userID, u.events, u.credStorer, u.clientManager, u.storeFactory)
|
||||
if newUserErr != nil {
|
||||
l.WithField("user", userID).WithError(newUserErr).Warn("Could not load user, skipping")
|
||||
user, creds, err := newUser(u.panicHandler, userID, u.events, u.credStorer, u.storeFactory, u.useOnlyActiveAddresses)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Warn("Could not create user, skipping")
|
||||
continue
|
||||
}
|
||||
|
||||
u.users = append(u.users, user)
|
||||
|
||||
if initUserErr := user.init(); initUserErr != nil {
|
||||
l.WithField("user", userID).WithError(initUserErr).Warn("Could not initialise user")
|
||||
if creds.IsConnected() {
|
||||
if err := u.loadConnectedUser(ctx, user, creds); err != nil {
|
||||
logrus.WithError(err).Warn("Could not load connected user")
|
||||
}
|
||||
} else {
|
||||
logrus.Warn("User is disconnected and must be connected manually")
|
||||
|
||||
if err := u.loadDisconnectedUser(ctx, user, creds); err != nil {
|
||||
logrus.WithError(err).Warn("Could not load disconnected user")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *Users) watchAppOutdated() {
|
||||
ch := make(chan string)
|
||||
|
||||
u.events.Add(events.UpgradeApplicationEvent, ch)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ch:
|
||||
isApplicationOutdated = true
|
||||
u.closeAllConnections()
|
||||
|
||||
case <-u.stopAll:
|
||||
return
|
||||
}
|
||||
}
|
||||
func (u *Users) loadDisconnectedUser(ctx context.Context, user *User, creds *credentials.Credentials) error {
|
||||
// FIXME(conman): We shouldn't be creating unauthorized clients... this is hacky, just to avoid huge refactor!
|
||||
return user.connect(ctx, u.clientManager.NewClient("", "", "", time.Time{}), creds)
|
||||
}
|
||||
|
||||
// watchAPIAuths receives auths from the client manager and sends them to the appropriate user.
|
||||
func (u *Users) watchAPIAuths() {
|
||||
for {
|
||||
select {
|
||||
case auth := <-u.clientManager.GetAuthUpdateChannel():
|
||||
log.Debug("Users received auth from ClientManager")
|
||||
|
||||
user, ok := u.hasUser(auth.UserID)
|
||||
if !ok {
|
||||
log.WithField("userID", auth.UserID).Info("User not available for auth update")
|
||||
continue
|
||||
}
|
||||
|
||||
if auth.Auth != nil {
|
||||
user.updateAuthToken(auth.Auth)
|
||||
} else if err := user.logout(); err != nil {
|
||||
log.WithError(err).
|
||||
WithField("userID", auth.UserID).
|
||||
Error("User logout failed while watching API auths")
|
||||
}
|
||||
|
||||
case <-u.stopAll:
|
||||
return
|
||||
}
|
||||
func (u *Users) loadConnectedUser(ctx context.Context, user *User, creds *credentials.Credentials) error {
|
||||
uid, ref, err := creds.SplitAPIToken()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get user's refresh token")
|
||||
}
|
||||
|
||||
client, auth, err := u.clientManager.NewClientWithRefresh(ctx, uid, ref)
|
||||
if err != nil {
|
||||
// FIXME(conman): This is a problem... if we weren't able to create a new client due to internet,
|
||||
// we need to be able to retry later, but I deleted all the hacky "retry auth if necessary" stuff...
|
||||
return user.connect(ctx, u.clientManager.NewClient(uid, "", ref, time.Time{}), creds)
|
||||
}
|
||||
|
||||
// Update the user's credentials with the latest auth used to connect this user.
|
||||
if creds, err = u.credStorer.UpdateToken(auth.UserID, auth.UID, auth.RefreshToken); err != nil {
|
||||
return errors.Wrap(err, "could not create get user's refresh token")
|
||||
}
|
||||
|
||||
return user.connect(ctx, client, creds)
|
||||
}
|
||||
|
||||
func (u *Users) watchAppOutdated() {
|
||||
// FIXME(conman): handle force upgrade events.
|
||||
|
||||
/*
|
||||
ch := make(chan string)
|
||||
|
||||
u.events.Add(events.UpgradeApplicationEvent, ch)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ch:
|
||||
isApplicationOutdated = true
|
||||
u.closeAllConnections()
|
||||
|
||||
case <-u.stopAll:
|
||||
return
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
func (u *Users) closeAllConnections() {
|
||||
@ -192,63 +198,45 @@ func (u *Users) closeAllConnections() {
|
||||
func (u *Users) Login(username, password string) (authClient pmapi.Client, auth *pmapi.Auth, err error) {
|
||||
u.crashBandicoot(username)
|
||||
|
||||
// We need to use anonymous client because we don't yet have userID and so can't save auth tokens yet.
|
||||
authClient = u.clientManager.GetAnonymousClient()
|
||||
|
||||
authInfo, err := authClient.AuthInfo(username)
|
||||
if err != nil {
|
||||
log.WithField("username", username).WithError(err).Error("Could not get auth info for user")
|
||||
return
|
||||
}
|
||||
|
||||
if auth, err = authClient.Auth(username, password, authInfo); err != nil {
|
||||
log.WithField("username", username).WithError(err).Error("Could not get auth for user")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
return u.clientManager.NewClientWithLogin(context.TODO(), username, password)
|
||||
}
|
||||
|
||||
// FinishLogin finishes the login procedure and adds the user into the credentials store.
|
||||
func (u *Users) FinishLogin(authClient pmapi.Client, auth *pmapi.Auth, mbPassphrase string) (user *User, err error) { //nolint[funlen]
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("Login not finished; removing auth session")
|
||||
if delAuthErr := authClient.DeleteAuth(); delAuthErr != nil {
|
||||
log.WithError(delAuthErr).Error("Failed to clear login session after unlock")
|
||||
}
|
||||
}
|
||||
// The anonymous client will be removed from list and authentication will not be deleted.
|
||||
authClient.Logout()
|
||||
}()
|
||||
|
||||
apiUser, hashedPassphrase, err := getAPIUser(authClient, mbPassphrase)
|
||||
func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password string) (user *User, err error) { //nolint[funlen]
|
||||
apiUser, passphrase, err := getAPIUser(context.TODO(), client, password)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get API user")
|
||||
return
|
||||
return nil, errors.Wrap(err, "failed to get API user")
|
||||
}
|
||||
|
||||
log.Info("Got API user")
|
||||
if user, ok := u.hasUser(apiUser.ID); ok {
|
||||
if user.IsConnected() {
|
||||
if err := client.AuthDelete(context.TODO()); err != nil {
|
||||
logrus.WithError(err).Warn("Failed to delete new auth session")
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if user, ok = u.hasUser(apiUser.ID); ok {
|
||||
if err = u.connectExistingUser(user, auth, hashedPassphrase); err != nil {
|
||||
log.WithError(err).Error("Failed to connect existing user")
|
||||
return
|
||||
return nil, errors.New("user is already connected")
|
||||
}
|
||||
} else {
|
||||
if err = u.addNewUser(apiUser, auth, hashedPassphrase); err != nil {
|
||||
log.WithError(err).Error("Failed to add new user")
|
||||
return
|
||||
|
||||
// Update the user's credentials with the latest auth used to connect this user.
|
||||
if _, err := u.credStorer.UpdateToken(auth.UserID, auth.UID, auth.RefreshToken); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to load user credentials")
|
||||
}
|
||||
|
||||
// Update the password in case the user changed it.
|
||||
creds, err := u.credStorer.UpdatePassword(apiUser.ID, string(passphrase))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to update password of user in credentials store")
|
||||
}
|
||||
|
||||
if err := user.connect(context.TODO(), client, creds); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to reconnect existing user")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Old credentials use username as key (user ID) which needs to be removed
|
||||
// once user logs in again with proper ID fetched from API.
|
||||
if _, ok := u.hasUser(apiUser.Name); ok {
|
||||
if err := u.DeleteUser(apiUser.Name, true); err != nil {
|
||||
log.WithError(err).Error("Failed to delete old user")
|
||||
}
|
||||
if err := u.addNewUser(context.TODO(), client, apiUser, auth, passphrase); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to add new user")
|
||||
}
|
||||
|
||||
u.events.Emit(events.UserRefreshEvent, apiUser.ID)
|
||||
@ -256,107 +244,63 @@ func (u *Users) FinishLogin(authClient pmapi.Client, auth *pmapi.Auth, mbPassphr
|
||||
return u.GetUser(apiUser.ID)
|
||||
}
|
||||
|
||||
// connectExistingUser connects an existing user.
|
||||
func (u *Users) connectExistingUser(user *User, auth *pmapi.Auth, hashedPassphrase string) (err error) {
|
||||
if user.IsConnected() {
|
||||
return errors.New("user is already connected")
|
||||
}
|
||||
|
||||
log.Info("Connecting existing user")
|
||||
|
||||
// Update the user's password in the cred store in case they changed it.
|
||||
if err = u.credStorer.UpdatePassword(user.ID(), hashedPassphrase); err != nil {
|
||||
return errors.Wrap(err, "failed to update password of user in credentials store")
|
||||
}
|
||||
|
||||
client := u.clientManager.GetClient(user.ID())
|
||||
|
||||
if auth, err = client.AuthRefresh(auth.GenToken()); err != nil {
|
||||
return errors.Wrap(err, "failed to refresh auth token of new client")
|
||||
}
|
||||
|
||||
if err = u.credStorer.UpdateToken(user.ID(), auth.GenToken()); err != nil {
|
||||
return errors.Wrap(err, "failed to update token of user in credentials store")
|
||||
}
|
||||
|
||||
if err = user.init(); err != nil {
|
||||
return errors.Wrap(err, "failed to initialise user")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// addNewUser adds a new user.
|
||||
func (u *Users) addNewUser(apiUser *pmapi.User, auth *pmapi.Auth, hashedPassphrase string) (err error) {
|
||||
func (u *Users) addNewUser(ctx context.Context, client pmapi.Client, apiUser *pmapi.User, auth *pmapi.Auth, passphrase []byte) error {
|
||||
u.lock.Lock()
|
||||
defer u.lock.Unlock()
|
||||
|
||||
client := u.clientManager.GetClient(apiUser.ID)
|
||||
var emails []string
|
||||
|
||||
if auth, err = client.AuthRefresh(auth.GenToken()); err != nil {
|
||||
return errors.Wrap(err, "failed to refresh token in new client")
|
||||
}
|
||||
|
||||
if apiUser, err = client.CurrentUser(); err != nil {
|
||||
return errors.Wrap(err, "failed to update API user")
|
||||
}
|
||||
|
||||
var emails []string //nolint[prealloc]
|
||||
if u.useOnlyActiveAddresses {
|
||||
emails = client.Addresses().ActiveEmails()
|
||||
} else {
|
||||
emails = client.Addresses().AllEmails()
|
||||
}
|
||||
|
||||
if _, err = u.credStorer.Add(apiUser.ID, apiUser.Name, auth.GenToken(), hashedPassphrase, emails); err != nil {
|
||||
return errors.Wrap(err, "failed to add user to credentials store")
|
||||
if _, err := u.credStorer.Add(apiUser.ID, apiUser.Name, auth.UID, auth.RefreshToken, string(passphrase), emails); err != nil {
|
||||
return errors.Wrap(err, "failed to add user credentials to credentials store")
|
||||
}
|
||||
|
||||
user, err := newUser(u.panicHandler, apiUser.ID, u.events, u.credStorer, u.clientManager, u.storeFactory)
|
||||
user, creds, err := newUser(u.panicHandler, apiUser.ID, u.events, u.credStorer, u.storeFactory, u.useOnlyActiveAddresses)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create user")
|
||||
return errors.Wrap(err, "failed to create new user")
|
||||
}
|
||||
|
||||
// The user needs to be part of the users list in order for it to receive an auth during initialisation.
|
||||
u.users = append(u.users, user)
|
||||
|
||||
if err = user.init(); err != nil {
|
||||
u.users = u.users[:len(u.users)-1]
|
||||
return errors.Wrap(err, "failed to initialise user")
|
||||
if err := user.connect(ctx, client, creds); err != nil {
|
||||
return errors.Wrap(err, "failed to connect new user")
|
||||
}
|
||||
|
||||
if err := u.SendMetric(metrics.New(metrics.Setup, metrics.NewUser, metrics.NoLabel)); err != nil {
|
||||
log.WithError(err).Error("Failed to send metric")
|
||||
}
|
||||
|
||||
return err
|
||||
u.users = append(u.users, user)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAPIUser(client pmapi.Client, mbPassphrase string) (user *pmapi.User, hashedPassphrase string, err error) {
|
||||
salt, err := client.AuthSalt()
|
||||
func getAPIUser(ctx context.Context, client pmapi.Client, password string) (*pmapi.User, []byte, error) {
|
||||
salt, err := client.AuthSalt(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get salt")
|
||||
return nil, "", err
|
||||
return nil, nil, errors.Wrap(err, "failed to get salt")
|
||||
}
|
||||
|
||||
hashedPassphrase, err = pmapi.HashMailboxPassword(mbPassphrase, salt)
|
||||
passphrase, err := pmapi.HashMailboxPassword(password, salt)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not hash mailbox password")
|
||||
return nil, "", err
|
||||
return nil, nil, errors.Wrap(err, "failed to hash password")
|
||||
}
|
||||
|
||||
// We unlock the user's PGP key here to detect if the user's mailbox password is wrong.
|
||||
if err = client.Unlock([]byte(hashedPassphrase)); err != nil {
|
||||
log.WithError(err).Error("Wrong mailbox password")
|
||||
return nil, "", ErrWrongMailboxPassword
|
||||
if err := client.Unlock(ctx, passphrase); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to unlock client")
|
||||
}
|
||||
|
||||
if user, err = client.CurrentUser(); err != nil {
|
||||
log.WithError(err).Error("Could not load user data")
|
||||
return nil, "", err
|
||||
user, err := client.CurrentUser(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to load user data")
|
||||
}
|
||||
|
||||
return user, hashedPassphrase, nil
|
||||
return user, passphrase, nil
|
||||
}
|
||||
|
||||
// GetUsers returns all added users into keychain (even logged out users).
|
||||
@ -452,11 +396,9 @@ func (u *Users) DeleteUser(userID string, clearStore bool) error {
|
||||
|
||||
// SendMetric sends a metric. We don't want to return any errors, only log them.
|
||||
func (u *Users) SendMetric(m metrics.Metric) error {
|
||||
c := u.clientManager.GetAnonymousClient()
|
||||
defer c.Logout()
|
||||
|
||||
cat, act, lab := m.Get()
|
||||
if err := c.SendSimpleMetric(string(cat), string(act), string(lab)); err != nil {
|
||||
|
||||
if err := u.clientManager.SendSimpleMetric(context.Background(), string(cat), string(act), string(lab)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -472,24 +414,22 @@ func (u *Users) SendMetric(m metrics.Metric) error {
|
||||
// AllowProxy instructs the app to use DoH to access an API proxy if necessary.
|
||||
// It also needs to work before the app is initialised (because we may need to use the proxy at startup).
|
||||
func (u *Users) AllowProxy() {
|
||||
u.clientManager.AllowProxy()
|
||||
// FIXME(conman): Support DoH.
|
||||
// u.apiManager.AllowProxy()
|
||||
}
|
||||
|
||||
// DisallowProxy instructs the app to not use DoH to access an API proxy if necessary.
|
||||
// It also needs to work before the app is initialised (because we may need to use the proxy at startup).
|
||||
func (u *Users) DisallowProxy() {
|
||||
u.clientManager.DisallowProxy()
|
||||
// FIXME(conman): Support DoH.
|
||||
// u.apiManager.DisallowProxy()
|
||||
}
|
||||
|
||||
// CheckConnection returns whether there is an internet connection.
|
||||
// This should use the connection manager when it is eventually implemented.
|
||||
func (u *Users) CheckConnection() error {
|
||||
return u.clientManager.CheckConnection()
|
||||
}
|
||||
|
||||
// StopWatchers stops all goroutines.
|
||||
func (u *Users) StopWatchers() {
|
||||
close(u.stopAll)
|
||||
// FIXME(conman): Other parts of bridge that rely on this method should register as a connection observer.
|
||||
panic("TODO: register as a connection observer to get this information")
|
||||
}
|
||||
|
||||
// hasUser returns whether the struct currently has a user with ID `id`.
|
||||
|
||||
@ -20,8 +20,8 @@ package users
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
time "time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -49,20 +49,19 @@ func TestNewUsersWithDisconnectedUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
// Basically every call client has get client manager.
|
||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
|
||||
gomock.InOrder(
|
||||
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil),
|
||||
m.pmapiClient.EXPECT().ListLabels().Return(nil, errors.New("ErrUnauthorized")),
|
||||
m.clientManager.EXPECT().NewClient("", "", "", time.Time{}).Return(m.pmapiClient),
|
||||
m.pmapiClient.EXPECT().AddAuthHandler(gomock.Any()),
|
||||
m.pmapiClient.EXPECT().ListLabels(gomock.Any()).Return(nil, errors.New("ErrUnauthorized")),
|
||||
m.pmapiClient.EXPECT().Addresses().Return(nil),
|
||||
)
|
||||
|
||||
checkUsersNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
|
||||
}
|
||||
|
||||
/*
|
||||
func TestNewUsersWithConnectedUserWithBadToken(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
@ -132,6 +131,7 @@ func TestNewUsersFirstStart(t *testing.T) {
|
||||
|
||||
testNewUsers(t, m)
|
||||
}
|
||||
*/
|
||||
|
||||
func checkUsersNew(t *testing.T, m mocks, expectedCredentials []*credentials.Credentials) {
|
||||
users := testNewUsers(t, m)
|
||||
|
||||
@ -48,18 +48,17 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
var (
|
||||
testAuth = &pmapi.Auth{ //nolint[gochecknoglobals]
|
||||
RefreshToken: "tok",
|
||||
}
|
||||
testAuthRefresh = &pmapi.Auth{ //nolint[gochecknoglobals]
|
||||
RefreshToken: "reftok",
|
||||
UID: "uid",
|
||||
AccessToken: "acc",
|
||||
RefreshToken: "ref",
|
||||
}
|
||||
|
||||
testCredentials = &credentials.Credentials{ //nolint[gochecknoglobals]
|
||||
UserID: "user",
|
||||
Name: "username",
|
||||
Emails: "user@pm.me",
|
||||
APIToken: "token",
|
||||
APIToken: "uid:acc",
|
||||
MailboxPassword: "pass",
|
||||
BridgePassword: "0123456789abcdef",
|
||||
Version: "v1",
|
||||
@ -67,11 +66,12 @@ var (
|
||||
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",
|
||||
APIToken: "uid:acc",
|
||||
MailboxPassword: "pass",
|
||||
BridgePassword: "0123456789abcdef",
|
||||
Version: "v1",
|
||||
@ -79,6 +79,7 @@ var (
|
||||
IsHidden: false,
|
||||
IsCombinedAddressMode: false,
|
||||
}
|
||||
|
||||
testCredentialsDisconnected = &credentials.Credentials{ //nolint[gochecknoglobals]
|
||||
UserID: "user",
|
||||
Name: "username",
|
||||
@ -92,6 +93,19 @@ var (
|
||||
IsCombinedAddressMode: true,
|
||||
}
|
||||
|
||||
testCredentialsSplitDisconnected = &credentials.Credentials{ //nolint[gochecknoglobals]
|
||||
UserID: "users",
|
||||
Name: "usersname",
|
||||
Emails: "users@pm.me;anotheruser@pm.me;alsouser@pm.me",
|
||||
APIToken: "",
|
||||
MailboxPassword: "",
|
||||
BridgePassword: "0123456789abcdef",
|
||||
Version: "v1",
|
||||
Timestamp: 123456789,
|
||||
IsHidden: false,
|
||||
IsCombinedAddressMode: false,
|
||||
}
|
||||
|
||||
testPMAPIUser = &pmapi.User{ //nolint[gochecknoglobals]
|
||||
ID: "user",
|
||||
Name: "username",
|
||||
@ -130,12 +144,12 @@ type mocks struct {
|
||||
ctrl *gomock.Controller
|
||||
locator *usersmocks.MockLocator
|
||||
PanicHandler *usersmocks.MockPanicHandler
|
||||
clientManager *usersmocks.MockClientManager
|
||||
credentialsStore *usersmocks.MockCredentialsStorer
|
||||
storeMaker *usersmocks.MockStoreMaker
|
||||
eventListener *MockListener
|
||||
|
||||
pmapiClient *pmapimocks.MockClient
|
||||
clientManager *pmapimocks.MockManager
|
||||
pmapiClient *pmapimocks.MockClient
|
||||
|
||||
storeCache *store.Cache
|
||||
}
|
||||
@ -171,12 +185,12 @@ func initMocks(t *testing.T) mocks {
|
||||
ctrl: mockCtrl,
|
||||
locator: usersmocks.NewMockLocator(mockCtrl),
|
||||
PanicHandler: usersmocks.NewMockPanicHandler(mockCtrl),
|
||||
clientManager: usersmocks.NewMockClientManager(mockCtrl),
|
||||
credentialsStore: usersmocks.NewMockCredentialsStorer(mockCtrl),
|
||||
storeMaker: usersmocks.NewMockStoreMaker(mockCtrl),
|
||||
eventListener: NewMockListener(mockCtrl),
|
||||
|
||||
pmapiClient: pmapimocks.NewMockClient(mockCtrl),
|
||||
clientManager: pmapimocks.NewMockManager(mockCtrl),
|
||||
pmapiClient: pmapimocks.NewMockClient(mockCtrl),
|
||||
|
||||
storeCache: store.NewCache(cacheFile.Name()),
|
||||
}
|
||||
@ -189,7 +203,7 @@ func initMocks(t *testing.T) mocks {
|
||||
var sentryReporter *sentry.Reporter // Sentry reporter is not used under unit tests.
|
||||
dbFile, err := ioutil.TempFile("", "bridge-store-db-*.db")
|
||||
require.NoError(t, err, "could not get temporary file for store db")
|
||||
return store.New(sentryReporter, m.PanicHandler, user, m.clientManager, m.eventListener, dbFile.Name(), m.storeCache)
|
||||
return store.New(sentryReporter, m.PanicHandler, user, m.eventListener, dbFile.Name(), m.storeCache)
|
||||
}).AnyTimes()
|
||||
m.storeMaker.EXPECT().Remove(gomock.Any()).AnyTimes()
|
||||
|
||||
@ -198,46 +212,42 @@ func initMocks(t *testing.T) mocks {
|
||||
|
||||
func testNewUsersWithUsers(t *testing.T, m mocks) *Users {
|
||||
// Events are asynchronous
|
||||
m.pmapiClient.EXPECT().GetEvent("").Return(testPMAPIEvent, nil).Times(2)
|
||||
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).Times(2)
|
||||
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).Times(2)
|
||||
m.pmapiClient.EXPECT().GetEvent(gomock.Any(), "").Return(testPMAPIEvent, nil).Times(2)
|
||||
m.pmapiClient.EXPECT().GetEvent(gomock.Any(), testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).Times(2)
|
||||
m.pmapiClient.EXPECT().ListMessages(gomock.Any(), gomock.Any()).Return([]*pmapi.Message{}, 0, nil).Times(2)
|
||||
|
||||
gomock.InOrder(
|
||||
m.credentialsStore.EXPECT().List().Return([]string{"user", "users"}, nil),
|
||||
|
||||
// Init for user.
|
||||
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.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(nil),
|
||||
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
|
||||
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
|
||||
m.credentialsStore.EXPECT().Get(testCredentials.UserID).Return(testCredentials, nil),
|
||||
m.clientManager.EXPECT().NewClientWithRefresh(gomock.Any(), "uid", "acc").Return(m.pmapiClient, testAuthRefresh, nil),
|
||||
m.pmapiClient.EXPECT().AddAuthHandler(gomock.Any()),
|
||||
m.credentialsStore.EXPECT().UpdateToken(testCredentials.UserID, testAuthRefresh.UID, testAuthRefresh.RefreshToken).Return(testCredentials, nil),
|
||||
m.credentialsStore.EXPECT().UpdatePassword(testCredentials.UserID, testCredentials.MailboxPassword).Return(testCredentials, nil),
|
||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte("pass")).Return(nil),
|
||||
m.pmapiClient.EXPECT().ListLabels(gomock.Any()).Return([]*pmapi.Label{}, nil),
|
||||
m.pmapiClient.EXPECT().CountMessages(gomock.Any(), "").Return([]*pmapi.MessagesCount{}, nil),
|
||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||
|
||||
// Init for users.
|
||||
m.credentialsStore.EXPECT().Get("users").Return(testCredentialsSplit, nil),
|
||||
m.credentialsStore.EXPECT().Get("users").Return(testCredentialsSplit, nil),
|
||||
m.pmapiClient.EXPECT().AuthRefresh("token").Return(testAuthRefresh, nil),
|
||||
m.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(nil),
|
||||
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
|
||||
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
|
||||
m.credentialsStore.EXPECT().Get(testCredentialsSplit.UserID).Return(testCredentialsSplit, nil),
|
||||
m.clientManager.EXPECT().NewClientWithRefresh(gomock.Any(), "uid", "acc").Return(m.pmapiClient, testAuthRefresh, nil),
|
||||
m.pmapiClient.EXPECT().AddAuthHandler(gomock.Any()),
|
||||
m.credentialsStore.EXPECT().UpdateToken(testCredentialsSplit.UserID, testAuthRefresh.UID, testAuthRefresh.RefreshToken).Return(testCredentialsSplit, nil),
|
||||
m.credentialsStore.EXPECT().UpdatePassword(testCredentialsSplit.UserID, testCredentialsSplit.MailboxPassword).Return(testCredentialsSplit, nil),
|
||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte("pass")).Return(nil),
|
||||
m.pmapiClient.EXPECT().ListLabels(gomock.Any()).Return([]*pmapi.Label{}, nil),
|
||||
m.pmapiClient.EXPECT().CountMessages(gomock.Any(), "").Return([]*pmapi.MessagesCount{}, nil),
|
||||
m.pmapiClient.EXPECT().Addresses().Return(testPMAPIAddresses),
|
||||
)
|
||||
|
||||
users := testNewUsers(t, m)
|
||||
|
||||
user, _ := users.GetUser("user")
|
||||
mockAuthUpdate(user, "reftok", m)
|
||||
|
||||
user, _ = users.GetUser("user")
|
||||
mockAuthUpdate(user, "reftok", m)
|
||||
|
||||
return users
|
||||
return testNewUsers(t, m)
|
||||
}
|
||||
|
||||
func testNewUsers(t *testing.T, m mocks) *Users { //nolint[unparam]
|
||||
m.eventListener.EXPECT().Add(events.UpgradeApplicationEvent, gomock.Any())
|
||||
m.clientManager.EXPECT().GetAuthUpdateChannel().Return(make(chan pmapi.ClientAuth))
|
||||
// FIXME(conman): How to handle force upgrade?
|
||||
// m.eventListener.EXPECT().Add(events.UpgradeApplicationEvent, gomock.Any())
|
||||
|
||||
users := New(m.locator, m.PanicHandler, m.eventListener, m.clientManager, m.credentialsStore, m.storeMaker, true)
|
||||
|
||||
@ -256,8 +266,8 @@ func TestClearData(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
||||
// m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
// m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
||||
|
||||
users := testNewUsersWithUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
@ -267,13 +277,11 @@ func TestClearData(t *testing.T) {
|
||||
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().AuthDelete(gomock.Any())
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(testCredentialsDisconnected, nil)
|
||||
|
||||
m.pmapiClient.EXPECT().Logout()
|
||||
m.credentialsStore.EXPECT().Logout("users").Return(nil)
|
||||
m.credentialsStore.EXPECT().Get("users").Return(testCredentialsSplit, nil)
|
||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any())
|
||||
m.credentialsStore.EXPECT().Logout("users").Return(testCredentialsSplitDisconnected, nil)
|
||||
|
||||
m.locator.EXPECT().Clear()
|
||||
|
||||
@ -285,9 +293,9 @@ func TestClearData(t *testing.T) {
|
||||
func mockEventLoopNoAction(m mocks) {
|
||||
// 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).AnyTimes()
|
||||
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).AnyTimes()
|
||||
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
|
||||
m.pmapiClient.EXPECT().GetEvent(gomock.Any(), "").Return(testPMAPIEvent, nil).AnyTimes()
|
||||
m.pmapiClient.EXPECT().GetEvent(gomock.Any(), testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).AnyTimes()
|
||||
m.pmapiClient.EXPECT().ListMessages(gomock.Any(), gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
|
||||
}
|
||||
|
||||
func mockConnectedUser(m mocks) {
|
||||
@ -295,27 +303,13 @@ func mockConnectedUser(m mocks) {
|
||||
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.pmapiClient.EXPECT().AuthRefresh("uid:acc").Return(testAuthRefresh, nil),
|
||||
|
||||
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
|
||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []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().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
|
||||
m.pmapiClient.EXPECT().ListLabels(gomock.Any()).Return([]*pmapi.Label{}, nil),
|
||||
m.pmapiClient.EXPECT().CountMessages(gomock.Any(), "").Return([]*pmapi.MessagesCount{}, nil),
|
||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||
)
|
||||
}
|
||||
|
||||
// mockAuthUpdate simulates users calling UpdateAuthToken on the given user.
|
||||
// This would normally be done by users when it receives an auth from the ClientManager,
|
||||
// but as we don't have a full users instance here, we do this manually.
|
||||
func mockAuthUpdate(user *User, token string, m mocks) {
|
||||
gomock.InOrder(
|
||||
m.credentialsStore.EXPECT().UpdateToken("user", ":"+token).Return(nil),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken(token), nil),
|
||||
)
|
||||
|
||||
user.updateAuthToken(refreshWithToken(token))
|
||||
|
||||
waitForEvents()
|
||||
}
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package users
|
||||
|
||||
// IsAuthorized returns whether the user has received an Auth from the API yet.
|
||||
func (u *User) IsAuthorized() bool {
|
||||
return u.isAuthorized
|
||||
}
|
||||
Reference in New Issue
Block a user