GODT-1166: Reduce the number of auth for live test

- Changed: Do not reauth controller clients.
- Changed: Verbosisty is set only once before run
- Changed: AddUser takes TestAccount as argument
- Added: Setup/clean up before/after test run
- Added: Access to the current refresh token from pmapi.Client interface.
- Added: Context function to add test a user to bridge without login, just call users.FinishLogin.
- Added: PMAPIController.GetAuthClient returns authenticated client for username.
- Added: Persistent clients does not loggout after every scenario.
- Changed: Disabled no-internet tests.
This commit is contained in:
Jakub
2021-05-19 09:57:36 +02:00
committed by Jakub Cuth
parent 5bf359d34f
commit 0c6a098af9
25 changed files with 334 additions and 86 deletions

View File

@ -21,45 +21,36 @@ import (
"net/http"
"sync"
"github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/sirupsen/logrus"
)
// Controller implements PMAPIController interface for specified endpoint.
type Controller struct {
log *logrus.Entry
// Internal states.
lock *sync.RWMutex
calls []*fakeCall
pmapiByUsername map[string]pmapi.Client
messageIDsByUsername map[string][]string
clientManager pmapi.Manager
// State controlled by test.
noInternetConnection bool
}
func NewController(app string) (*Controller, pmapi.Manager) {
cm := pmapi.New(pmapi.NewConfig(getAppVersionName(app), constants.Version))
func NewController(_ string) (*Controller, pmapi.Manager) {
controller := &Controller{
log: logrus.WithField("pkg", "live-controller"),
lock: &sync.RWMutex{},
calls: []*fakeCall{},
pmapiByUsername: map[string]pmapi.Client{},
messageIDsByUsername: map[string][]string{},
clientManager: cm,
noInternetConnection: false,
}
cm.SetTransport(&fakeTransport{
persistentClients.manager.SetTransport(&fakeTransport{
ctl: controller,
transport: http.DefaultTransport,
})
return controller, cm
}
func getAppVersionName(app string) string {
if app == "ie" {
return "importExport"
}
return app
return controller, persistentClients.manager
}

View File

@ -37,9 +37,9 @@ var systemLabelNameToID = map[string]string{ //nolint[gochecknoglobals]
}
func (ctl *Controller) AddUserLabel(username string, label *pmapi.Label) error {
client, ok := ctl.pmapiByUsername[username]
if !ok {
return fmt.Errorf("user %s does not exist", username)
client, err := getPersistentClient(username)
if err != nil {
return err
}
label.Exclusive = getLabelExclusive(label.Name)
@ -68,9 +68,9 @@ func (ctl *Controller) getLabelID(username, labelName string) (string, error) {
return labelID, nil
}
client, ok := ctl.pmapiByUsername[username]
if !ok {
return "", fmt.Errorf("user %s does not exist", username)
client, err := getPersistentClient(username)
if err != nil {
return "", err
}
labels, err := client.ListLabels(context.Background())

View File

@ -19,7 +19,6 @@ package liveapi
import (
"context"
"fmt"
messageUtils "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
@ -31,9 +30,9 @@ func (ctl *Controller) AddUserMessage(username string, message *pmapi.Message) (
return "", errors.New("add user messages with attachments is not implemented for live")
}
client, ok := ctl.pmapiByUsername[username]
if !ok {
return "", fmt.Errorf("user %s does not exist", username)
client, err := getPersistentClient(username)
if err != nil {
return "", err
}
if message.Flags == 0 {
@ -75,9 +74,9 @@ func (ctl *Controller) AddUserMessage(username string, message *pmapi.Message) (
}
func (ctl *Controller) GetMessages(username, labelID string) ([]*pmapi.Message, error) {
client, ok := ctl.pmapiByUsername[username]
if !ok {
return nil, fmt.Errorf("user %s does not exist", username)
client, err := getPersistentClient(username)
if err != nil {
return nil, err
}
page := 0

View File

@ -0,0 +1,131 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.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 liveapi
import (
"context"
"fmt"
"math/rand"
"os"
"github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/pkg/srp"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// persistentClients keeps authenticated clients for tests.
//
// We need to reduce the number of authentication done by live tests.
// Before every *scenario* we are creating and authenticating new client.
// This is not necessary for controller purposes. We can reuse the same clients
// for all tests.
//
//nolint[gochecknoglobals]
var persistentClients = struct {
manager pmapi.Manager
byName map[string]pmapi.Client
saltByName map[string]string
}{}
type persistentClient struct {
pmapi.Client
username string
}
// AuthDelete is noop. All sessions will be closed in CleanupPersistentClients.
func (pc *persistentClient) AuthDelete(_ context.Context) error {
return nil
}
// AuthSalt returns cached string. Otherwise after some time there is an error:
//
// Access token does not have sufficient scope
//
// while all other routes works normally. Need to confirm with Aron that this
// is expected behaviour.
func (pc *persistentClient) AuthSalt(_ context.Context) (string, error) {
return persistentClients.saltByName[pc.username], nil
}
func SetupPersistentClients() {
app := os.Getenv("TEST_APP")
persistentClients.manager = pmapi.New(pmapi.NewConfig(getAppVersionName(app), constants.Version))
persistentClients.manager.SetLogging(logrus.WithField("pkg", "liveapi"), logrus.GetLevel() == logrus.TraceLevel)
persistentClients.byName = map[string]pmapi.Client{}
persistentClients.saltByName = map[string]string{}
}
func getAppVersionName(app string) string {
if app == "ie" {
return "importExport"
}
return app
}
func CleanupPersistentClients() {
for username, client := range persistentClients.byName {
if err := client.AuthDelete(context.Background()); err != nil {
logrus.WithError(err).
WithField("username", username).
Error("Failed to logout persistent client")
}
}
}
func addPersistentClient(username string, password, mailboxPassword []byte) (pmapi.Client, error) {
if cl, ok := persistentClients.byName[username]; ok {
return cl, nil
}
srp.RandReader = rand.New(rand.NewSource(42)) //nolint[gosec] It is OK to use weaker random number generator here
client, _, err := persistentClients.manager.NewClientWithLogin(context.Background(), username, password)
if err != nil {
return nil, errors.Wrap(err, "failed to create new persistent client")
}
salt, err := client.AuthSalt(context.Background())
if err != nil {
return nil, errors.Wrap(err, "persistent client: failed to get salt")
}
hashedMboxPass, err := pmapi.HashMailboxPassword(mailboxPassword, salt)
if err != nil {
return nil, errors.Wrap(err, "persistent client: failed to hash mailbox password")
}
if err := client.Unlock(context.Background(), hashedMboxPass); err != nil {
return nil, errors.Wrap(err, "persistent client: failed to unlock user")
}
persistentClients.byName[username] = client
persistentClients.saltByName[username] = salt
return client, nil
}
func getPersistentClient(username string) (pmapi.Client, error) {
v, ok := persistentClients.byName[username]
if !ok {
return nil, fmt.Errorf("user %s does not exist", username)
}
return &persistentClient{v, username}, nil
}

View File

@ -21,43 +21,42 @@ import (
"context"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/test/accounts"
"github.com/cucumber/godog"
"github.com/pkg/errors"
)
func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password []byte, twoFAEnabled bool) error {
if twoFAEnabled {
func (ctl *Controller) AddUser(account *accounts.TestAccount) error {
if account.IsTwoFAEnabled() {
return godog.ErrPending
}
client, _, err := ctl.clientManager.NewClientWithLogin(context.Background(), user.Name, password)
client, err := addPersistentClient(account.User().Name, account.Password(), account.MailboxPassword())
if err != nil {
return errors.Wrap(err, "failed to create new client")
return errors.Wrap(err, "failed to add persistent client")
}
salt, err := client.AuthSalt(context.Background())
if err != nil {
return errors.Wrap(err, "failed to get salt")
}
mailboxPassword, err := pmapi.HashMailboxPassword(password, salt)
if err != nil {
return errors.Wrap(err, "failed to hash mailbox password")
}
if err := client.Unlock(context.Background(), mailboxPassword); err != nil {
return errors.Wrap(err, "failed to unlock user")
}
if err := cleanup(client, addresses); err != nil {
if err := cleanup(client, account.Addresses()); err != nil {
return errors.Wrap(err, "failed to clean user")
}
ctl.pmapiByUsername[user.Name] = client
return nil
}
func (ctl *Controller) ReorderAddresses(user *pmapi.User, addressIDs []string) error {
return ctl.pmapiByUsername[user.Name].ReorderAddresses(context.Background(), addressIDs)
client, err := getPersistentClient(user.Name)
if err != nil {
return err
}
return client.ReorderAddresses(context.Background(), addressIDs)
}
func (ctl *Controller) GetAuthClient(username string) pmapi.Client {
client, err := getPersistentClient(username)
if err != nil {
ctl.log.WithError(err).
WithField("username", username).
Fatal("Cannot get authenticated client")
}
return client
}