2022-10-13 08:58:11 +00:00
|
|
|
// Copyright (c) 2022 Proton AG
|
|
|
|
//
|
|
|
|
// This file is part of Proton Mail Bridge.
|
|
|
|
//
|
|
|
|
// Proton Mail 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.
|
|
|
|
//
|
|
|
|
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
package tests
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-10-09 21:05:52 +00:00
|
|
|
"crypto/tls"
|
2022-11-03 09:43:25 +00:00
|
|
|
"crypto/x509"
|
|
|
|
"encoding/json"
|
2022-08-26 15:00:21 +00:00
|
|
|
"fmt"
|
2022-10-11 17:40:28 +00:00
|
|
|
"net/http/cookiejar"
|
2022-10-28 13:44:32 +00:00
|
|
|
"os"
|
2022-11-03 09:43:25 +00:00
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
2022-10-24 16:56:15 +00:00
|
|
|
"time"
|
2022-08-26 15:00:21 +00:00
|
|
|
|
2022-11-03 09:43:25 +00:00
|
|
|
"github.com/ProtonMail/gluon/queue"
|
2022-08-26 15:00:21 +00:00
|
|
|
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
2022-11-03 09:43:25 +00:00
|
|
|
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
2022-10-11 17:40:28 +00:00
|
|
|
"github.com/ProtonMail/proton-bridge/v2/internal/cookies"
|
2022-08-26 15:00:21 +00:00
|
|
|
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
2022-11-03 09:43:25 +00:00
|
|
|
frontend "github.com/ProtonMail/proton-bridge/v2/internal/frontend/grpc"
|
2022-08-26 15:00:21 +00:00
|
|
|
"github.com/ProtonMail/proton-bridge/v2/internal/useragent"
|
|
|
|
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
|
2022-11-03 09:43:25 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2022-10-09 21:05:52 +00:00
|
|
|
"gitlab.protontech.ch/go/liteapi"
|
2022-11-03 09:43:25 +00:00
|
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
"google.golang.org/grpc/metadata"
|
|
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
2022-08-26 15:00:21 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func (t *testCtx) startBridge() error {
|
2022-11-03 09:43:25 +00:00
|
|
|
logrus.Info("Starting bridge")
|
|
|
|
|
|
|
|
eventCh, err := t.initBridge()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not create bridge: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Info("Starting frontend service")
|
|
|
|
|
|
|
|
if err := t.initFrontendService(eventCh); err != nil {
|
|
|
|
return fmt.Errorf("could not create frontend service: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Info("Starting frontend client")
|
|
|
|
|
|
|
|
if err := t.initFrontendClient(); err != nil {
|
|
|
|
return fmt.Errorf("could not create frontend client: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.events.await(events.AllUsersLoaded{}, 30*time.Second)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testCtx) stopBridge() error {
|
|
|
|
if err := t.closeFrontendService(context.Background()); err != nil {
|
|
|
|
return fmt.Errorf("could not close frontend: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.closeFrontendClient(); err != nil {
|
|
|
|
return fmt.Errorf("could not close frontend client: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.closeBridge(context.Background()); err != nil {
|
|
|
|
return fmt.Errorf("could not close bridge: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testCtx) initBridge() (<-chan events.Event, error) {
|
|
|
|
if t.bridge != nil {
|
|
|
|
return nil, fmt.Errorf("bridge is already started")
|
|
|
|
}
|
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
// Bridge will enable the proxy by default at startup.
|
2022-10-09 21:05:52 +00:00
|
|
|
t.mocks.ProxyCtl.EXPECT().AllowProxy()
|
2022-08-26 15:00:21 +00:00
|
|
|
|
|
|
|
// Get the path to the vault.
|
|
|
|
vaultDir, err := t.locator.ProvideSettingsPath()
|
|
|
|
if err != nil {
|
2022-11-03 09:43:25 +00:00
|
|
|
return nil, fmt.Errorf("could not get vault dir: %w", err)
|
2022-08-26 15:00:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the default gluon path.
|
|
|
|
gluonDir, err := t.locator.ProvideGluonPath()
|
|
|
|
if err != nil {
|
2022-11-03 09:43:25 +00:00
|
|
|
return nil, fmt.Errorf("could not get gluon dir: %w", err)
|
2022-08-26 15:00:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create the vault.
|
|
|
|
vault, corrupt, err := vault.New(vaultDir, gluonDir, t.storeKey)
|
|
|
|
if err != nil {
|
2022-11-03 09:43:25 +00:00
|
|
|
return nil, fmt.Errorf("could not create vault: %w", err)
|
2022-08-26 15:00:21 +00:00
|
|
|
} else if corrupt {
|
2022-11-03 09:43:25 +00:00
|
|
|
return nil, fmt.Errorf("vault is corrupt")
|
2022-08-26 15:00:21 +00:00
|
|
|
}
|
|
|
|
|
2022-10-11 21:24:20 +00:00
|
|
|
// Create the underlying cookie jar.
|
2022-10-11 17:40:28 +00:00
|
|
|
jar, err := cookiejar.New(nil)
|
|
|
|
if err != nil {
|
2022-11-03 09:43:25 +00:00
|
|
|
return nil, fmt.Errorf("could not create cookie jar: %w", err)
|
2022-10-11 17:40:28 +00:00
|
|
|
}
|
|
|
|
|
2022-10-11 21:24:20 +00:00
|
|
|
// Create the persisting cookie jar.
|
2022-10-11 17:40:28 +00:00
|
|
|
persister, err := cookies.NewCookieJar(jar, vault)
|
|
|
|
if err != nil {
|
2022-11-03 09:43:25 +00:00
|
|
|
return nil, fmt.Errorf("could not create cookie persister: %w", err)
|
2022-10-11 17:40:28 +00:00
|
|
|
}
|
|
|
|
|
2022-11-03 09:43:25 +00:00
|
|
|
var (
|
|
|
|
logIMAP bool
|
|
|
|
logSMTP bool
|
|
|
|
)
|
2022-10-28 13:44:32 +00:00
|
|
|
|
|
|
|
if len(os.Getenv("FEATURE_TEST_LOG_IMAP")) != 0 {
|
|
|
|
logIMAP = true
|
|
|
|
}
|
|
|
|
|
2022-11-03 09:43:25 +00:00
|
|
|
if len(os.Getenv("FEATURE_TEST_LOG_SMTP")) != 0 {
|
|
|
|
logSMTP = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if logIMAP || logSMTP {
|
|
|
|
logrus.SetLevel(logrus.TraceLevel)
|
|
|
|
}
|
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
// Create the bridge.
|
2022-10-11 22:20:04 +00:00
|
|
|
bridge, eventCh, err := bridge.New(
|
2022-10-13 00:33:20 +00:00
|
|
|
// App stuff
|
2022-08-26 15:00:21 +00:00
|
|
|
t.locator,
|
|
|
|
vault,
|
2022-10-11 17:40:28 +00:00
|
|
|
t.mocks.Autostarter,
|
|
|
|
t.mocks.Updater,
|
|
|
|
t.version,
|
|
|
|
|
2022-10-13 00:33:20 +00:00
|
|
|
// API stuff
|
2022-10-11 17:40:28 +00:00
|
|
|
t.api.GetHostURL(),
|
|
|
|
persister,
|
2022-08-26 15:00:21 +00:00
|
|
|
useragent.New(),
|
|
|
|
t.mocks.TLSReporter,
|
2022-10-09 21:05:52 +00:00
|
|
|
liteapi.NewDialer(t.netCtl, &tls.Config{InsecureSkipVerify: true}).GetRoundTripper(),
|
|
|
|
t.mocks.ProxyCtl,
|
2022-10-21 16:41:31 +00:00
|
|
|
t.mocks.CrashHandler,
|
|
|
|
t.reporter,
|
2022-10-11 17:40:28 +00:00
|
|
|
|
2022-10-13 00:33:20 +00:00
|
|
|
// Logging stuff
|
2022-10-28 13:44:32 +00:00
|
|
|
logIMAP,
|
|
|
|
logIMAP,
|
2022-11-03 09:43:25 +00:00
|
|
|
logSMTP,
|
2022-08-26 15:00:21 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
2022-11-03 09:43:25 +00:00
|
|
|
return nil, fmt.Errorf("could not create bridge: %w", err)
|
2022-08-26 15:00:21 +00:00
|
|
|
}
|
|
|
|
|
2022-11-03 09:43:25 +00:00
|
|
|
t.bridge = bridge
|
2022-10-13 00:33:20 +00:00
|
|
|
|
2022-11-03 09:43:25 +00:00
|
|
|
return t.events.collectFrom(eventCh), nil
|
|
|
|
}
|
2022-10-11 22:20:04 +00:00
|
|
|
|
2022-11-03 09:43:25 +00:00
|
|
|
func (t *testCtx) closeBridge(ctx context.Context) error {
|
|
|
|
if t.bridge == nil {
|
|
|
|
return fmt.Errorf("bridge is not started")
|
|
|
|
}
|
|
|
|
|
|
|
|
t.bridge.Close(ctx)
|
2022-08-26 15:00:21 +00:00
|
|
|
|
2022-11-04 13:50:43 +00:00
|
|
|
t.bridge = nil
|
|
|
|
|
2022-08-26 15:00:21 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-03 09:43:25 +00:00
|
|
|
func (t *testCtx) initFrontendService(eventCh <-chan events.Event) error {
|
|
|
|
if t.service != nil {
|
|
|
|
return fmt.Errorf("frontend service is already started")
|
|
|
|
}
|
|
|
|
|
|
|
|
// When starting the frontend, we might enable autostart on bridge if it isn't already.
|
|
|
|
t.mocks.Autostarter.EXPECT().Enable().AnyTimes()
|
|
|
|
|
|
|
|
service, err := frontend.NewService(
|
|
|
|
new(mockCrashHandler),
|
|
|
|
new(mockRestarter),
|
|
|
|
t.locator,
|
|
|
|
t.bridge,
|
|
|
|
eventCh,
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not create service: %w", err)
|
2022-08-26 15:00:21 +00:00
|
|
|
}
|
|
|
|
|
2022-11-03 09:43:25 +00:00
|
|
|
logrus.Info("Frontend service started")
|
|
|
|
|
|
|
|
t.service = service
|
2022-10-26 21:09:05 +00:00
|
|
|
|
2022-11-03 09:43:25 +00:00
|
|
|
t.serviceWG.Add(1)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
defer t.serviceWG.Done()
|
|
|
|
|
|
|
|
if err := service.Loop(); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}()
|
2022-08-26 15:00:21 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2022-11-03 09:43:25 +00:00
|
|
|
|
|
|
|
func (t *testCtx) closeFrontendService(ctx context.Context) error {
|
|
|
|
if t.service == nil {
|
|
|
|
return fmt.Errorf("frontend service is not started")
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := t.client.Quit(ctx, &emptypb.Empty{}); err != nil {
|
|
|
|
return fmt.Errorf("could not quit frontend: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.serviceWG.Wait()
|
|
|
|
|
|
|
|
logrus.Info("Frontend service stopped")
|
|
|
|
|
|
|
|
t.service = nil
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testCtx) initFrontendClient() error {
|
|
|
|
if t.client != nil {
|
|
|
|
return fmt.Errorf("frontend client is already started")
|
|
|
|
}
|
|
|
|
|
|
|
|
settings, err := t.locator.ProvideSettingsPath()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not get settings path: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := os.ReadFile(filepath.Join(settings, "grpcServerConfig.json"))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not read grpcServerConfig.json: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var cfg frontend.Config
|
|
|
|
|
|
|
|
if err := json.Unmarshal(b, &cfg); err != nil {
|
|
|
|
return fmt.Errorf("could not unmarshal grpcServerConfig.json: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
cp := x509.NewCertPool()
|
|
|
|
|
|
|
|
if !cp.AppendCertsFromPEM([]byte(cfg.Cert)) {
|
|
|
|
return fmt.Errorf("failed to append certificates to pool")
|
|
|
|
}
|
|
|
|
|
|
|
|
conn, err := grpc.DialContext(
|
|
|
|
context.Background(),
|
|
|
|
fmt.Sprintf("%v:%d", constants.Host, cfg.Port),
|
|
|
|
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{RootCAs: cp})),
|
|
|
|
grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
|
|
|
return invoker(metadata.AppendToOutgoingContext(ctx, "server-token", cfg.Token), method, req, reply, cc, opts...)
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not dial grpc server: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
client := frontend.NewBridgeClient(conn)
|
|
|
|
|
|
|
|
stream, err := client.RunEventStream(context.Background(), &frontend.EventStreamRequest{ClientPlatform: runtime.GOOS})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not start event stream: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
eventCh := queue.NewQueuedChannel[*frontend.StreamEvent](0, 0)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
defer eventCh.CloseAndDiscardQueued()
|
|
|
|
|
|
|
|
for {
|
|
|
|
event, err := stream.Recv()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
eventCh.Enqueue(event)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
logrus.Info("Frontend client started")
|
|
|
|
|
|
|
|
t.client = client
|
|
|
|
t.clientConn = conn
|
|
|
|
t.clientEventCh = eventCh
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testCtx) closeFrontendClient() error {
|
|
|
|
if t.client == nil {
|
|
|
|
return fmt.Errorf("frontend client is not started")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.clientConn.Close(); err != nil {
|
|
|
|
return fmt.Errorf("could not close frontend client connection: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Info("Frontend client stopped")
|
|
|
|
|
|
|
|
t.client = nil
|
|
|
|
t.clientConn = nil
|
|
|
|
t.clientEventCh = nil
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockCrashHandler struct{}
|
|
|
|
|
|
|
|
func (m *mockCrashHandler) HandlePanic() {}
|
|
|
|
|
|
|
|
type mockRestarter struct{}
|
|
|
|
|
|
|
|
func (m *mockRestarter) Set(restart, crash bool) {}
|
|
|
|
|
|
|
|
func (m *mockRestarter) AddFlags(flags ...string) {}
|
|
|
|
|
|
|
|
func (m *mockRestarter) Override(exe string) {}
|