mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-15 14:56:42 +00:00
GODT-35: Finish all details and make tests pass
This commit is contained in:
@ -1,251 +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
|
||||
|
||||
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)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().IsUnlocked().Return(false),
|
||||
m.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(nil),
|
||||
|
||||
m.pmapiClient.EXPECT().UpdateUser().Return(nil, nil),
|
||||
m.pmapiClient.EXPECT().ReloadKeys([]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),
|
||||
)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().GetEvent(testPMAPIEvent.EventID).Return(testPMAPIEvent, nil).MaxTimes(1),
|
||||
m.pmapiClient.EXPECT().ListMessages(gomock.Any()).Return([]*pmapi.Message{}, 0, nil).MaxTimes(1),
|
||||
)
|
||||
|
||||
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),
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().Logout().Return(),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(nil),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, 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)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().Logout().Return(),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(errors.New("logout failed")),
|
||||
m.credentialsStore.EXPECT().Delete("user").Return(nil),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil),
|
||||
)
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
|
||||
err := user.Logout()
|
||||
waitForEvents()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCheckBridgeLoginOK(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
user := testNewUser(m)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().IsUnlocked().Return(false),
|
||||
m.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(nil),
|
||||
)
|
||||
|
||||
err := user.CheckBridgeLogin(testCredentials.BridgePassword)
|
||||
|
||||
waitForEvents()
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCheckBridgeLoginTwiceOK(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
user := testNewUser(m)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().IsUnlocked().Return(false),
|
||||
m.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(nil),
|
||||
m.pmapiClient.EXPECT().IsUnlocked().Return(true),
|
||||
)
|
||||
|
||||
err := user.CheckBridgeLogin(testCredentials.BridgePassword)
|
||||
waitForEvents()
|
||||
assert.NoError(t, err)
|
||||
|
||||
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()
|
||||
assert.Equal(t, pmapi.ErrUpgradeApplication, err)
|
||||
|
||||
isApplicationOutdated = false
|
||||
}
|
||||
|
||||
func TestCheckBridgeLoginLoggedOut(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
|
||||
|
||||
user, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.clientManager, m.storeMaker)
|
||||
assert.NoError(t, err)
|
||||
|
||||
m.clientManager.EXPECT().GetClient(gomock.Any()).Return(m.pmapiClient).MinTimes(1)
|
||||
gomock.InOrder(
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil),
|
||||
m.pmapiClient.EXPECT().ListLabels().Return(nil, errors.New("ErrUnauthorized")),
|
||||
m.pmapiClient.EXPECT().Addresses().Return(nil),
|
||||
)
|
||||
|
||||
err = user.init()
|
||||
assert.Error(t, err)
|
||||
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
|
||||
|
||||
err = user.CheckBridgeLogin(testCredentialsDisconnected.BridgePassword)
|
||||
waitForEvents()
|
||||
assert.Equal(t, ErrLoggedOutUser, err)
|
||||
}
|
||||
|
||||
func TestCheckBridgeLoginBadPassword(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
user := testNewUser(m)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().IsUnlocked().Return(false),
|
||||
m.pmapiClient.EXPECT().Unlock([]byte("pass")).Return(nil),
|
||||
)
|
||||
|
||||
err := user.CheckBridgeLogin("wrong!")
|
||||
waitForEvents()
|
||||
assert.Equal(t, "backend/credentials: incorrect password", err.Error())
|
||||
}
|
||||
@ -1,112 +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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||
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.clientManager, m.storeMaker)
|
||||
a.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNewUserAuthRefreshFails(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
|
||||
gomock.InOrder(
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
|
||||
m.pmapiClient.EXPECT().AuthRefresh("token").Return(nil, errors.New("bad token")),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(nil),
|
||||
|
||||
m.pmapiClient.EXPECT().Logout(),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(nil),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil),
|
||||
)
|
||||
|
||||
checkNewUserHasCredentials(testCredentialsDisconnected, m)
|
||||
}
|
||||
|
||||
func TestNewUserUnlockFails(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
|
||||
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
|
||||
gomock.InOrder(
|
||||
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(errors.New("bad password")),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(nil),
|
||||
m.pmapiClient.EXPECT().Logout(),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(nil),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil),
|
||||
)
|
||||
|
||||
checkNewUserHasCredentials(testCredentialsDisconnected, m)
|
||||
}
|
||||
|
||||
func TestNewUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
mockConnectedUser(m)
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
checkNewUserHasCredentials(testCredentials, m)
|
||||
}
|
||||
|
||||
func checkNewUserHasCredentials(creds *credentials.Credentials, m mocks) {
|
||||
user, _ := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.clientManager, m.storeMaker)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
_ = user.init()
|
||||
|
||||
waitForEvents()
|
||||
|
||||
a.Equal(m.t, creds, user.creds)
|
||||
}
|
||||
|
||||
func _TestUserEventRefreshUpdatesAddresses(t *testing.T) { // nolint[funlen]
|
||||
a.Fail(t, "not implemented")
|
||||
}
|
||||
@ -1,89 +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
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testNewUser sets up a new, authorised user.
|
||||
func testNewUser(m mocks) *User {
|
||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
|
||||
mockConnectedUser(m)
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
user, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.clientManager, m.storeMaker)
|
||||
assert.NoError(m.t, err)
|
||||
|
||||
err = user.init()
|
||||
assert.NoError(m.t, err)
|
||||
|
||||
mockAuthUpdate(user, "reftok", m)
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func testNewUserForLogout(m mocks) *User {
|
||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
|
||||
mockConnectedUser(m)
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
user, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.clientManager, m.storeMaker)
|
||||
assert.NoError(m.t, err)
|
||||
|
||||
err = user.init()
|
||||
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())
|
||||
}
|
||||
@ -1,143 +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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetNoUser(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)
|
||||
|
||||
checkUsersGetUser(t, m, "nouser", -1, "user nouser not found")
|
||||
}
|
||||
|
||||
func TestGetUserByID(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)
|
||||
|
||||
checkUsersGetUser(t, m, "user", 0, "")
|
||||
checkUsersGetUser(t, m, "users", 1, "")
|
||||
}
|
||||
|
||||
func TestGetUserByName(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)
|
||||
|
||||
checkUsersGetUser(t, m, "username", 0, "")
|
||||
checkUsersGetUser(t, m, "usersname", 1, "")
|
||||
}
|
||||
|
||||
func TestGetUserByEmail(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)
|
||||
|
||||
checkUsersGetUser(t, m, "user@pm.me", 0, "")
|
||||
checkUsersGetUser(t, m, "users@pm.me", 1, "")
|
||||
checkUsersGetUser(t, m, "anotheruser@pm.me", 1, "")
|
||||
checkUsersGetUser(t, m, "alsouser@pm.me", 1, "")
|
||||
}
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
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)
|
||||
|
||||
users := testNewUsersWithUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().Logout().Return(),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(nil),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil),
|
||||
m.credentialsStore.EXPECT().Delete("user").Return(nil),
|
||||
)
|
||||
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
|
||||
err := users.DeleteUser("user", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(users.users))
|
||||
}
|
||||
|
||||
// Even when logout fails, delete is done.
|
||||
func TestDeleteUserWithFailingLogout(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)
|
||||
|
||||
users := testNewUsersWithUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().Logout().Return(),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(errors.New("logout failed")),
|
||||
m.credentialsStore.EXPECT().Delete("user").Return(nil),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(nil, errors.New("no such user")),
|
||||
m.credentialsStore.EXPECT().Delete("user").Return(nil),
|
||||
)
|
||||
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
|
||||
err := users.DeleteUser("user", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(users.users))
|
||||
}
|
||||
|
||||
func checkUsersGetUser(t *testing.T, m mocks, query string, index int, expectedError string) {
|
||||
users := testNewUsersWithUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
user, err := users.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 = users.users[index]
|
||||
}
|
||||
|
||||
assert.Equal(m.t, expectedUser, user)
|
||||
}
|
||||
@ -1,219 +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
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUsersFinishLoginBadMailboxPassword(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
gomock.InOrder(
|
||||
// Init users with no user from keychain.
|
||||
m.credentialsStore.EXPECT().List().Return([]string{}, nil),
|
||||
|
||||
// Set up mocks for FinishLogin.
|
||||
m.pmapiClient.EXPECT().AuthSalt().Return("", nil),
|
||||
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(errors.New("no keys could be unlocked")),
|
||||
m.pmapiClient.EXPECT().DeleteAuth(),
|
||||
m.pmapiClient.EXPECT().Logout(),
|
||||
)
|
||||
|
||||
checkUsersFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "", ErrWrongMailboxPassword)
|
||||
}
|
||||
|
||||
func refreshWithToken(token string) *pmapi.Auth {
|
||||
return &pmapi.Auth{
|
||||
RefreshToken: token,
|
||||
}
|
||||
}
|
||||
|
||||
func credentialsWithToken(token string) *credentials.Credentials {
|
||||
tmp := &credentials.Credentials{}
|
||||
*tmp = *testCredentials
|
||||
tmp.APIToken = token
|
||||
return tmp
|
||||
}
|
||||
|
||||
func TestUsersFinishLoginNewUser(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(
|
||||
// users.New() finds no users in keychain.
|
||||
m.credentialsStore.EXPECT().List().Return([]string{}, nil),
|
||||
|
||||
// getAPIUser() loads user info from API (e.g. userID).
|
||||
m.pmapiClient.EXPECT().AuthSalt().Return("", nil),
|
||||
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
|
||||
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil),
|
||||
|
||||
// addNewUser()
|
||||
m.pmapiClient.EXPECT().AuthRefresh(":tok").Return(refreshWithToken("afterLogin"), nil),
|
||||
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil),
|
||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||
m.credentialsStore.EXPECT().Add("user", "username", ":afterLogin", testCredentials.MailboxPassword, []string{testPMAPIAddress.Email}),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken(":afterLogin"), nil),
|
||||
|
||||
// user.init() in addNewUser
|
||||
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken(":afterLogin"), nil),
|
||||
m.pmapiClient.EXPECT().AuthRefresh(":afterLogin").Return(refreshWithToken("afterCredentials"), nil),
|
||||
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
|
||||
|
||||
// store.New() in user.init
|
||||
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
|
||||
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
|
||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||
|
||||
// Emit event for new user and send metrics.
|
||||
m.clientManager.EXPECT().GetAnonymousClient().Return(m.pmapiClient),
|
||||
m.pmapiClient.EXPECT().SendSimpleMetric(string(metrics.Setup), string(metrics.NewUser), string(metrics.NoLabel)),
|
||||
m.pmapiClient.EXPECT().Logout(),
|
||||
|
||||
// Reload account list in GUI.
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user"),
|
||||
|
||||
// defer logout anonymous
|
||||
m.pmapiClient.EXPECT().Logout(),
|
||||
)
|
||||
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
user := checkUsersFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil)
|
||||
|
||||
mockAuthUpdate(user, "afterCredentials", m)
|
||||
}
|
||||
|
||||
func TestUsersFinishLoginExistingDisconnectedUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
loggedOutCreds := *testCredentials
|
||||
loggedOutCreds.APIToken = ""
|
||||
loggedOutCreds.MailboxPassword = ""
|
||||
|
||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
|
||||
gomock.InOrder(
|
||||
// users.New() finds one existing user in keychain.
|
||||
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil),
|
||||
|
||||
// newUser()
|
||||
m.credentialsStore.EXPECT().Get("user").Return(&loggedOutCreds, nil),
|
||||
|
||||
// user.init()
|
||||
m.credentialsStore.EXPECT().Get("user").Return(&loggedOutCreds, nil),
|
||||
|
||||
// store.New() in user.init
|
||||
m.pmapiClient.EXPECT().ListLabels().Return(nil, pmapi.ErrUnauthorized),
|
||||
m.pmapiClient.EXPECT().Addresses().Return(nil),
|
||||
|
||||
// getAPIUser() loads user info from API (e.g. userID).
|
||||
m.pmapiClient.EXPECT().AuthSalt().Return("", nil),
|
||||
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
|
||||
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil),
|
||||
|
||||
// connectExistingUser()
|
||||
m.credentialsStore.EXPECT().UpdatePassword("user", testCredentials.MailboxPassword).Return(nil),
|
||||
m.pmapiClient.EXPECT().AuthRefresh(":tok").Return(refreshWithToken("afterLogin"), nil),
|
||||
m.credentialsStore.EXPECT().UpdateToken("user", ":afterLogin").Return(nil),
|
||||
|
||||
// user.init() in connectExistingUser
|
||||
m.credentialsStore.EXPECT().Get("user").Return(credentialsWithToken(":afterLogin"), nil),
|
||||
m.pmapiClient.EXPECT().AuthRefresh(":afterLogin").Return(refreshWithToken("afterCredentials"), nil),
|
||||
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
|
||||
|
||||
// store.New() in user.init
|
||||
m.pmapiClient.EXPECT().ListLabels().Return([]*pmapi.Label{}, nil),
|
||||
m.pmapiClient.EXPECT().CountMessages("").Return([]*pmapi.MessagesCount{}, nil),
|
||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||
|
||||
// Reload account list in GUI.
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user"),
|
||||
|
||||
// defer logout anonymous
|
||||
m.pmapiClient.EXPECT().Logout(),
|
||||
)
|
||||
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
user := checkUsersFinishLogin(t, m, testAuth, testCredentials.MailboxPassword, "user", nil)
|
||||
|
||||
mockAuthUpdate(user, "afterCredentials", m)
|
||||
}
|
||||
|
||||
func TestUsersFinishLoginConnectedUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
|
||||
|
||||
mockConnectedUser(m)
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
users := testNewUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
// Then, try to log in again...
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().AuthSalt().Return("", nil),
|
||||
m.pmapiClient.EXPECT().Unlock([]byte(testCredentials.MailboxPassword)).Return(nil),
|
||||
m.pmapiClient.EXPECT().CurrentUser().Return(testPMAPIUser, nil),
|
||||
m.pmapiClient.EXPECT().DeleteAuth(),
|
||||
m.pmapiClient.EXPECT().Logout(),
|
||||
)
|
||||
|
||||
_, err := users.FinishLogin(m.pmapiClient, testAuth, testCredentials.MailboxPassword)
|
||||
assert.Equal(t, "user is already connected", err.Error())
|
||||
}
|
||||
|
||||
func checkUsersFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassword string, expectedUserID string, expectedErr error) *User {
|
||||
users := testNewUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
user, err := users.FinishLogin(m.pmapiClient, auth, mailboxPassword)
|
||||
|
||||
waitForEvents()
|
||||
|
||||
assert.Equal(t, expectedErr, err)
|
||||
|
||||
if expectedUserID != "" {
|
||||
assert.Equal(t, expectedUserID, user.ID())
|
||||
assert.Equal(t, 1, len(users.users))
|
||||
assert.Equal(t, expectedUserID, users.users[0].ID())
|
||||
} else {
|
||||
assert.Equal(t, (*User)(nil), user)
|
||||
assert.Equal(t, 0, len(users.users))
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
@ -233,7 +233,7 @@ func (s *Store) get(userID string) (creds *Credentials, err error) {
|
||||
|
||||
_, secret, err := s.secrets.Get(userID)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get credentials from native keychain")
|
||||
log.WithError(err).Warn("Could not get credentials from native keychain")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -26,8 +26,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
r "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const testSep = "\n"
|
||||
@ -249,26 +248,26 @@ func TestMarshalFormats(t *testing.T) {
|
||||
log.Infof("secretFmt %#v %d\n", secretFmt, len(secretFmt))
|
||||
|
||||
output := testCredentials{APIToken: "refresh"}
|
||||
require.NoError(t, output.UnmarshalStrings(secretStrings))
|
||||
r.NoError(t, output.UnmarshalStrings(secretStrings))
|
||||
log.Infof("strings out %#v \n", output)
|
||||
require.True(t, input.IsSame(&output), "strings out not same")
|
||||
r.True(t, input.IsSame(&output), "strings out not same")
|
||||
|
||||
output = testCredentials{APIToken: "refresh"}
|
||||
require.NoError(t, output.UnmarshalGob(secretGob))
|
||||
r.NoError(t, output.UnmarshalGob(secretGob))
|
||||
log.Infof("gob out %#v\n \n", output)
|
||||
assert.Equal(t, input, output)
|
||||
r.Equal(t, input, output)
|
||||
|
||||
output = testCredentials{APIToken: "refresh"}
|
||||
require.NoError(t, output.FromJSON(secretJSON))
|
||||
r.NoError(t, output.FromJSON(secretJSON))
|
||||
log.Infof("json out %#v \n", output)
|
||||
require.True(t, input.IsSame(&output), "json out not same")
|
||||
r.True(t, input.IsSame(&output), "json out not same")
|
||||
|
||||
/*
|
||||
// Simple Fscanf not working!
|
||||
output = testCredentials{APIToken: "refresh"}
|
||||
require.NoError(t, output.UnmarshalFmt(secretFmt))
|
||||
r.NoError(t, output.UnmarshalFmt(secretFmt))
|
||||
log.Infof("fmt out %#v \n", output)
|
||||
require.True(t, input.IsSame(&output), "fmt out not same")
|
||||
r.True(t, input.IsSame(&output), "fmt out not same")
|
||||
*/
|
||||
}
|
||||
|
||||
@ -291,7 +290,7 @@ func TestMarshal(t *testing.T) {
|
||||
log.Infof("secret %#v %d\n", secret, len(secret))
|
||||
|
||||
output := Credentials{APIToken: "refresh"}
|
||||
require.NoError(t, output.Unmarshal(secret))
|
||||
r.NoError(t, output.Unmarshal(secret))
|
||||
log.Infof("output %#v\n", output)
|
||||
assert.Equal(t, input, output)
|
||||
r.Equal(t, input, output)
|
||||
}
|
||||
|
||||
@ -1,107 +0,0 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./listener/listener.go
|
||||
|
||||
// Package users is a generated GoMock package.
|
||||
package users
|
||||
|
||||
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)
|
||||
}
|
||||
120
internal/users/mocks/listener_mocks.go
Normal file
120
internal/users/mocks/listener_mocks.go
Normal file
@ -0,0 +1,120 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/ProtonMail/proton-bridge/pkg/listener (interfaces: Listener)
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Add mocks base method
|
||||
func (m *MockListener) Add(arg0 string, arg1 chan<- string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Add", arg0, arg1)
|
||||
}
|
||||
|
||||
// Add indicates an expected call of Add
|
||||
func (mr *MockListenerMockRecorder) Add(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), arg0, arg1)
|
||||
}
|
||||
|
||||
// Emit mocks base method
|
||||
func (m *MockListener) Emit(arg0, arg1 string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Emit", arg0, arg1)
|
||||
}
|
||||
|
||||
// Emit indicates an expected call of Emit
|
||||
func (mr *MockListenerMockRecorder) Emit(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Emit", reflect.TypeOf((*MockListener)(nil).Emit), arg0, arg1)
|
||||
}
|
||||
|
||||
// ProvideChannel mocks base method
|
||||
func (m *MockListener) ProvideChannel(arg0 string) <-chan string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ProvideChannel", arg0)
|
||||
ret0, _ := ret[0].(<-chan string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ProvideChannel indicates an expected call of ProvideChannel
|
||||
func (mr *MockListenerMockRecorder) ProvideChannel(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProvideChannel", reflect.TypeOf((*MockListener)(nil).ProvideChannel), arg0)
|
||||
}
|
||||
|
||||
// Remove mocks base method
|
||||
func (m *MockListener) Remove(arg0 string, arg1 chan<- string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Remove", arg0, arg1)
|
||||
}
|
||||
|
||||
// Remove indicates an expected call of Remove
|
||||
func (mr *MockListenerMockRecorder) Remove(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockListener)(nil).Remove), arg0, arg1)
|
||||
}
|
||||
|
||||
// RetryEmit mocks base method
|
||||
func (m *MockListener) RetryEmit(arg0 string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "RetryEmit", arg0)
|
||||
}
|
||||
|
||||
// RetryEmit indicates an expected call of RetryEmit
|
||||
func (mr *MockListenerMockRecorder) RetryEmit(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryEmit", reflect.TypeOf((*MockListener)(nil).RetryEmit), arg0)
|
||||
}
|
||||
|
||||
// SetBuffer mocks base method
|
||||
func (m *MockListener) SetBuffer(arg0 string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetBuffer", arg0)
|
||||
}
|
||||
|
||||
// SetBuffer indicates an expected call of SetBuffer
|
||||
func (mr *MockListenerMockRecorder) SetBuffer(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBuffer", reflect.TypeOf((*MockListener)(nil).SetBuffer), arg0)
|
||||
}
|
||||
|
||||
// SetLimit mocks base method
|
||||
func (m *MockListener) SetLimit(arg0 string, arg1 time.Duration) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetLimit", arg0, arg1)
|
||||
}
|
||||
|
||||
// SetLimit indicates an expected call of SetLimit
|
||||
func (mr *MockListenerMockRecorder) SetLimit(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLimit", reflect.TypeOf((*MockListener)(nil).SetLimit), arg0, arg1)
|
||||
}
|
||||
@ -5,11 +5,10 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
store "github.com/ProtonMail/proton-bridge/internal/store"
|
||||
credentials "github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockLocator is a mock of Locator interface
|
||||
|
||||
@ -89,29 +89,25 @@ func newUser(
|
||||
// - 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 {
|
||||
// - loading its store.
|
||||
func (u *User) connect(client pmapi.Client, creds *credentials.Credentials) error {
|
||||
u.log.Info("Connecting user")
|
||||
|
||||
// Connected users have an API client.
|
||||
u.client = client
|
||||
|
||||
// FIXME(conman): How to remove this auth handler when user is disconnected?
|
||||
u.client.AddAuthHandler(u.handleAuth)
|
||||
u.client.AddAuthRefreshHandler(u.handleAuthRefresh)
|
||||
|
||||
// Save the latest credentials for the user.
|
||||
u.creds = creds
|
||||
|
||||
// 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
|
||||
}
|
||||
if err := u.unlockIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Connected users have a store.
|
||||
if err := u.loadStore(); err != nil {
|
||||
if err := u.loadStore(); err != nil { //nolint[revive] easier to read
|
||||
return err
|
||||
}
|
||||
|
||||
@ -138,17 +134,25 @@ func (u *User) loadStore() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) handleAuth(auth *pmapi.Auth) error {
|
||||
u.log.Debug("User received auth")
|
||||
func (u *User) handleAuthRefresh(auth *pmapi.AuthRefresh) {
|
||||
u.log.Debug("User received auth refresh update")
|
||||
|
||||
if auth == nil {
|
||||
if err := u.logout(); err != nil {
|
||||
log.WithError(err).
|
||||
WithField("userID", u.userID).
|
||||
Error("User logout failed while watching API auths")
|
||||
}
|
||||
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.log.WithError(err).Error("Failed to update refresh token in credentials store")
|
||||
return
|
||||
}
|
||||
|
||||
u.creds = creds
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clearStore removes the database.
|
||||
@ -181,13 +185,6 @@ func (u *User) closeStore() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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() pmapi.Client {
|
||||
return u.client
|
||||
}
|
||||
|
||||
// ID returns the user's userID.
|
||||
func (u *User) ID() string {
|
||||
return u.userID
|
||||
@ -210,9 +207,43 @@ func (u *User) IsConnected() bool {
|
||||
}
|
||||
|
||||
func (u *User) GetClient() pmapi.Client {
|
||||
if err := u.unlockIfNecessary(); err != nil {
|
||||
u.log.WithError(err).Error("Failed to unlock user")
|
||||
}
|
||||
return u.client
|
||||
}
|
||||
|
||||
// unlockIfNecessary will not trigger keyring unlocking if it was already successfully unlocked.
|
||||
func (u *User) unlockIfNecessary() error {
|
||||
if !u.creds.IsConnected() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if u.client.IsUnlocked() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// unlockIfNecessary is called with every access to underlying pmapi
|
||||
// client. Unlock should only finish unlocking when connection is back up.
|
||||
// That means it should try it fast enough and not retry if connection
|
||||
// is still down.
|
||||
err := u.client.Unlock(pmapi.ContextWithoutRetry(context.Background()), []byte(u.creds.MailboxPassword))
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch errors.Cause(err) {
|
||||
case pmapi.ErrNoConnection, pmapi.ErrUpgradeApplication:
|
||||
u.log.WithError(err).Warn("Could not unlock user")
|
||||
return nil
|
||||
}
|
||||
|
||||
if logoutErr := u.logout(); logoutErr != nil {
|
||||
u.log.WithError(logoutErr).Warn("Could not logout user")
|
||||
}
|
||||
return errors.Wrap(err, "failed to unlock user")
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -307,14 +338,10 @@ 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 {
|
||||
// FIXME(conman): Handle force upgrade?
|
||||
|
||||
/*
|
||||
if isApplicationOutdated {
|
||||
u.listener.Emit(events.UpgradeApplicationEvent, "")
|
||||
return pmapi.ErrUpgradeApplication
|
||||
}
|
||||
*/
|
||||
if isApplicationOutdated {
|
||||
u.listener.Emit(events.UpgradeApplicationEvent, "")
|
||||
return pmapi.ErrUpgradeApplication
|
||||
}
|
||||
|
||||
u.lock.RLock()
|
||||
defer u.lock.RUnlock()
|
||||
@ -328,16 +355,16 @@ func (u *User) CheckBridgeLogin(password string) error {
|
||||
}
|
||||
|
||||
// UpdateUser updates user details from API and saves to the credentials.
|
||||
func (u *User) UpdateUser() error {
|
||||
func (u *User) UpdateUser(ctx context.Context) error {
|
||||
u.lock.Lock()
|
||||
defer u.lock.Unlock()
|
||||
|
||||
_, err := u.client.UpdateUser(context.TODO())
|
||||
_, err := u.client.UpdateUser(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := u.client.ReloadKeys(context.TODO(), []byte(u.creds.MailboxPassword)); err != nil {
|
||||
if err := u.client.ReloadKeys(ctx, []byte(u.creds.MailboxPassword)); err != nil {
|
||||
return errors.Wrap(err, "failed to reload keys")
|
||||
}
|
||||
|
||||
@ -414,8 +441,7 @@ func (u *User) Logout() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FIXME(conman): Do we delete API client now? Who cleans up? What about registered handlers?
|
||||
if err := u.client.AuthDelete(context.TODO()); err != nil {
|
||||
if err := u.client.AuthDelete(context.Background()); err != nil {
|
||||
u.log.WithError(err).Warn("Failed to delete auth")
|
||||
}
|
||||
|
||||
|
||||
195
internal/users/user_credentials_test.go
Normal file
195
internal/users/user_credentials_test.go
Normal file
@ -0,0 +1,195 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
r "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUpdateUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
user := testNewUser(m)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().UpdateUser(gomock.Any()).Return(nil, nil),
|
||||
m.pmapiClient.EXPECT().ReloadKeys(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(nil),
|
||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||
|
||||
m.credentialsStore.EXPECT().UpdateEmails("user", []string{testPMAPIAddress.Email}).Return(testCredentials, nil),
|
||||
)
|
||||
|
||||
r.NoError(t, user.UpdateUser(context.Background()))
|
||||
}
|
||||
|
||||
func TestUserSwitchAddressMode(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
user := testNewUser(m)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
// Ignore any sync on background.
|
||||
m.pmapiClient.EXPECT().ListMessages(gomock.Any(), gomock.Any()).Return([]*pmapi.Message{}, 0, nil).AnyTimes()
|
||||
|
||||
// Check initial state.
|
||||
r.True(t, user.store.IsCombinedMode())
|
||||
r.True(t, user.creds.IsCombinedAddressMode)
|
||||
r.True(t, user.IsCombinedAddressMode())
|
||||
|
||||
// Mock change to split mode.
|
||||
gomock.InOrder(
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me"),
|
||||
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}),
|
||||
m.credentialsStore.EXPECT().SwitchAddressMode("user").Return(testCredentialsSplit, nil),
|
||||
)
|
||||
|
||||
// Check switch to split mode.
|
||||
r.NoError(t, user.SwitchAddressMode())
|
||||
r.False(t, user.store.IsCombinedMode())
|
||||
r.False(t, user.creds.IsCombinedAddressMode)
|
||||
r.False(t, user.IsCombinedAddressMode())
|
||||
|
||||
// MOck change to combined mode.
|
||||
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(gomock.Any()).Return([]*pmapi.Label{}, nil),
|
||||
m.pmapiClient.EXPECT().CountMessages(gomock.Any(), "").Return([]*pmapi.MessagesCount{}, nil),
|
||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||
m.credentialsStore.EXPECT().SwitchAddressMode("user").Return(testCredentials, nil),
|
||||
)
|
||||
|
||||
// Check switch to combined mode.
|
||||
r.NoError(t, user.SwitchAddressMode())
|
||||
r.True(t, user.store.IsCombinedMode())
|
||||
r.True(t, user.creds.IsCombinedAddressMode)
|
||||
r.True(t, user.IsCombinedAddressMode())
|
||||
}
|
||||
|
||||
func TestLogoutUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
user := testNewUser(m)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(testCredentialsDisconnected, nil),
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me"),
|
||||
)
|
||||
|
||||
err := user.Logout()
|
||||
r.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestLogoutUserFailsLogout(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
user := testNewUser(m)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(nil, errors.New("logout failed")),
|
||||
m.credentialsStore.EXPECT().Delete("user").Return(nil),
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me"),
|
||||
)
|
||||
|
||||
err := user.Logout()
|
||||
r.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCheckBridgeLogin(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
user := testNewUser(m)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
err := user.CheckBridgeLogin(testCredentials.BridgePassword)
|
||||
r.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")
|
||||
r.Equal(t, pmapi.ErrUpgradeApplication, err)
|
||||
|
||||
isApplicationOutdated = false
|
||||
}
|
||||
|
||||
func TestCheckBridgeLoginLoggedOut(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
gomock.InOrder(
|
||||
// Mock init of user.
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil),
|
||||
m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any()),
|
||||
m.pmapiClient.EXPECT().ListLabels(gomock.Any()).Return(nil, errors.New("ErrUnauthorized")),
|
||||
m.pmapiClient.EXPECT().Addresses().Return(nil),
|
||||
|
||||
// Mock CheckBridgeLogin.
|
||||
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user"),
|
||||
)
|
||||
|
||||
user, _, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.storeMaker, false)
|
||||
r.NoError(t, err)
|
||||
|
||||
err = user.connect(m.pmapiClient, testCredentialsDisconnected)
|
||||
r.Error(t, err)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
err = user.CheckBridgeLogin(testCredentialsDisconnected.BridgePassword)
|
||||
r.Equal(t, ErrLoggedOutUser, err)
|
||||
}
|
||||
|
||||
func TestCheckBridgeLoginBadPassword(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
user := testNewUser(m)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
err := user.CheckBridgeLogin("wrong!")
|
||||
r.EqualError(t, err, "backend/credentials: incorrect password")
|
||||
}
|
||||
88
internal/users/user_new_test.go
Normal file
88
internal/users/user_new_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
r "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
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.storeMaker, false)
|
||||
r.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNewUserUnlockFails(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
gomock.InOrder(
|
||||
// Init of user.
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
|
||||
m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any()),
|
||||
m.pmapiClient.EXPECT().IsUnlocked().Return(false),
|
||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(errors.New("bad password")),
|
||||
|
||||
// Handle of unlock error.
|
||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(testCredentialsDisconnected, nil),
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me"),
|
||||
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user"),
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user"),
|
||||
)
|
||||
|
||||
checkNewUserHasCredentials(m, "failed to unlock user: bad password", testCredentialsDisconnected)
|
||||
}
|
||||
|
||||
func TestNewUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
|
||||
mockInitConnectedUser(m)
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
checkNewUserHasCredentials(m, "", testCredentials)
|
||||
}
|
||||
|
||||
func checkNewUserHasCredentials(m mocks, wantErr string, wantCreds *credentials.Credentials) {
|
||||
user, _, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.storeMaker, false)
|
||||
r.NoError(m.t, err)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
err = user.connect(m.pmapiClient, testCredentials)
|
||||
if wantErr == "" {
|
||||
r.NoError(m.t, err)
|
||||
} else {
|
||||
r.EqualError(m.t, err, wantErr)
|
||||
}
|
||||
|
||||
r.Equal(m.t, wantCreds, user.creds)
|
||||
|
||||
waitForEvents()
|
||||
}
|
||||
51
internal/users/user_store_test.go
Normal file
51
internal/users/user_store_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
r "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func _TestNeverLongStorePath(t *testing.T) { // nolint[unused]
|
||||
r.Fail(t, "not implemented")
|
||||
}
|
||||
|
||||
func TestClearStoreWithStore(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
user := testNewUser(m)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
r.Nil(t, user.store.Close())
|
||||
user.store = nil
|
||||
r.Nil(t, user.clearStore())
|
||||
}
|
||||
|
||||
func TestClearStoreWithoutStore(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
user := testNewUser(m)
|
||||
defer cleanUpUserData(user)
|
||||
|
||||
r.NotNil(t, user.store)
|
||||
r.Nil(t, user.clearStore())
|
||||
}
|
||||
41
internal/users/user_test.go
Normal file
41
internal/users/user_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
r "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testNewUser sets up a new, authorised user.
|
||||
func testNewUser(m mocks) *User {
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
|
||||
mockInitConnectedUser(m)
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
user, creds, err := newUser(m.PanicHandler, "user", m.eventListener, m.credentialsStore, m.storeMaker, false)
|
||||
r.NoError(m.t, err)
|
||||
|
||||
err = user.connect(m.pmapiClient, creds)
|
||||
r.NoError(m.t, err)
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func cleanUpUserData(u *User) {
|
||||
_ = u.clearStore()
|
||||
}
|
||||
@ -89,24 +89,42 @@ func New(
|
||||
lock: sync.RWMutex{},
|
||||
}
|
||||
|
||||
// FIXME(conman): Handle force upgrade events.
|
||||
/*
|
||||
go func() {
|
||||
defer panicHandler.HandlePanic()
|
||||
u.watchAppOutdated()
|
||||
}()
|
||||
*/
|
||||
go func() {
|
||||
defer panicHandler.HandlePanic()
|
||||
u.watchEvents()
|
||||
}()
|
||||
|
||||
if u.credStorer == nil {
|
||||
log.Error("No credentials store is available")
|
||||
} else if err := u.loadUsersFromCredentialsStore(context.TODO()); err != nil {
|
||||
} else if err := u.loadUsersFromCredentialsStore(); err != nil {
|
||||
log.WithError(err).Error("Could not load all users from credentials store")
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *Users) loadUsersFromCredentialsStore(ctx context.Context) error {
|
||||
func (u *Users) watchEvents() {
|
||||
upgradeCh := u.events.ProvideChannel(events.UpgradeApplicationEvent)
|
||||
internetOnCh := u.events.ProvideChannel(events.InternetOnEvent)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-upgradeCh:
|
||||
isApplicationOutdated = true
|
||||
u.closeAllConnections()
|
||||
case <-internetOnCh:
|
||||
for _, user := range u.users {
|
||||
if user.store == nil {
|
||||
if err := user.loadStore(); err != nil {
|
||||
log.WithError(err).Error("Failed to load store after reconnecting")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Users) loadUsersFromCredentialsStore() error {
|
||||
u.lock.Lock()
|
||||
defer u.lock.Unlock()
|
||||
|
||||
@ -116,23 +134,26 @@ func (u *Users) loadUsersFromCredentialsStore(ctx context.Context) error {
|
||||
}
|
||||
|
||||
for _, userID := range userIDs {
|
||||
l := log.WithField("user", userID)
|
||||
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")
|
||||
l.WithError(err).Warn("Could not create user, skipping")
|
||||
continue
|
||||
}
|
||||
|
||||
u.users = append(u.users, user)
|
||||
|
||||
if creds.IsConnected() {
|
||||
if err := u.loadConnectedUser(ctx, user, creds); err != nil {
|
||||
logrus.WithError(err).Warn("Could not load connected user")
|
||||
// If there is no connection, we don't want to retry. Load should
|
||||
// happen fast enough to not block GUI. When connection is back up,
|
||||
// watchEvents and unlockIfNecessary will finish user init later.
|
||||
if err := u.loadConnectedUser(pmapi.ContextWithoutRetry(context.Background()), user, creds); err != nil {
|
||||
l.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")
|
||||
l.Warn("User is disconnected and must be connected manually")
|
||||
if err := user.connect(u.clientManager.NewClient("", "", "", time.Time{}), creds); err != nil {
|
||||
l.WithError(err).Warn("Could not load disconnected user")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -140,11 +161,6 @@ func (u *Users) loadUsersFromCredentialsStore(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (u *Users) loadConnectedUser(ctx context.Context, user *User, creds *credentials.Credentials) error {
|
||||
uid, ref, err := creds.SplitAPIToken()
|
||||
if err != nil {
|
||||
@ -153,38 +169,27 @@ func (u *Users) loadConnectedUser(ctx context.Context, user *User, creds *creden
|
||||
|
||||
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)
|
||||
// When client cannot be refreshed right away due to no connection,
|
||||
// we create client which will refresh automatically when possible.
|
||||
connectErr := user.connect(u.clientManager.NewClient(uid, "", ref, time.Time{}), creds)
|
||||
|
||||
switch errors.Cause(err) {
|
||||
case pmapi.ErrNoConnection, pmapi.ErrUpgradeApplication:
|
||||
return connectErr
|
||||
}
|
||||
|
||||
if logoutErr := user.logout(); logoutErr != nil {
|
||||
logrus.WithError(logoutErr).Warn("Could not logout user")
|
||||
}
|
||||
return errors.Wrap(err, "could not refresh token")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if creds, err = u.credStorer.UpdateToken(creds.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
|
||||
}
|
||||
}
|
||||
*/
|
||||
return user.connect(client, creds)
|
||||
}
|
||||
|
||||
func (u *Users) closeAllConnections() {
|
||||
@ -198,19 +203,19 @@ func (u *Users) closeAllConnections() {
|
||||
func (u *Users) Login(username, password string) (authClient pmapi.Client, auth *pmapi.Auth, err error) {
|
||||
u.crashBandicoot(username)
|
||||
|
||||
return u.clientManager.NewClientWithLogin(context.TODO(), username, password)
|
||||
return u.clientManager.NewClientWithLogin(context.Background(), username, password)
|
||||
}
|
||||
|
||||
// FinishLogin finishes the login procedure and adds the user into the credentials store.
|
||||
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)
|
||||
apiUser, passphrase, err := getAPIUser(context.Background(), client, password)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get API user")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user, ok := u.hasUser(apiUser.ID); ok {
|
||||
if user.IsConnected() {
|
||||
if err := client.AuthDelete(context.TODO()); err != nil {
|
||||
if err := client.AuthDelete(context.Background()); err != nil {
|
||||
logrus.WithError(err).Warn("Failed to delete new auth session")
|
||||
}
|
||||
|
||||
@ -228,14 +233,16 @@ func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password stri
|
||||
return nil, errors.Wrap(err, "failed to update password of user in credentials store")
|
||||
}
|
||||
|
||||
if err := user.connect(context.TODO(), client, creds); err != nil {
|
||||
if err := user.connect(client, creds); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to reconnect existing user")
|
||||
}
|
||||
|
||||
u.events.Emit(events.UserRefreshEvent, apiUser.ID)
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
if err := u.addNewUser(context.TODO(), client, apiUser, auth, passphrase); err != nil {
|
||||
if err := u.addNewUser(client, apiUser, auth, passphrase); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to add new user")
|
||||
}
|
||||
|
||||
@ -245,7 +252,7 @@ func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password stri
|
||||
}
|
||||
|
||||
// addNewUser adds a new user.
|
||||
func (u *Users) addNewUser(ctx context.Context, client pmapi.Client, apiUser *pmapi.User, auth *pmapi.Auth, passphrase []byte) error {
|
||||
func (u *Users) addNewUser(client pmapi.Client, apiUser *pmapi.User, auth *pmapi.Auth, passphrase []byte) error {
|
||||
u.lock.Lock()
|
||||
defer u.lock.Unlock()
|
||||
|
||||
@ -266,7 +273,7 @@ func (u *Users) addNewUser(ctx context.Context, client pmapi.Client, apiUser *pm
|
||||
return errors.Wrap(err, "failed to create new user")
|
||||
}
|
||||
|
||||
if err := user.connect(ctx, client, creds); err != nil {
|
||||
if err := user.connect(client, creds); err != nil {
|
||||
return errors.Wrap(err, "failed to connect new user")
|
||||
}
|
||||
|
||||
@ -292,7 +299,7 @@ func getAPIUser(ctx context.Context, client pmapi.Client, password string) (*pma
|
||||
|
||||
// We unlock the user's PGP key here to detect if the user's mailbox password is wrong.
|
||||
if err := client.Unlock(ctx, passphrase); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to unlock client")
|
||||
return nil, nil, ErrWrongMailboxPassword
|
||||
}
|
||||
|
||||
user, err := client.CurrentUser(ctx)
|
||||
@ -414,22 +421,13 @@ 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() {
|
||||
// FIXME(conman): Support DoH.
|
||||
// u.apiManager.AllowProxy()
|
||||
u.clientManager.AllowProxy()
|
||||
}
|
||||
|
||||
// DisallowProxy instructs the app to not use DoH to access an API proxy if necessary.
|
||||
// It also needs to work before the app is initialised (because we may need to use the proxy at startup).
|
||||
func (u *Users) DisallowProxy() {
|
||||
// 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 {
|
||||
// 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")
|
||||
u.clientManager.DisallowProxy()
|
||||
}
|
||||
|
||||
// hasUser returns whether the struct currently has a user with ID `id`.
|
||||
|
||||
49
internal/users/users_clear_test.go
Normal file
49
internal/users/users_clear_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
r "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestClearData(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
users := testNewUsersWithUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "users@pm.me")
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "anotheruser@pm.me")
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "alsouser@pm.me")
|
||||
|
||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any())
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(testCredentialsDisconnected, nil)
|
||||
|
||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any())
|
||||
m.credentialsStore.EXPECT().Logout("users").Return(testCredentialsSplitDisconnected, nil)
|
||||
|
||||
m.locator.EXPECT().Clear()
|
||||
|
||||
r.NoError(t, users.ClearData())
|
||||
}
|
||||
69
internal/users/users_delete_test.go
Normal file
69
internal/users/users_delete_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
r "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
users := testNewUsersWithUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(testCredentialsDisconnected, nil),
|
||||
m.credentialsStore.EXPECT().Delete("user").Return(nil),
|
||||
)
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
|
||||
err := users.DeleteUser("user", true)
|
||||
r.NoError(t, err)
|
||||
r.Equal(t, 1, len(users.users))
|
||||
}
|
||||
|
||||
// Even when logout fails, delete is done.
|
||||
func TestDeleteUserWithFailingLogout(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
users := testNewUsersWithUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil),
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(nil, errors.New("logout failed")),
|
||||
// Once called from user.Logout after failed creds.Logout as fallback, and once at the end of users.Logout.
|
||||
m.credentialsStore.EXPECT().Delete("user").Return(nil),
|
||||
m.credentialsStore.EXPECT().Delete("user").Return(nil),
|
||||
)
|
||||
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
|
||||
err := users.DeleteUser("user", true)
|
||||
r.NoError(t, err)
|
||||
r.Equal(t, 1, len(users.users))
|
||||
}
|
||||
76
internal/users/users_get_test.go
Normal file
76
internal/users/users_get_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
r "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetNoUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
checkUsersGetUser(t, m, "nouser", -1, "user nouser not found")
|
||||
}
|
||||
|
||||
func TestGetUserByID(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
checkUsersGetUser(t, m, "user", 0, "")
|
||||
checkUsersGetUser(t, m, "users", 1, "")
|
||||
}
|
||||
|
||||
func TestGetUserByName(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
checkUsersGetUser(t, m, "username", 0, "")
|
||||
checkUsersGetUser(t, m, "usersname", 1, "")
|
||||
}
|
||||
|
||||
func TestGetUserByEmail(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
checkUsersGetUser(t, m, "user@pm.me", 0, "")
|
||||
checkUsersGetUser(t, m, "users@pm.me", 1, "")
|
||||
checkUsersGetUser(t, m, "anotheruser@pm.me", 1, "")
|
||||
checkUsersGetUser(t, m, "alsouser@pm.me", 1, "")
|
||||
}
|
||||
|
||||
func checkUsersGetUser(t *testing.T, m mocks, query string, index int, expectedError string) {
|
||||
users := testNewUsersWithUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
user, err := users.GetUser(query)
|
||||
|
||||
if expectedError != "" {
|
||||
r.EqualError(m.t, err, expectedError)
|
||||
} else {
|
||||
r.NoError(m.t, err)
|
||||
}
|
||||
|
||||
var expectedUser *User
|
||||
if index >= 0 {
|
||||
expectedUser = users.users[index]
|
||||
}
|
||||
r.Equal(m.t, expectedUser, user)
|
||||
}
|
||||
132
internal/users/users_login_test.go
Normal file
132
internal/users/users_login_test.go
Normal file
@ -0,0 +1,132 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"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/pkg/errors"
|
||||
r "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUsersFinishLoginBadMailboxPassword(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
// Init users with no user from keychain.
|
||||
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
|
||||
|
||||
// Set up mocks for FinishLogin.
|
||||
m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil)
|
||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(errors.New("no keys could be unlocked"))
|
||||
|
||||
checkUsersFinishLogin(t, m, testAuthRefresh, testCredentials.MailboxPassword, "", ErrWrongMailboxPassword)
|
||||
}
|
||||
|
||||
func TestUsersFinishLoginNewUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
// Init users with no user from keychain.
|
||||
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
|
||||
|
||||
mockAddingConnectedUser(m)
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
m.clientManager.EXPECT().SendSimpleMetric(gomock.Any(), string(metrics.Setup), string(metrics.NewUser), string(metrics.NoLabel))
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, testCredentials.UserID)
|
||||
|
||||
checkUsersFinishLogin(t, m, testAuthRefresh, testCredentials.MailboxPassword, testCredentials.UserID, nil)
|
||||
}
|
||||
|
||||
func TestUsersFinishLoginExistingDisconnectedUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
// Mock loading disconnected user.
|
||||
m.credentialsStore.EXPECT().List().Return([]string{testCredentialsDisconnected.UserID}, nil)
|
||||
mockLoadingDisconnectedUser(m, testCredentialsDisconnected)
|
||||
|
||||
// Mock process of FinishLogin of already added user.
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil),
|
||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(nil),
|
||||
m.pmapiClient.EXPECT().CurrentUser(gomock.Any()).Return(testPMAPIUserDisconnected, nil),
|
||||
m.credentialsStore.EXPECT().UpdateToken(testCredentialsDisconnected.UserID, testAuthRefresh.UID, testAuthRefresh.RefreshToken).Return(testCredentials, nil),
|
||||
m.credentialsStore.EXPECT().UpdatePassword(testCredentialsDisconnected.UserID, testCredentials.MailboxPassword).Return(testCredentials, nil),
|
||||
)
|
||||
mockInitConnectedUser(m)
|
||||
mockEventLoopNoAction(m)
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, testCredentialsDisconnected.UserID)
|
||||
|
||||
authRefresh := &pmapi.Auth{
|
||||
UserID: testCredentialsDisconnected.UserID,
|
||||
AuthRefresh: pmapi.AuthRefresh{
|
||||
UID: "uid",
|
||||
AccessToken: "acc",
|
||||
RefreshToken: "ref",
|
||||
},
|
||||
}
|
||||
checkUsersFinishLogin(t, m, authRefresh, testCredentials.MailboxPassword, testCredentialsDisconnected.UserID, nil)
|
||||
}
|
||||
|
||||
func TestUsersFinishLoginConnectedUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
// Mock loading connected user.
|
||||
m.credentialsStore.EXPECT().List().Return([]string{testCredentials.UserID}, nil)
|
||||
mockLoadingConnectedUser(m, testCredentials)
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
// Mock process of FinishLogin of already connected user.
|
||||
gomock.InOrder(
|
||||
m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil),
|
||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(nil),
|
||||
m.pmapiClient.EXPECT().CurrentUser(gomock.Any()).Return(testPMAPIUser, nil),
|
||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil),
|
||||
)
|
||||
|
||||
users := testNewUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
_, err := users.FinishLogin(m.pmapiClient, testAuthRefresh, testCredentials.MailboxPassword)
|
||||
r.EqualError(t, err, "user is already connected")
|
||||
}
|
||||
|
||||
func checkUsersFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassword string, expectedUserID string, expectedErr error) {
|
||||
users := testNewUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
user, err := users.FinishLogin(m.pmapiClient, auth, mailboxPassword)
|
||||
|
||||
r.Equal(t, expectedErr, err)
|
||||
|
||||
if expectedUserID != "" {
|
||||
r.Equal(t, expectedUserID, user.ID())
|
||||
r.Equal(t, 1, len(users.users))
|
||||
r.Equal(t, expectedUserID, users.users[0].ID())
|
||||
} else {
|
||||
r.Equal(t, (*User)(nil), user)
|
||||
r.Equal(t, 0, len(users.users))
|
||||
}
|
||||
}
|
||||
@ -22,9 +22,10 @@ import (
|
||||
"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"
|
||||
r "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewUsersNoKeychain(t *testing.T) {
|
||||
@ -32,7 +33,6 @@ func TestNewUsersNoKeychain(t *testing.T) {
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.credentialsStore.EXPECT().List().Return([]string{}, errors.New("no keychain"))
|
||||
|
||||
checkUsersNew(t, m, []*credentials.Credentials{})
|
||||
}
|
||||
|
||||
@ -41,108 +41,73 @@ func TestNewUsersWithoutUsersInCredentialsStore(t *testing.T) {
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
|
||||
|
||||
checkUsersNew(t, m, []*credentials.Credentials{})
|
||||
}
|
||||
|
||||
func TestNewUsersWithDisconnectedUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
gomock.InOrder(
|
||||
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil),
|
||||
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()
|
||||
|
||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
|
||||
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()
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(nil)
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentialsDisconnected, nil)
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
|
||||
checkUsersNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
|
||||
}
|
||||
|
||||
func TestNewUsersWithConnectedUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
|
||||
|
||||
mockConnectedUser(m)
|
||||
m.credentialsStore.EXPECT().List().Return([]string{testCredentials.UserID}, nil)
|
||||
mockLoadingConnectedUser(m, testCredentials)
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
checkUsersNew(t, m, []*credentials.Credentials{testCredentials})
|
||||
}
|
||||
|
||||
func TestNewUsersWithDisconnectedUser(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.credentialsStore.EXPECT().List().Return([]string{testCredentialsDisconnected.UserID}, nil)
|
||||
mockLoadingDisconnectedUser(m, testCredentialsDisconnected)
|
||||
checkUsersNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
|
||||
}
|
||||
|
||||
// Tests two users with different states and checks also the order from
|
||||
// credentials store is kept also in array of users.
|
||||
func TestNewUsersWithUsers(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
m.credentialsStore.EXPECT().List().Return([]string{"userDisconnected", "user"}, nil)
|
||||
|
||||
gomock.InOrder(
|
||||
m.credentialsStore.EXPECT().Get("userDisconnected").Return(testCredentialsDisconnected, nil),
|
||||
m.credentialsStore.EXPECT().Get("userDisconnected").Return(testCredentialsDisconnected, nil),
|
||||
// Set up mocks for store initialisation for the unauth user.
|
||||
m.clientManager.EXPECT().GetClient("userDisconnected").Return(m.pmapiClient),
|
||||
m.pmapiClient.EXPECT().ListLabels().Return(nil, errors.New("ErrUnauthorized")),
|
||||
m.clientManager.EXPECT().GetClient("userDisconnected").Return(m.pmapiClient),
|
||||
m.pmapiClient.EXPECT().Addresses().Return(nil),
|
||||
)
|
||||
|
||||
mockConnectedUser(m)
|
||||
|
||||
m.credentialsStore.EXPECT().List().Return([]string{testCredentialsDisconnected.UserID, testCredentials.UserID}, nil)
|
||||
mockLoadingDisconnectedUser(m, testCredentialsDisconnected)
|
||||
mockLoadingConnectedUser(m, testCredentials)
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
checkUsersNew(t, m, []*credentials.Credentials{testCredentialsDisconnected, testCredentials})
|
||||
}
|
||||
|
||||
func TestNewUsersFirstStart(t *testing.T) {
|
||||
func TestNewUsersWithConnectedUserWithBadToken(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
|
||||
m.credentialsStore.EXPECT().List().Return([]string{}, nil)
|
||||
m.clientManager.EXPECT().NewClientWithRefresh(gomock.Any(), "uid", "acc").Return(nil, nil, errors.New("bad token"))
|
||||
m.clientManager.EXPECT().NewClient("uid", "", "acc", time.Time{}).Return(m.pmapiClient)
|
||||
m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any())
|
||||
m.pmapiClient.EXPECT().IsUnlocked().Return(false)
|
||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(errors.New("not authorized"))
|
||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any())
|
||||
|
||||
testNewUsers(t, m)
|
||||
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil)
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(testCredentialsDisconnected, nil)
|
||||
|
||||
m.eventListener.EXPECT().Emit(events.LogoutEvent, "user")
|
||||
m.eventListener.EXPECT().Emit(events.UserRefreshEvent, "user")
|
||||
m.eventListener.EXPECT().Emit(events.CloseConnectionEvent, "user@pm.me")
|
||||
|
||||
checkUsersNew(t, m, []*credentials.Credentials{testCredentialsDisconnected})
|
||||
}
|
||||
*/
|
||||
|
||||
func checkUsersNew(t *testing.T, m mocks, expectedCredentials []*credentials.Credentials) {
|
||||
users := testNewUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
|
||||
assert.Equal(m.t, len(expectedCredentials), len(users.GetUsers()))
|
||||
r.Equal(m.t, len(expectedCredentials), len(users.GetUsers()))
|
||||
|
||||
credentials := []*credentials.Credentials{}
|
||||
for _, user := range users.users {
|
||||
credentials = append(credentials, user.creds)
|
||||
}
|
||||
|
||||
assert.Equal(m.t, expectedCredentials, credentials)
|
||||
r.Equal(m.t, expectedCredentials, credentials)
|
||||
}
|
||||
|
||||
@ -33,8 +33,9 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
pmapimocks "github.com/ProtonMail/proton-bridge/pkg/pmapi/mocks"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
r "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@ -49,9 +50,12 @@ func TestMain(m *testing.M) {
|
||||
|
||||
var (
|
||||
testAuthRefresh = &pmapi.Auth{ //nolint[gochecknoglobals]
|
||||
UID: "uid",
|
||||
AccessToken: "acc",
|
||||
RefreshToken: "ref",
|
||||
UserID: "user",
|
||||
AuthRefresh: pmapi.AuthRefresh{
|
||||
UID: "uid",
|
||||
AccessToken: "acc",
|
||||
RefreshToken: "ref",
|
||||
},
|
||||
}
|
||||
|
||||
testCredentials = &credentials.Credentials{ //nolint[gochecknoglobals]
|
||||
@ -81,7 +85,7 @@ var (
|
||||
}
|
||||
|
||||
testCredentialsDisconnected = &credentials.Credentials{ //nolint[gochecknoglobals]
|
||||
UserID: "user",
|
||||
UserID: "userDisconnected",
|
||||
Name: "username",
|
||||
Emails: "user@pm.me",
|
||||
APIToken: "",
|
||||
@ -94,7 +98,7 @@ var (
|
||||
}
|
||||
|
||||
testCredentialsSplitDisconnected = &credentials.Credentials{ //nolint[gochecknoglobals]
|
||||
UserID: "users",
|
||||
UserID: "usersDisconnected",
|
||||
Name: "usersname",
|
||||
Emails: "users@pm.me;anotheruser@pm.me;alsouser@pm.me",
|
||||
APIToken: "",
|
||||
@ -111,17 +115,22 @@ var (
|
||||
Name: "username",
|
||||
}
|
||||
|
||||
testPMAPIUserDisconnected = &pmapi.User{ //nolint[gochecknoglobals]
|
||||
ID: "userDisconnected",
|
||||
Name: "username",
|
||||
}
|
||||
|
||||
testPMAPIAddress = &pmapi.Address{ //nolint[gochecknoglobals]
|
||||
ID: "testAddressID",
|
||||
Type: pmapi.OriginalAddress,
|
||||
Email: "user@pm.me",
|
||||
Receive: pmapi.CanReceive,
|
||||
Receive: true,
|
||||
}
|
||||
|
||||
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},
|
||||
{ID: "usersAddress1ID", Email: "users@pm.me", Receive: true, Type: pmapi.OriginalAddress},
|
||||
{ID: "usersAddress2ID", Email: "anotheruser@pm.me", Receive: true, Type: pmapi.AliasAddress},
|
||||
{ID: "usersAddress3ID", Email: "alsouser@pm.me", Receive: true, Type: pmapi.AliasAddress},
|
||||
}
|
||||
|
||||
testPMAPIEvent = &pmapi.Event{ // nolint[gochecknoglobals]
|
||||
@ -129,15 +138,6 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
@ -146,7 +146,7 @@ type mocks struct {
|
||||
PanicHandler *usersmocks.MockPanicHandler
|
||||
credentialsStore *usersmocks.MockCredentialsStorer
|
||||
storeMaker *usersmocks.MockStoreMaker
|
||||
eventListener *MockListener
|
||||
eventListener *usersmocks.MockListener
|
||||
|
||||
clientManager *pmapimocks.MockManager
|
||||
pmapiClient *pmapimocks.MockClient
|
||||
@ -154,6 +154,48 @@ type mocks struct {
|
||||
storeCache *store.Cache
|
||||
}
|
||||
|
||||
func initMocks(t *testing.T) mocks {
|
||||
var mockCtrl *gomock.Controller
|
||||
if os.Getenv("VERBOSITY") == "trace" {
|
||||
mockCtrl = gomock.NewController(&fullStackReporter{t})
|
||||
} else {
|
||||
mockCtrl = gomock.NewController(t)
|
||||
}
|
||||
|
||||
cacheFile, err := ioutil.TempFile("", "bridge-store-cache-*.db")
|
||||
r.NoError(t, err, "could not get temporary file for store cache")
|
||||
|
||||
m := mocks{
|
||||
t: t,
|
||||
|
||||
ctrl: mockCtrl,
|
||||
locator: usersmocks.NewMockLocator(mockCtrl),
|
||||
PanicHandler: usersmocks.NewMockPanicHandler(mockCtrl),
|
||||
credentialsStore: usersmocks.NewMockCredentialsStorer(mockCtrl),
|
||||
storeMaker: usersmocks.NewMockStoreMaker(mockCtrl),
|
||||
eventListener: usersmocks.NewMockListener(mockCtrl),
|
||||
|
||||
clientManager: pmapimocks.NewMockManager(mockCtrl),
|
||||
pmapiClient: pmapimocks.NewMockClient(mockCtrl),
|
||||
|
||||
storeCache: store.NewCache(cacheFile.Name()),
|
||||
}
|
||||
|
||||
// Called during clean-up.
|
||||
m.PanicHandler.EXPECT().HandlePanic().AnyTimes()
|
||||
|
||||
// Set up store factory.
|
||||
m.storeMaker.EXPECT().New(gomock.Any()).DoAndReturn(func(user store.BridgeUser) (*store.Store, error) {
|
||||
var sentryReporter *sentry.Reporter // Sentry reporter is not used under unit tests.
|
||||
dbFile, err := ioutil.TempFile("", "bridge-store-db-*.db")
|
||||
r.NoError(t, err, "could not get temporary file for store db")
|
||||
return store.New(sentryReporter, m.PanicHandler, user, m.eventListener, dbFile.Name(), m.storeCache)
|
||||
}).AnyTimes()
|
||||
m.storeMaker.EXPECT().Remove(gomock.Any()).AnyTimes()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
type fullStackReporter struct {
|
||||
T testing.TB
|
||||
}
|
||||
@ -168,86 +210,18 @@ func (fr *fullStackReporter) Fatalf(format string, args ...interface{}) {
|
||||
fr.T.FailNow()
|
||||
}
|
||||
|
||||
func initMocks(t *testing.T) mocks {
|
||||
var mockCtrl *gomock.Controller
|
||||
if os.Getenv("VERBOSITY") == "trace" {
|
||||
mockCtrl = gomock.NewController(&fullStackReporter{t})
|
||||
} else {
|
||||
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,
|
||||
locator: usersmocks.NewMockLocator(mockCtrl),
|
||||
PanicHandler: usersmocks.NewMockPanicHandler(mockCtrl),
|
||||
credentialsStore: usersmocks.NewMockCredentialsStorer(mockCtrl),
|
||||
storeMaker: usersmocks.NewMockStoreMaker(mockCtrl),
|
||||
eventListener: NewMockListener(mockCtrl),
|
||||
|
||||
clientManager: pmapimocks.NewMockManager(mockCtrl),
|
||||
pmapiClient: pmapimocks.NewMockClient(mockCtrl),
|
||||
|
||||
storeCache: store.NewCache(cacheFile.Name()),
|
||||
}
|
||||
|
||||
// Called during clean-up.
|
||||
m.PanicHandler.EXPECT().HandlePanic().AnyTimes()
|
||||
|
||||
// Set up store factory.
|
||||
m.storeMaker.EXPECT().New(gomock.Any()).DoAndReturn(func(user store.BridgeUser) (*store.Store, error) {
|
||||
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.eventListener, dbFile.Name(), m.storeCache)
|
||||
}).AnyTimes()
|
||||
m.storeMaker.EXPECT().Remove(gomock.Any()).AnyTimes()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func testNewUsersWithUsers(t *testing.T, m mocks) *Users {
|
||||
// Events are asynchronous
|
||||
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(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(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),
|
||||
)
|
||||
m.credentialsStore.EXPECT().List().Return([]string{testCredentials.UserID, testCredentialsSplit.UserID}, nil)
|
||||
mockLoadingConnectedUser(m, testCredentials)
|
||||
mockLoadingConnectedUser(m, testCredentialsSplit)
|
||||
mockEventLoopNoAction(m)
|
||||
|
||||
return testNewUsers(t, m)
|
||||
}
|
||||
|
||||
func testNewUsers(t *testing.T, m mocks) *Users { //nolint[unparam]
|
||||
// FIXME(conman): How to handle force upgrade?
|
||||
// m.eventListener.EXPECT().Add(events.UpgradeApplicationEvent, gomock.Any())
|
||||
m.eventListener.EXPECT().ProvideChannel(events.UpgradeApplicationEvent)
|
||||
m.eventListener.EXPECT().ProvideChannel(events.InternetOnEvent)
|
||||
|
||||
users := New(m.locator, m.PanicHandler, m.eventListener, m.clientManager, m.credentialsStore, m.storeMaker, true)
|
||||
|
||||
@ -256,38 +230,84 @@ func testNewUsers(t *testing.T, m mocks) *Users { //nolint[unparam]
|
||||
return users
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func cleanUpUsersData(b *Users) {
|
||||
for _, user := range b.users {
|
||||
_ = user.clearStore()
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearData(t *testing.T) {
|
||||
m := initMocks(t)
|
||||
defer m.ctrl.Finish()
|
||||
func mockAddingConnectedUser(m mocks) {
|
||||
gomock.InOrder(
|
||||
// Mock of users.FinishLogin.
|
||||
m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil),
|
||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(nil),
|
||||
m.pmapiClient.EXPECT().CurrentUser(gomock.Any()).Return(testPMAPIUser, nil),
|
||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||
m.credentialsStore.EXPECT().Add("user", "username", testAuthRefresh.UID, testAuthRefresh.RefreshToken, testCredentials.MailboxPassword, []string{testPMAPIAddress.Email}).Return(testCredentials, nil),
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
|
||||
)
|
||||
|
||||
// m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).MinTimes(1)
|
||||
// m.clientManager.EXPECT().GetClient("users").Return(m.pmapiClient).MinTimes(1)
|
||||
mockInitConnectedUser(m)
|
||||
}
|
||||
|
||||
users := testNewUsersWithUsers(t, m)
|
||||
defer cleanUpUsersData(users)
|
||||
func mockLoadingConnectedUser(m mocks, creds *credentials.Credentials) {
|
||||
authRefresh := &pmapi.AuthRefresh{
|
||||
UID: "uid",
|
||||
AccessToken: "acc",
|
||||
RefreshToken: "ref",
|
||||
}
|
||||
|
||||
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")
|
||||
gomock.InOrder(
|
||||
// Mock of users.loadUsersFromCredentialsStore.
|
||||
m.credentialsStore.EXPECT().Get(creds.UserID).Return(creds, nil),
|
||||
m.clientManager.EXPECT().NewClientWithRefresh(gomock.Any(), "uid", "acc").Return(m.pmapiClient, authRefresh, nil),
|
||||
m.credentialsStore.EXPECT().UpdateToken(creds.UserID, authRefresh.UID, authRefresh.RefreshToken).Return(creds, nil),
|
||||
)
|
||||
|
||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any())
|
||||
m.credentialsStore.EXPECT().Logout("user").Return(testCredentialsDisconnected, nil)
|
||||
mockInitConnectedUser(m)
|
||||
}
|
||||
|
||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any())
|
||||
m.credentialsStore.EXPECT().Logout("users").Return(testCredentialsSplitDisconnected, nil)
|
||||
func mockInitConnectedUser(m mocks) {
|
||||
// Mock of user initialisation.
|
||||
m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any())
|
||||
m.pmapiClient.EXPECT().IsUnlocked().Return(true).AnyTimes()
|
||||
|
||||
m.locator.EXPECT().Clear()
|
||||
// Mock of store initialisation.
|
||||
gomock.InOrder(
|
||||
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}),
|
||||
)
|
||||
}
|
||||
|
||||
require.NoError(t, users.ClearData())
|
||||
func mockLoadingDisconnectedUser(m mocks, creds *credentials.Credentials) {
|
||||
gomock.InOrder(
|
||||
// Mock of users.loadUsersFromCredentialsStore.
|
||||
m.credentialsStore.EXPECT().Get(creds.UserID).Return(creds, nil),
|
||||
m.clientManager.EXPECT().NewClient("", "", "", time.Time{}).Return(m.pmapiClient),
|
||||
)
|
||||
|
||||
waitForEvents()
|
||||
mockInitDisconnectedUser(m)
|
||||
}
|
||||
|
||||
func mockInitDisconnectedUser(m mocks) {
|
||||
gomock.InOrder(
|
||||
// Mock of user initialisation.
|
||||
m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any()),
|
||||
|
||||
// Mock of store initialisation for the unauthorized user.
|
||||
m.pmapiClient.EXPECT().ListLabels(gomock.Any()).Return(nil, errors.New("ErrUnauthorized")),
|
||||
m.pmapiClient.EXPECT().Addresses().Return(nil),
|
||||
)
|
||||
}
|
||||
|
||||
func mockEventLoopNoAction(m mocks) {
|
||||
@ -297,19 +317,3 @@ func mockEventLoopNoAction(m mocks) {
|
||||
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) {
|
||||
gomock.InOrder(
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
|
||||
|
||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
|
||||
// m.pmapiClient.EXPECT().AuthRefresh("uid:acc").Return(testAuthRefresh, 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(gomock.Any()).Return([]*pmapi.Label{}, nil),
|
||||
m.pmapiClient.EXPECT().CountMessages(gomock.Any(), "").Return([]*pmapi.MessagesCount{}, nil),
|
||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user