forked from Silverfish/proton-bridge
GODT-35: New pmapi client and manager using resty
This commit is contained in:
@ -26,6 +26,7 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/internal/sentry"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
// GetBridge returns bridge instance.
|
||||
@ -52,7 +53,6 @@ func (ctx *TestContext) RestartBridge() error {
|
||||
_ = user.GetStore().Close()
|
||||
}
|
||||
|
||||
ctx.bridge.StopWatchers()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
ctx.withBridgeInstance()
|
||||
@ -68,7 +68,7 @@ func newBridgeInstance(
|
||||
settings *fakeSettings,
|
||||
credStore users.CredentialsStorer,
|
||||
eventListener listener.Listener,
|
||||
clientManager users.ClientManager,
|
||||
clientManager pmapi.Manager,
|
||||
) *bridge.Bridge {
|
||||
sentryReporter := sentry.NewReporter("bridge", constants.Version, useragent.New())
|
||||
panicHandler := &panicHandler{t: t}
|
||||
|
||||
@ -23,7 +23,6 @@ import (
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
@ -53,7 +52,7 @@ type TestContext struct {
|
||||
// pmapiController is used to control real or fake pmapi clients.
|
||||
// The clients are created by the clientManager.
|
||||
pmapiController PMAPIController
|
||||
clientManager *pmapi.ClientManager
|
||||
clientManager pmapi.Manager
|
||||
|
||||
// Core related variables.
|
||||
bridge *bridge.Bridge
|
||||
@ -99,10 +98,7 @@ func New(app string) *TestContext {
|
||||
|
||||
userAgent := useragent.New()
|
||||
|
||||
cm := pmapi.NewClientManager(
|
||||
pmapi.GetAPIConfig(getConfigName(app), constants.Version),
|
||||
userAgent,
|
||||
)
|
||||
pmapiController, clientManager := newPMAPIController()
|
||||
|
||||
ctx := &TestContext{
|
||||
t: &bddT{},
|
||||
@ -111,8 +107,8 @@ func New(app string) *TestContext {
|
||||
settings: newFakeSettings(),
|
||||
listener: listener.New(),
|
||||
userAgent: userAgent,
|
||||
pmapiController: newPMAPIController(cm),
|
||||
clientManager: cm,
|
||||
pmapiController: pmapiController,
|
||||
clientManager: clientManager,
|
||||
testAccounts: newTestAccounts(),
|
||||
credStore: newFakeCredStore(),
|
||||
imapClients: make(map[string]*mocks.IMAPClient),
|
||||
@ -164,7 +160,7 @@ func (ctx *TestContext) GetPMAPIController() PMAPIController {
|
||||
}
|
||||
|
||||
// GetClientManager returns client manager being used for testing.
|
||||
func (ctx *TestContext) GetClientManager() *pmapi.ClientManager {
|
||||
func (ctx *TestContext) GetClientManager() pmapi.Manager {
|
||||
return ctx.clientManager
|
||||
}
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ func (c *fakeCredStore) List() (userIDs []string, err error) {
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) Add(userID, userName, apiToken, mailboxPassword string, emails []string) (*credentials.Credentials, error) {
|
||||
func (c *fakeCredStore) Add(userID, userName, uid, ref, mailboxPassword string, emails []string) (*credentials.Credentials, error) {
|
||||
bridgePassword := bridgePassword
|
||||
if c, ok := c.credentials[userID]; ok {
|
||||
bridgePassword = c.BridgePassword
|
||||
@ -60,7 +60,7 @@ func (c *fakeCredStore) Add(userID, userName, apiToken, mailboxPassword string,
|
||||
UserID: userID,
|
||||
Name: userName,
|
||||
Emails: strings.Join(emails, ";"),
|
||||
APIToken: apiToken,
|
||||
APIToken: uid + ":" + ref,
|
||||
MailboxPassword: mailboxPassword,
|
||||
BridgePassword: bridgePassword,
|
||||
IsCombinedAddressMode: true, // otherwise by default starts in split mode
|
||||
@ -73,36 +73,38 @@ func (c *fakeCredStore) Get(userID string) (*credentials.Credentials, error) {
|
||||
return c.credentials[userID], nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) SwitchAddressMode(userID string) error {
|
||||
return nil
|
||||
func (c *fakeCredStore) SwitchAddressMode(userID string) (*credentials.Credentials, error) {
|
||||
// FIXME(conman): Why is this empty?
|
||||
return c.credentials[userID], nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) UpdateEmails(userID string, emails []string) error {
|
||||
return nil
|
||||
func (c *fakeCredStore) UpdateEmails(userID string, emails []string) (*credentials.Credentials, error) {
|
||||
// FIXME(conman): Why is this empty?
|
||||
return c.credentials[userID], nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) UpdatePassword(userID, password string) error {
|
||||
func (c *fakeCredStore) UpdatePassword(userID, password string) (*credentials.Credentials, error) {
|
||||
creds, err := c.Get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
creds.MailboxPassword = password
|
||||
return nil
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) UpdateToken(userID, apiToken string) error {
|
||||
func (c *fakeCredStore) UpdateToken(userID, uid, ref string) (*credentials.Credentials, error) {
|
||||
creds, err := c.Get(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
creds.APIToken = apiToken
|
||||
return nil
|
||||
creds.APIToken = uid + ":" + ref
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) Logout(userID string) error {
|
||||
func (c *fakeCredStore) Logout(userID string) (*credentials.Credentials, error) {
|
||||
c.credentials[userID].APIToken = ""
|
||||
c.credentials[userID].MailboxPassword = ""
|
||||
return nil
|
||||
return c.credentials[userID], nil
|
||||
}
|
||||
|
||||
func (c *fakeCredStore) Delete(userID string) error {
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
// GetImportExport returns import-export instance.
|
||||
@ -42,7 +43,7 @@ func newImportExportInstance(
|
||||
cache importexport.Cacher,
|
||||
credStore users.CredentialsStorer,
|
||||
eventListener listener.Listener,
|
||||
clientManager users.ClientManager,
|
||||
clientManager pmapi.Manager,
|
||||
) *importexport.ImportExport {
|
||||
panicHandler := &panicHandler{t: t}
|
||||
return importexport.New(locations, cache, panicHandler, eventListener, clientManager, credStore)
|
||||
|
||||
@ -39,37 +39,15 @@ type PMAPIController interface {
|
||||
GetCalls(method, path string) [][]byte
|
||||
}
|
||||
|
||||
func newPMAPIController(cm *pmapi.ClientManager) PMAPIController {
|
||||
func newPMAPIController() (PMAPIController, pmapi.Manager) {
|
||||
switch os.Getenv(EnvName) {
|
||||
case EnvFake:
|
||||
return newFakePMAPIController(cm)
|
||||
return fakeapi.NewController()
|
||||
|
||||
case EnvLive:
|
||||
return newLivePMAPIController(cm)
|
||||
return liveapi.NewController()
|
||||
|
||||
default:
|
||||
panic("unknown env")
|
||||
}
|
||||
}
|
||||
|
||||
func newFakePMAPIController(cm *pmapi.ClientManager) PMAPIController {
|
||||
return newFakePMAPIControllerWrap(fakeapi.NewController(cm))
|
||||
}
|
||||
|
||||
type fakePMAPIControllerWrap struct {
|
||||
*fakeapi.Controller
|
||||
}
|
||||
|
||||
func newFakePMAPIControllerWrap(controller *fakeapi.Controller) PMAPIController {
|
||||
return &fakePMAPIControllerWrap{Controller: controller}
|
||||
}
|
||||
|
||||
func newLivePMAPIController(cm *pmapi.ClientManager) PMAPIController {
|
||||
return newLiveAPIControllerWrap(liveapi.NewController(cm))
|
||||
}
|
||||
|
||||
type liveAPIControllerWrap struct {
|
||||
*liveapi.Controller
|
||||
}
|
||||
|
||||
func newLiveAPIControllerWrap(controller *liveapi.Controller) PMAPIController {
|
||||
return &liveAPIControllerWrap{Controller: controller}
|
||||
}
|
||||
|
||||
65
test/context/pmapi_manager.go
Normal file
65
test/context/pmapi_manager.go
Normal file
@ -0,0 +1,65 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
func newLivePMAPIManager() pmapi.Manager {
|
||||
return pmapi.New(pmapi.DefaultConfig)
|
||||
}
|
||||
|
||||
func newFakePMAPIManager() pmapi.Manager {
|
||||
return &fakePMAPIManager{}
|
||||
}
|
||||
|
||||
type fakePMAPIManager struct{}
|
||||
|
||||
func (*fakePMAPIManager) NewClient(string, string, string, time.Time) pmapi.Client {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) NewClientWithRefresh(context.Context, string, string) (pmapi.Client, *pmapi.Auth, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) NewClientWithLogin(context.Context, string, string) (pmapi.Client, *pmapi.Auth, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) DownloadAndVerify(kr *crypto.KeyRing, url, sig string) ([]byte, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) ReportBug(context.Context, pmapi.ReportBugReq) error {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) SendSimpleMetric(context.Context, string, string, string) error {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) SetLogger(resty.Logger) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) SetTransport(http.RoundTripper) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) SetCookieJar(http.CookieJar) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) SetRetryCount(int) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) AddConnectionObserver(pmapi.ConnectionObserver) {
|
||||
panic("TODO")
|
||||
}
|
||||
@ -18,6 +18,7 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
@ -25,6 +26,7 @@ import (
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/store"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/srp"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -36,7 +38,7 @@ func (ctx *TestContext) GetUsers() *users.Users {
|
||||
}
|
||||
|
||||
// LoginUser logs in the user with the given username, password, and mailbox password.
|
||||
func (ctx *TestContext) LoginUser(username, password, mailboxPassword string) (err error) {
|
||||
func (ctx *TestContext) LoginUser(username, password, mailboxPassword string) error {
|
||||
srp.RandReader = rand.New(rand.NewSource(42)) //nolint[gosec] It is OK to use weaker random number generator here
|
||||
|
||||
client, auth, err := ctx.users.Login(username, password)
|
||||
@ -44,8 +46,8 @@ func (ctx *TestContext) LoginUser(username, password, mailboxPassword string) (e
|
||||
return errors.Wrap(err, "failed to login")
|
||||
}
|
||||
|
||||
if auth.HasTwoFactor() {
|
||||
if err := client.Auth2FA("2fa code", auth); err != nil {
|
||||
if auth.TwoFA.Enabled == pmapi.TOTPEnabled {
|
||||
if err := client.Auth2FA(context.TODO(), pmapi.Auth2FAReq{TwoFactorCode: "2fa code"}); err != nil {
|
||||
return errors.Wrap(err, "failed to login with 2FA")
|
||||
}
|
||||
}
|
||||
@ -57,7 +59,7 @@ func (ctx *TestContext) LoginUser(username, password, mailboxPassword string) (e
|
||||
|
||||
ctx.addCleanupChecked(user.Logout, "Logging out user")
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUser retrieves the bridge user matching the given query string.
|
||||
|
||||
@ -19,6 +19,7 @@ package fakeapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -53,7 +54,7 @@ func newTestAttachment(iAtt int, msgID string) *pmapi.Attachment {
|
||||
}
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) GetAttachment(attachmentID string) (io.ReadCloser, error) {
|
||||
func (api *FakePMAPI) GetAttachment(_ context.Context, attachmentID string) (io.ReadCloser, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/mail/v4/attachments/"+attachmentID, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -65,7 +66,7 @@ func (api *FakePMAPI) GetAttachment(attachmentID string) (io.ReadCloser, error)
|
||||
return ioutil.NopCloser(r), nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) CreateAttachment(attachment *pmapi.Attachment, data io.Reader, signature io.Reader) (*pmapi.Attachment, error) {
|
||||
func (api *FakePMAPI) CreateAttachment(_ context.Context, attachment *pmapi.Attachment, data io.Reader, signature io.Reader) (*pmapi.Attachment, error) {
|
||||
if err := api.checkAndRecordCall(POST, "/mail/v4/attachments", nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -76,7 +77,3 @@ func (api *FakePMAPI) CreateAttachment(attachment *pmapi.Attachment, data io.Rea
|
||||
attachment.KeyPackets = base64.StdEncoding.EncodeToString(bytes)
|
||||
return attachment, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) DeleteAttachment(attID string) error {
|
||||
return api.checkAndRecordCall(DELETE, "/mail/v4/attachments/"+attID, nil)
|
||||
}
|
||||
|
||||
@ -18,76 +18,23 @@
|
||||
package fakeapi
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"context"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (api *FakePMAPI) SetAuths(auths chan<- *pmapi.Auth) {
|
||||
api.auths = auths
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) AuthInfo(username string) (*pmapi.AuthInfo, error) {
|
||||
if err := api.checkInternetAndRecordCall(POST, "/auth/info", &pmapi.AuthInfoReq{
|
||||
Username: username,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authInfo := &pmapi.AuthInfo{}
|
||||
user, ok := api.controller.usersByUsername[username]
|
||||
if !ok {
|
||||
// If username is wrong, API server will return empty but
|
||||
// positive response
|
||||
return authInfo, nil
|
||||
}
|
||||
authInfo.TwoFA = user.get2FAInfo()
|
||||
return authInfo, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) Auth(username, password string, authInfo *pmapi.AuthInfo) (*pmapi.Auth, error) {
|
||||
if err := api.checkInternetAndRecordCall(POST, "/auth", &pmapi.AuthReq{
|
||||
Username: username,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := api.controller.createSessionIfAuthorized(username, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
api.setUID(session.uid)
|
||||
|
||||
if err := api.setUser(username); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := api.controller.usersByUsername[username]
|
||||
auth := &pmapi.Auth{
|
||||
TwoFA: user.get2FAInfo(),
|
||||
RefreshToken: session.refreshToken,
|
||||
ExpiresIn: 86400, // seconds
|
||||
}
|
||||
auth.DANGEROUSLYSetUID(session.uid)
|
||||
|
||||
api.sendAuth(auth)
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) Auth2FA(twoFactorCode string, auth *pmapi.Auth) error {
|
||||
if err := api.checkInternetAndRecordCall(POST, "/auth/2fa", &pmapi.Auth2FAReq{
|
||||
TwoFactorCode: twoFactorCode,
|
||||
}); err != nil {
|
||||
func (api *FakePMAPI) Auth2FA(_ context.Context, req pmapi.Auth2FAReq) error {
|
||||
if err := api.checkAndRecordCall(POST, "/auth/2fa", req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if api.uid == "" {
|
||||
return pmapi.ErrInvalidToken
|
||||
return pmapi.ErrUnauthorized
|
||||
}
|
||||
|
||||
session, ok := api.controller.sessionsByUID[api.uid]
|
||||
if !ok {
|
||||
return pmapi.ErrInvalidToken
|
||||
return pmapi.ErrUnauthorized
|
||||
}
|
||||
|
||||
session.hasFullScope = true
|
||||
@ -95,92 +42,24 @@ func (api *FakePMAPI) Auth2FA(twoFactorCode string, auth *pmapi.Auth) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) AuthRefresh(token string) (*pmapi.Auth, error) {
|
||||
if api.lastToken == "" {
|
||||
api.lastToken = token
|
||||
}
|
||||
|
||||
split := strings.Split(token, ":")
|
||||
if len(split) != 2 {
|
||||
return nil, pmapi.ErrInvalidToken
|
||||
}
|
||||
|
||||
if err := api.checkInternetAndRecordCall(POST, "/auth/refresh", &pmapi.AuthRefreshReq{
|
||||
ResponseType: "token",
|
||||
GrantType: "refresh_token",
|
||||
UID: split[0],
|
||||
RefreshToken: split[1],
|
||||
RedirectURI: "https://protonmail.ch",
|
||||
State: "random_string",
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, ok := api.controller.sessionsByUID[split[0]]
|
||||
if !ok || session.refreshToken != split[1] {
|
||||
api.log.WithField("token", token).
|
||||
WithField("session", session).
|
||||
Warn("Refresh token failed")
|
||||
// The API server will respond normal error not 401 (check api)
|
||||
// i.e. should not use `sendAuth(nil)`
|
||||
api.setUID("")
|
||||
return nil, pmapi.ErrInvalidToken
|
||||
}
|
||||
api.setUID(split[0])
|
||||
|
||||
if err := api.setUser(session.username); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
api.controller.refreshTheTokensForSession(session)
|
||||
api.lastToken = split[0] + ":" + session.refreshToken
|
||||
|
||||
auth := &pmapi.Auth{
|
||||
RefreshToken: session.refreshToken,
|
||||
ExpiresIn: 86400,
|
||||
}
|
||||
auth.DANGEROUSLYSetUID(session.uid)
|
||||
|
||||
api.sendAuth(auth)
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) AuthSalt() (string, error) {
|
||||
if err := api.checkInternetAndRecordCall(GET, "/keys/salts", nil); err != nil {
|
||||
func (api *FakePMAPI) AuthSalt(_ context.Context) (string, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/keys/salts", nil); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) Logout() {
|
||||
api.controller.clientManager.LogoutClient(api.userID)
|
||||
func (api *FakePMAPI) AddAuthHandler(handler pmapi.AuthHandler) {
|
||||
api.authHandlers = append(api.authHandlers, handler)
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) IsConnected() bool {
|
||||
return api.uid != "" && api.lastToken != ""
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) DeleteAuth() error {
|
||||
func (api *FakePMAPI) AuthDelete(_ context.Context) error {
|
||||
if err := api.checkAndRecordCall(DELETE, "/auth", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
api.controller.deleteSession(api.uid)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) ClearData() {
|
||||
if api.userKeyRing != nil {
|
||||
api.userKeyRing.ClearPrivateParams()
|
||||
api.userKeyRing = nil
|
||||
}
|
||||
|
||||
for addrID, addr := range api.addrKeyRing {
|
||||
if addr != nil {
|
||||
addr.ClearPrivateParams()
|
||||
delete(api.addrKeyRing, addrID)
|
||||
}
|
||||
}
|
||||
|
||||
api.unsetUser()
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package fakeapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -29,7 +30,7 @@ func (api *FakePMAPI) DecryptAndVerifyCards(cards []pmapi.Card) ([]pmapi.Card, e
|
||||
return cards, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) GetContactEmailByEmail(email string, page int, pageSize int) ([]pmapi.ContactEmail, error) {
|
||||
func (api *FakePMAPI) GetContactEmailByEmail(_ context.Context, email string, page int, pageSize int) ([]pmapi.ContactEmail, error) {
|
||||
v := url.Values{}
|
||||
v.Set("Page", strconv.Itoa(page))
|
||||
if pageSize > 0 {
|
||||
@ -42,7 +43,7 @@ func (api *FakePMAPI) GetContactEmailByEmail(email string, page int, pageSize in
|
||||
return []pmapi.ContactEmail{}, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) GetContactByID(contactID string) (pmapi.Contact, error) {
|
||||
func (api *FakePMAPI) GetContactByID(_ context.Context, contactID string) (pmapi.Contact, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/contacts/"+contactID, nil); err != nil {
|
||||
return pmapi.Contact{}, err
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ type Controller struct {
|
||||
labelIDGenerator idGenerator
|
||||
messageIDGenerator idGenerator
|
||||
tokenGenerator idGenerator
|
||||
clientManager *pmapi.ClientManager
|
||||
clientManager pmapi.Manager
|
||||
|
||||
// State controlled by test.
|
||||
noInternetConnection bool
|
||||
@ -46,7 +46,7 @@ type Controller struct {
|
||||
log *logrus.Entry
|
||||
}
|
||||
|
||||
func NewController(cm *pmapi.ClientManager) *Controller {
|
||||
func NewController() (*Controller, pmapi.Manager) {
|
||||
controller := &Controller{
|
||||
lock: &sync.RWMutex{},
|
||||
fakeAPIs: []*FakePMAPI{},
|
||||
@ -54,7 +54,6 @@ func NewController(cm *pmapi.ClientManager) *Controller {
|
||||
labelIDGenerator: 100, // We cannot use system label IDs.
|
||||
messageIDGenerator: 0,
|
||||
tokenGenerator: 1000, // No specific reason; 1000 simply feels right.
|
||||
clientManager: cm,
|
||||
|
||||
noInternetConnection: false,
|
||||
usersByUsername: map[string]*fakeUser{},
|
||||
@ -67,11 +66,11 @@ func NewController(cm *pmapi.ClientManager) *Controller {
|
||||
log: logrus.WithField("pkg", "fakeapi-controller"),
|
||||
}
|
||||
|
||||
cm.SetClientConstructor(func(userID string) pmapi.Client {
|
||||
fakeAPI := New(controller, userID)
|
||||
controller.fakeAPIs = append(controller.fakeAPIs, fakeAPI)
|
||||
return fakeAPI
|
||||
})
|
||||
cm := &fakePMAPIManager{
|
||||
controller: controller,
|
||||
}
|
||||
|
||||
return controller
|
||||
controller.clientManager = cm
|
||||
|
||||
return controller, cm
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/nsf/jsondiff"
|
||||
)
|
||||
|
||||
@ -39,23 +40,31 @@ type fakeCall struct {
|
||||
request []byte
|
||||
}
|
||||
|
||||
func (ctl *Controller) recordCall(method method, path string, req interface{}) {
|
||||
func (ctl *Controller) recordCall(method method, path string, req interface{}) error {
|
||||
ctl.lock.Lock()
|
||||
defer ctl.lock.Unlock()
|
||||
|
||||
request := []byte{}
|
||||
var request []byte
|
||||
|
||||
if req != nil {
|
||||
var err error
|
||||
request, err = json.Marshal(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
if request, err = json.Marshal(req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ctl.calls = append(ctl.calls, &fakeCall{
|
||||
method: method,
|
||||
path: path,
|
||||
request: request,
|
||||
})
|
||||
|
||||
if ctl.noInternetConnection {
|
||||
return pmapi.ErrNoConnection
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctl *Controller) PrintCalls() {
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package fakeapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@ -51,7 +52,7 @@ func (ctl *Controller) ReorderAddresses(user *pmapi.User, addressIDs []string) e
|
||||
return errors.New("no such user")
|
||||
}
|
||||
|
||||
return api.ReorderAddresses(addressIDs)
|
||||
return api.ReorderAddresses(context.TODO(), addressIDs)
|
||||
}
|
||||
|
||||
func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password string, twoFAEnabled bool) error {
|
||||
|
||||
@ -19,16 +19,36 @@ package fakeapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
type fakeSession struct {
|
||||
username string
|
||||
uid, refreshToken string
|
||||
hasFullScope bool
|
||||
username string
|
||||
uid, acc, ref string
|
||||
hasFullScope bool
|
||||
}
|
||||
|
||||
var errWrongNameOrPassword = errors.New("Incorrect login credentials. Please try again") //nolint[stylecheck]
|
||||
|
||||
func (ctl *Controller) checkAccessToken(uid, acc string) bool {
|
||||
session, ok := ctl.sessionsByUID[uid]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return session.uid == uid && session.acc == acc
|
||||
}
|
||||
|
||||
func (ctl *Controller) checkScope(uid string) bool {
|
||||
session, ok := ctl.sessionsByUID[uid]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return session.hasFullScope
|
||||
}
|
||||
|
||||
func (ctl *Controller) createSessionIfAuthorized(username, password string) (*fakeSession, error) {
|
||||
// get user
|
||||
user, ok := ctl.usersByUsername[username]
|
||||
@ -40,16 +60,32 @@ func (ctl *Controller) createSessionIfAuthorized(username, password string) (*fa
|
||||
session := &fakeSession{
|
||||
username: username,
|
||||
uid: ctl.tokenGenerator.next("uid"),
|
||||
acc: ctl.tokenGenerator.next("acc"),
|
||||
ref: ctl.tokenGenerator.next("ref"),
|
||||
hasFullScope: !user.has2FA,
|
||||
}
|
||||
ctl.refreshTheTokensForSession(session)
|
||||
|
||||
ctl.sessionsByUID[session.uid] = session
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (ctl *Controller) refreshTheTokensForSession(session *fakeSession) {
|
||||
session.refreshToken = ctl.tokenGenerator.next("refresh")
|
||||
func (ctl *Controller) refreshSessionIfAuthorized(uid, ref string) (*fakeSession, error) {
|
||||
session, ok := ctl.sessionsByUID[uid]
|
||||
if !ok {
|
||||
return nil, pmapi.ErrUnauthorized
|
||||
}
|
||||
|
||||
if ref != session.ref {
|
||||
return nil, pmapi.ErrUnauthorized
|
||||
}
|
||||
|
||||
session.ref = ctl.tokenGenerator.next("ref")
|
||||
session.acc = ctl.tokenGenerator.next("acc")
|
||||
|
||||
ctl.sessionsByUID[session.uid] = session
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (ctl *Controller) deleteSession(uid string) {
|
||||
|
||||
@ -24,14 +24,3 @@ type fakeUser struct {
|
||||
password string
|
||||
has2FA bool
|
||||
}
|
||||
|
||||
func (fu *fakeUser) get2FAInfo() *pmapi.TwoFactorInfo {
|
||||
twoFAEnabled := 0
|
||||
if fu.has2FA {
|
||||
twoFAEnabled = 1
|
||||
}
|
||||
return &pmapi.TwoFactorInfo{
|
||||
Enabled: twoFAEnabled,
|
||||
TOTP: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,9 +17,13 @@
|
||||
|
||||
package fakeapi
|
||||
|
||||
import "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
import (
|
||||
"context"
|
||||
|
||||
func (api *FakePMAPI) CountMessages(addressID string) ([]*pmapi.MessagesCount, error) {
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (api *FakePMAPI) CountMessages(_ context.Context, addressID string) ([]*pmapi.MessagesCount, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/mail/v4/messages/count?AddressID="+addressID, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -43,10 +47,16 @@ func (api *FakePMAPI) getCounts(addressID string) []*pmapi.MessagesCount {
|
||||
counts.Unread++
|
||||
}
|
||||
} else {
|
||||
var unread int
|
||||
|
||||
if message.Unread == pmapi.True {
|
||||
unread = 1
|
||||
}
|
||||
|
||||
allCounts[labelID] = &pmapi.MessagesCount{
|
||||
LabelID: labelID,
|
||||
Total: 1,
|
||||
Unread: message.Unread,
|
||||
Unread: unread,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,10 +18,12 @@
|
||||
package fakeapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (api *FakePMAPI) GetEvent(eventID string) (*pmapi.Event, error) {
|
||||
func (api *FakePMAPI) GetEvent(_ context.Context, eventID string) (*pmapi.Event, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/events/"+eventID, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -34,28 +34,64 @@ type FakePMAPI struct {
|
||||
controller *Controller
|
||||
eventIDGenerator idGenerator
|
||||
|
||||
auths chan<- *pmapi.Auth
|
||||
user *pmapi.User
|
||||
userKeyRing *crypto.KeyRing
|
||||
addresses *pmapi.AddressList
|
||||
addrKeyRing map[string]*crypto.KeyRing
|
||||
labels []*pmapi.Label
|
||||
messages []*pmapi.Message
|
||||
events []*pmapi.Event
|
||||
authHandlers []pmapi.AuthHandler
|
||||
user *pmapi.User
|
||||
userKeyRing *crypto.KeyRing
|
||||
addresses *pmapi.AddressList
|
||||
addrKeyRing map[string]*crypto.KeyRing
|
||||
labels []*pmapi.Label
|
||||
messages []*pmapi.Message
|
||||
events []*pmapi.Event
|
||||
|
||||
// uid represents the API UID. It is the unique session ID.
|
||||
uid, lastToken string
|
||||
uid string
|
||||
acc string // FIXME(conman): Check this is correct!
|
||||
ref string // FIXME(conman): Check this is correct!
|
||||
|
||||
log *logrus.Entry
|
||||
}
|
||||
|
||||
func New(controller *Controller, userID string) *FakePMAPI {
|
||||
fakePMAPI := &FakePMAPI{
|
||||
func newFakePMAPI(controller *Controller, userID, uid, acc, ref string) *FakePMAPI {
|
||||
return &FakePMAPI{
|
||||
controller: controller,
|
||||
log: logrus.WithField("pkg", "fakeapi"),
|
||||
log: logrus.WithField("pkg", "fakeapi").WithField("uid", uid),
|
||||
uid: uid,
|
||||
acc: acc, // FIXME(conman): This should be checked!
|
||||
ref: ref, // FIXME(conman): This should be checked!
|
||||
userID: userID,
|
||||
addrKeyRing: make(map[string]*crypto.KeyRing),
|
||||
}
|
||||
}
|
||||
|
||||
func NewFakePMAPI(controller *Controller, username, userID, uid, acc, ref string) (*FakePMAPI, error) {
|
||||
user, ok := controller.usersByUsername[username]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("user %s does not exist", username)
|
||||
}
|
||||
|
||||
addresses, ok := controller.addressesByUsername[username]
|
||||
if !ok {
|
||||
addresses = &pmapi.AddressList{}
|
||||
}
|
||||
|
||||
labels, ok := controller.labelsByUsername[username]
|
||||
if !ok {
|
||||
labels = []*pmapi.Label{}
|
||||
}
|
||||
|
||||
messages, ok := controller.messagesByUsername[username]
|
||||
if !ok {
|
||||
messages = []*pmapi.Message{}
|
||||
}
|
||||
|
||||
fakePMAPI := newFakePMAPI(controller, userID, uid, acc, ref)
|
||||
|
||||
fakePMAPI.log = fakePMAPI.log.WithField("username", username)
|
||||
fakePMAPI.username = username
|
||||
fakePMAPI.user = user.user
|
||||
fakePMAPI.addresses = addresses
|
||||
fakePMAPI.labels = labels
|
||||
fakePMAPI.messages = messages
|
||||
|
||||
fakePMAPI.addEvent(&pmapi.Event{
|
||||
EventID: fakePMAPI.eventIDGenerator.last("event"),
|
||||
@ -63,7 +99,7 @@ func New(controller *Controller, userID string) *FakePMAPI {
|
||||
More: 0,
|
||||
})
|
||||
|
||||
return fakePMAPI
|
||||
return fakePMAPI, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) CloseConnections() {
|
||||
@ -74,54 +110,24 @@ func (api *FakePMAPI) checkAndRecordCall(method method, path string, request int
|
||||
api.controller.locker.Lock()
|
||||
defer api.controller.locker.Unlock()
|
||||
|
||||
if err := api.checkInternetAndRecordCall(method, path, request); err != nil {
|
||||
api.log.WithField(string(method), path).Trace("CALL")
|
||||
|
||||
if err := api.controller.recordCall(method, path, request); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Try re-auth
|
||||
if api.uid == "" && api.lastToken != "" {
|
||||
api.log.WithField("lastToken", api.lastToken).Warn("Handling unauthorized status")
|
||||
if _, err := api.AuthRefresh(api.lastToken); err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME(conman): This needs to match conman behaviour. Should try auth refresh somehow.
|
||||
if !api.controller.checkAccessToken(api.uid, api.acc) {
|
||||
return pmapi.ErrUnauthorized
|
||||
}
|
||||
|
||||
// Check client is authenticated. There is difference between
|
||||
// * invalid token
|
||||
// * and missing token
|
||||
// but API treats it the same
|
||||
if api.uid == "" {
|
||||
return pmapi.ErrInvalidToken
|
||||
}
|
||||
|
||||
// Any route (except Auth and AuthRefresh) can end with wrong
|
||||
// token and it should be translated into logout
|
||||
session, ok := api.controller.sessionsByUID[api.uid]
|
||||
if !ok {
|
||||
api.setUID("") // all consecutive requests will not send auth nil
|
||||
api.sendAuth(nil)
|
||||
return pmapi.ErrInvalidToken
|
||||
} else if !session.hasFullScope {
|
||||
// This is exact error string from the server (at least from documentation).
|
||||
if path != "/auth/2fa" && !api.controller.checkScope(api.uid) {
|
||||
return errors.New("Access token does not have sufficient scope") //nolint[stylecheck]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) checkInternetAndRecordCall(method method, path string, request interface{}) error {
|
||||
api.log.WithField(string(method), path).Trace("CALL")
|
||||
api.controller.recordCall(method, path, request)
|
||||
if api.controller.noInternetConnection {
|
||||
return pmapi.ErrAPINotReachable
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) sendAuth(auth *pmapi.Auth) {
|
||||
api.controller.clientManager.HandleAuth(pmapi.ClientAuth{UserID: api.userID, Auth: auth})
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) setUser(username string) error {
|
||||
api.username = username
|
||||
api.log = api.log.WithField("username", username)
|
||||
@ -153,14 +159,9 @@ func (api *FakePMAPI) setUser(username string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) setUID(uid string) {
|
||||
api.uid = uid
|
||||
api.log = api.log.WithField("uid", api.uid)
|
||||
api.log.Info("UID updated")
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) unsetUser() {
|
||||
api.setUID("")
|
||||
api.uid = ""
|
||||
api.acc = "" // FIXME(conman): This should be checked!
|
||||
api.user = nil
|
||||
api.labels = nil
|
||||
api.messages = nil
|
||||
|
||||
@ -17,7 +17,11 @@
|
||||
|
||||
package fakeapi
|
||||
|
||||
import "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
// publicKey is used from pmapi unit tests.
|
||||
// For now we need just some key, no need to have some specific one.
|
||||
@ -55,7 +59,7 @@ a+hqY4Jr/a7ui40S+7xYRHKL/7ZAS4/grWllhU3dbNrwSzrOKwrA/U0/9t73
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
`
|
||||
|
||||
func (api *FakePMAPI) GetPublicKeysForEmail(email string) (keys []pmapi.PublicKey, internal bool, err error) {
|
||||
func (api *FakePMAPI) GetPublicKeysForEmail(_ context.Context, email string) (keys []pmapi.PublicKey, internal bool, err error) {
|
||||
if err := api.checkAndRecordCall(GET, "/keys?Email="+email, nil); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package fakeapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
@ -32,14 +33,14 @@ func (api *FakePMAPI) isLabelFolder(labelID string) bool {
|
||||
return labelID == pmapi.InboxLabel || labelID == pmapi.ArchiveLabel || labelID == pmapi.SentLabel
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) ListLabels() ([]*pmapi.Label, error) {
|
||||
func (api *FakePMAPI) ListLabels(context.Context) ([]*pmapi.Label, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/labels/1", nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return api.labels, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) CreateLabel(label *pmapi.Label) (*pmapi.Label, error) {
|
||||
func (api *FakePMAPI) CreateLabel(_ context.Context, label *pmapi.Label) (*pmapi.Label, error) {
|
||||
if err := api.checkAndRecordCall(POST, "/labels", &pmapi.LabelReq{Label: label}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -61,7 +62,7 @@ func (api *FakePMAPI) CreateLabel(label *pmapi.Label) (*pmapi.Label, error) {
|
||||
return label, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) UpdateLabel(label *pmapi.Label) (*pmapi.Label, error) {
|
||||
func (api *FakePMAPI) UpdateLabel(_ context.Context, label *pmapi.Label) (*pmapi.Label, error) {
|
||||
if err := api.checkAndRecordCall(PUT, "/labels", &pmapi.LabelReq{Label: label}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -81,7 +82,7 @@ func (api *FakePMAPI) UpdateLabel(label *pmapi.Label) (*pmapi.Label, error) {
|
||||
return nil, fmt.Errorf("label %s does not exist", label.ID)
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) DeleteLabel(labelID string) error {
|
||||
func (api *FakePMAPI) DeleteLabel(_ context.Context, labelID string) error {
|
||||
if err := api.checkAndRecordCall(DELETE, "/labels/"+labelID, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
164
test/fakeapi/manager.go
Normal file
164
test/fakeapi/manager.go
Normal file
@ -0,0 +1,164 @@
|
||||
package fakeapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type fakePMAPIManager struct {
|
||||
controller *Controller
|
||||
}
|
||||
|
||||
func (m *fakePMAPIManager) NewClient(uid string, acc string, ref string, _ time.Time) pmapi.Client {
|
||||
session, ok := m.controller.sessionsByUID[uid]
|
||||
if !ok {
|
||||
return newFakePMAPI(m.controller, "", "", "", "")
|
||||
}
|
||||
|
||||
user, ok := m.controller.usersByUsername[session.username]
|
||||
if !ok {
|
||||
return newFakePMAPI(m.controller, "", "", "", "")
|
||||
}
|
||||
|
||||
client, err := NewFakePMAPI(m.controller, session.username, user.user.ID, session.uid, session.acc, session.ref)
|
||||
if err != nil {
|
||||
return newFakePMAPI(m.controller, "", "", "", "")
|
||||
}
|
||||
|
||||
m.controller.fakeAPIs = append(m.controller.fakeAPIs, client)
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func (m *fakePMAPIManager) NewClientWithRefresh(_ context.Context, uid, ref string) (pmapi.Client, *pmapi.Auth, error) {
|
||||
if err := m.controller.recordCall(POST, "/auth/refresh", &pmapi.AuthRefreshReq{
|
||||
UID: uid,
|
||||
RefreshToken: ref,
|
||||
ResponseType: "token",
|
||||
GrantType: "refresh_token",
|
||||
RedirectURI: "https://protonmail.ch",
|
||||
State: "random_string",
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
session, err := m.controller.refreshSessionIfAuthorized(uid, ref)
|
||||
if err != nil {
|
||||
return nil, nil, pmapi.ErrUnauthorized
|
||||
}
|
||||
|
||||
user, ok := m.controller.usersByUsername[session.username]
|
||||
if !ok {
|
||||
return nil, nil, errWrongNameOrPassword
|
||||
}
|
||||
|
||||
client, err := NewFakePMAPI(m.controller, session.username, user.user.ID, session.uid, session.acc, session.ref)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
m.controller.fakeAPIs = append(m.controller.fakeAPIs, client)
|
||||
|
||||
auth := &pmapi.Auth{
|
||||
UID: session.uid,
|
||||
AccessToken: session.acc,
|
||||
RefreshToken: session.ref,
|
||||
ExpiresIn: 86400, // seconds,
|
||||
}
|
||||
|
||||
if user.has2FA {
|
||||
auth.TwoFA = pmapi.TwoFAInfo{
|
||||
Enabled: pmapi.TOTPEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
return client, auth, nil
|
||||
}
|
||||
|
||||
func (m *fakePMAPIManager) NewClientWithLogin(_ context.Context, username string, password string) (pmapi.Client, *pmapi.Auth, error) {
|
||||
if err := m.controller.recordCall(POST, "/auth/info", &pmapi.GetAuthInfoReq{Username: username}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// If username is wrong, API server will return empty but positive response.
|
||||
// However, we will fail to create a client, so we return error here.
|
||||
user, ok := m.controller.usersByUsername[username]
|
||||
if !ok {
|
||||
return nil, nil, errWrongNameOrPassword
|
||||
}
|
||||
|
||||
if err := m.controller.recordCall(POST, "/auth", &pmapi.AuthReq{Username: username}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
session, err := m.controller.createSessionIfAuthorized(username, password)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
client, err := NewFakePMAPI(m.controller, username, user.user.ID, session.uid, session.acc, session.ref)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
m.controller.fakeAPIs = append(m.controller.fakeAPIs, client)
|
||||
|
||||
auth := &pmapi.Auth{
|
||||
UID: session.uid,
|
||||
AccessToken: session.acc,
|
||||
RefreshToken: session.ref,
|
||||
ExpiresIn: 86400, // seconds,
|
||||
}
|
||||
|
||||
if user.has2FA {
|
||||
auth.TwoFA = pmapi.TwoFAInfo{
|
||||
Enabled: pmapi.TOTPEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
return client, auth, nil
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) DownloadAndVerify(kr *crypto.KeyRing, url, sig string) ([]byte, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) ReportBug(context.Context, pmapi.ReportBugReq) error {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (m *fakePMAPIManager) SendSimpleMetric(_ context.Context, cat string, act string, lab string) error {
|
||||
v := url.Values{}
|
||||
|
||||
v.Set("Category", cat)
|
||||
v.Set("Action", act)
|
||||
v.Set("Label", lab)
|
||||
|
||||
return m.controller.recordCall(GET, "/metrics?"+v.Encode(), nil)
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) SetLogger(resty.Logger) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) SetTransport(http.RoundTripper) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) SetCookieJar(http.CookieJar) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) SetRetryCount(int) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (*fakePMAPIManager) AddConnectionObserver(pmapi.ConnectionObserver) {
|
||||
panic("TODO")
|
||||
}
|
||||
@ -19,6 +19,7 @@ package fakeapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@ -29,7 +30,7 @@ import (
|
||||
|
||||
var errWasNotUpdated = errors.New("message was not updated")
|
||||
|
||||
func (api *FakePMAPI) GetMessage(apiID string) (*pmapi.Message, error) {
|
||||
func (api *FakePMAPI) GetMessage(_ context.Context, apiID string) (*pmapi.Message, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/mail/v4/messages/"+apiID, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -49,7 +50,7 @@ func (api *FakePMAPI) GetMessage(apiID string) (*pmapi.Message, error) {
|
||||
// * ID
|
||||
// * Attachments
|
||||
// * AutoWildcard
|
||||
func (api *FakePMAPI) ListMessages(filter *pmapi.MessagesFilter) ([]*pmapi.Message, int, error) {
|
||||
func (api *FakePMAPI) ListMessages(_ context.Context, filter *pmapi.MessagesFilter) ([]*pmapi.Message, int, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/mail/v4/messages", filter); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@ -131,10 +132,14 @@ func isMessageMatchingFilter(filter *pmapi.MessagesFilter, message *pmapi.Messag
|
||||
return false
|
||||
}
|
||||
if filter.Unread != nil {
|
||||
wantUnread := 0
|
||||
var wantUnread pmapi.Boolean
|
||||
|
||||
if *filter.Unread {
|
||||
wantUnread = 1
|
||||
wantUnread = pmapi.True
|
||||
} else {
|
||||
wantUnread = pmapi.False
|
||||
}
|
||||
|
||||
if message.Unread != wantUnread {
|
||||
return false
|
||||
}
|
||||
@ -150,7 +155,7 @@ func copyFilteredMessage(message *pmapi.Message) *pmapi.Message {
|
||||
return filteredMessage
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) CreateDraft(message *pmapi.Message, parentID string, action int) (*pmapi.Message, error) {
|
||||
func (api *FakePMAPI) CreateDraft(ctx context.Context, message *pmapi.Message, parentID string, action int) (*pmapi.Message, error) {
|
||||
if err := api.checkAndRecordCall(POST, "/mail/v4/messages", &pmapi.DraftReq{
|
||||
Message: message,
|
||||
ParentID: parentID,
|
||||
@ -160,7 +165,7 @@ func (api *FakePMAPI) CreateDraft(message *pmapi.Message, parentID string, actio
|
||||
return nil, err
|
||||
}
|
||||
if parentID != "" {
|
||||
if _, err := api.GetMessage(parentID); err != nil {
|
||||
if _, err := api.GetMessage(ctx, parentID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@ -174,11 +179,11 @@ func (api *FakePMAPI) CreateDraft(message *pmapi.Message, parentID string, actio
|
||||
return message, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) SendMessage(messageID string, sendMessageRequest *pmapi.SendMessageReq) (sent, parent *pmapi.Message, err error) {
|
||||
func (api *FakePMAPI) SendMessage(ctx context.Context, messageID string, sendMessageRequest *pmapi.SendMessageReq) (sent, parent *pmapi.Message, err error) {
|
||||
if err := api.checkAndRecordCall(POST, "/mail/v4/messages/"+messageID, sendMessageRequest); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
message, err := api.GetMessage(messageID)
|
||||
message, err := api.GetMessage(ctx, messageID)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "draft does not exist")
|
||||
}
|
||||
@ -188,7 +193,7 @@ func (api *FakePMAPI) SendMessage(messageID string, sendMessageRequest *pmapi.Se
|
||||
return message, nil, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) Import(importMessageRequests []*pmapi.ImportMsgReq) ([]*pmapi.ImportMsgRes, error) {
|
||||
func (api *FakePMAPI) Import(_ context.Context, importMessageRequests pmapi.ImportMsgReqs) ([]*pmapi.ImportMsgRes, error) {
|
||||
if err := api.checkAndRecordCall(POST, "/import", importMessageRequests); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -211,7 +216,7 @@ func (api *FakePMAPI) Import(importMessageRequests []*pmapi.ImportMsgReq) ([]*pm
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) generateMessageFromImportRequest(msgReq *pmapi.ImportMsgReq) (*pmapi.Message, error) {
|
||||
m, _, _, _, err := message.Parse(bytes.NewReader(msgReq.Body)) // nolint[dogsled]
|
||||
m, _, _, _, err := message.Parse(bytes.NewReader(msgReq.Message)) // nolint[dogsled]
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -230,16 +235,16 @@ func (api *FakePMAPI) generateMessageFromImportRequest(msgReq *pmapi.ImportMsgRe
|
||||
return &pmapi.Message{
|
||||
ID: messageID,
|
||||
ExternalID: m.ExternalID,
|
||||
AddressID: msgReq.AddressID,
|
||||
AddressID: msgReq.Metadata.AddressID,
|
||||
Sender: m.Sender,
|
||||
ToList: m.ToList,
|
||||
Subject: m.Subject,
|
||||
Unread: msgReq.Unread,
|
||||
Unread: msgReq.Metadata.Unread,
|
||||
LabelIDs: api.generateLabelIDsFromImportRequest(msgReq),
|
||||
Body: m.Body,
|
||||
Header: m.Header,
|
||||
Flags: msgReq.Flags,
|
||||
Time: msgReq.Time,
|
||||
Flags: msgReq.Metadata.Flags,
|
||||
Time: msgReq.Metadata.Time,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -248,17 +253,17 @@ func (api *FakePMAPI) generateMessageFromImportRequest(msgReq *pmapi.ImportMsgRe
|
||||
func (api *FakePMAPI) generateLabelIDsFromImportRequest(msgReq *pmapi.ImportMsgReq) []string {
|
||||
isInSentOrInbox := false
|
||||
labelIDs := []string{pmapi.AllMailLabel}
|
||||
for _, labelID := range msgReq.LabelIDs {
|
||||
for _, labelID := range msgReq.Metadata.LabelIDs {
|
||||
if labelID == pmapi.InboxLabel || labelID == pmapi.SentLabel {
|
||||
isInSentOrInbox = true
|
||||
} else {
|
||||
labelIDs = append(labelIDs, labelID)
|
||||
}
|
||||
}
|
||||
if isInSentOrInbox && (msgReq.Flags&pmapi.FlagSent) != 0 {
|
||||
if isInSentOrInbox && (msgReq.Metadata.Flags&pmapi.FlagSent) != 0 {
|
||||
labelIDs = append(labelIDs, pmapi.SentLabel)
|
||||
}
|
||||
if isInSentOrInbox && (msgReq.Flags&pmapi.FlagReceived) != 0 {
|
||||
if isInSentOrInbox && (msgReq.Metadata.Flags&pmapi.FlagReceived) != 0 {
|
||||
labelIDs = append(labelIDs, pmapi.InboxLabel)
|
||||
}
|
||||
return labelIDs
|
||||
@ -287,7 +292,7 @@ func (api *FakePMAPI) addMessage(message *pmapi.Message) {
|
||||
api.addEventMessage(pmapi.EventCreate, message)
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) DeleteMessages(apiIDs []string) error {
|
||||
func (api *FakePMAPI) DeleteMessages(_ context.Context, apiIDs []string) error {
|
||||
err := api.deleteMessages(PUT, "/mail/v4/messages/delete", &pmapi.MessagesActionReq{
|
||||
IDs: apiIDs,
|
||||
}, func(message *pmapi.Message) bool {
|
||||
@ -304,7 +309,7 @@ func (api *FakePMAPI) DeleteMessages(apiIDs []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) EmptyFolder(labelID string, addressID string) error {
|
||||
func (api *FakePMAPI) EmptyFolder(_ context.Context, labelID string, addressID string) error {
|
||||
err := api.deleteMessages(DELETE, "/mail/v4/messages/empty?LabelID="+labelID+"&AddressID="+addressID, nil, func(message *pmapi.Message) bool {
|
||||
return hasItem(message.LabelIDs, labelID) && message.AddressID == addressID
|
||||
})
|
||||
@ -340,7 +345,7 @@ func (api *FakePMAPI) deleteMessages(method method, path string, request interfa
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) LabelMessages(apiIDs []string, labelID string) error {
|
||||
func (api *FakePMAPI) LabelMessages(_ context.Context, apiIDs []string, labelID string) error {
|
||||
return api.updateMessages(PUT, "/mail/v4/messages/label", &pmapi.LabelMessagesReq{
|
||||
IDs: apiIDs,
|
||||
LabelID: labelID,
|
||||
@ -366,7 +371,7 @@ func (api *FakePMAPI) LabelMessages(apiIDs []string, labelID string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) UnlabelMessages(apiIDs []string, labelID string) error {
|
||||
func (api *FakePMAPI) UnlabelMessages(_ context.Context, apiIDs []string, labelID string) error {
|
||||
return api.updateMessages(PUT, "/mail/v4/messages/unlabel", &pmapi.LabelMessagesReq{
|
||||
IDs: apiIDs,
|
||||
LabelID: labelID,
|
||||
@ -384,7 +389,7 @@ func (api *FakePMAPI) UnlabelMessages(apiIDs []string, labelID string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) MarkMessagesRead(apiIDs []string) error {
|
||||
func (api *FakePMAPI) MarkMessagesRead(_ context.Context, apiIDs []string) error {
|
||||
return api.updateMessages(PUT, "/mail/v4/messages/read", &pmapi.MessagesActionReq{
|
||||
IDs: apiIDs,
|
||||
}, apiIDs, func(message *pmapi.Message) error {
|
||||
@ -396,7 +401,7 @@ func (api *FakePMAPI) MarkMessagesRead(apiIDs []string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) MarkMessagesUnread(apiIDs []string) error {
|
||||
func (api *FakePMAPI) MarkMessagesUnread(_ context.Context, apiIDs []string) error {
|
||||
err := api.updateMessages(PUT, "/mail/v4/messages/unread", &pmapi.MessagesActionReq{
|
||||
IDs: apiIDs,
|
||||
}, apiIDs, func(message *pmapi.Message) error {
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
// 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 fakeapi
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (api *FakePMAPI) Report(report pmapi.ReportReq) error {
|
||||
return api.checkInternetAndRecordCall(POST, "/reports/bug", report)
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) SendSimpleMetric(category, action, label string) error {
|
||||
v := url.Values{}
|
||||
v.Set("Category", category)
|
||||
v.Set("Action", action)
|
||||
v.Set("Label", label)
|
||||
return api.checkInternetAndRecordCall(GET, "/metrics?"+v.Encode(), nil)
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) ReportSentryCrash(err error) error {
|
||||
return nil
|
||||
}
|
||||
@ -18,11 +18,13 @@
|
||||
package fakeapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (api *FakePMAPI) GetMailSettings() (pmapi.MailSettings, error) {
|
||||
func (api *FakePMAPI) GetMailSettings(context.Context) (pmapi.MailSettings, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/mail/v4/settings", nil); err != nil {
|
||||
return pmapi.MailSettings{}, err
|
||||
}
|
||||
@ -33,7 +35,7 @@ func (api *FakePMAPI) IsUnlocked() bool {
|
||||
return api.userKeyRing != nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) Unlock(passphrase []byte) (err error) {
|
||||
func (api *FakePMAPI) Unlock(_ context.Context, passphrase []byte) (err error) {
|
||||
if api.userKeyRing != nil {
|
||||
return
|
||||
}
|
||||
@ -63,19 +65,19 @@ func (api *FakePMAPI) Unlock(passphrase []byte) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) ReloadKeys(passphrase []byte) (err error) {
|
||||
if _, err = api.UpdateUser(); err != nil {
|
||||
func (api *FakePMAPI) ReloadKeys(ctx context.Context, passphrase []byte) (err error) {
|
||||
if _, err = api.UpdateUser(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return api.Unlock(passphrase)
|
||||
return api.Unlock(ctx, passphrase)
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) CurrentUser() (*pmapi.User, error) {
|
||||
return api.UpdateUser()
|
||||
func (api *FakePMAPI) CurrentUser(ctx context.Context) (*pmapi.User, error) {
|
||||
return api.UpdateUser(ctx)
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) UpdateUser() (*pmapi.User, error) {
|
||||
func (api *FakePMAPI) UpdateUser(context.Context) (*pmapi.User, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/users", nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -83,14 +85,14 @@ func (api *FakePMAPI) UpdateUser() (*pmapi.User, error) {
|
||||
return api.user, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) GetAddresses() (pmapi.AddressList, error) {
|
||||
func (api *FakePMAPI) GetAddresses(context.Context) (pmapi.AddressList, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/addresses", nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return *api.addresses, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) ReorderAddresses(addressIDs []string) error {
|
||||
func (api *FakePMAPI) ReorderAddresses(_ context.Context, addressIDs []string) error {
|
||||
if err := api.checkAndRecordCall(PUT, "/addresses/order", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ Feature: Start bridge
|
||||
Then "user" is connected
|
||||
And "user" has loaded store
|
||||
And "user" has running event loop
|
||||
And "user" has API auth
|
||||
|
||||
Scenario: Start with connected user, database file and no internet connection
|
||||
Given there is connected user "user"
|
||||
@ -16,7 +15,6 @@ Feature: Start bridge
|
||||
Then "user" is connected
|
||||
And "user" has loaded store
|
||||
And "user" has running event loop
|
||||
And "user" does not have API auth
|
||||
|
||||
@ignore
|
||||
Scenario: Start with connected user, no database file and internet connection
|
||||
@ -26,7 +24,7 @@ Feature: Start bridge
|
||||
Then "user" is connected
|
||||
And "user" has loaded store
|
||||
And "user" has running event loop
|
||||
And "user" has API auth
|
||||
And "user" is connected
|
||||
|
||||
@ignore
|
||||
Scenario: Start with connected user, no database file and no internet connection
|
||||
@ -35,7 +33,6 @@ Feature: Start bridge
|
||||
And there is no internet connection
|
||||
When bridge starts
|
||||
Then "user" is disconnected
|
||||
And "user" does not have API auth
|
||||
|
||||
Scenario: Start with disconnected user, database file and internet connection
|
||||
Given there is disconnected user "user"
|
||||
@ -44,7 +41,6 @@ Feature: Start bridge
|
||||
Then "user" is disconnected
|
||||
And "user" has loaded store
|
||||
And "user" does not have running event loop
|
||||
And "user" does not have API auth
|
||||
|
||||
Scenario: Start with disconnected user, database file and no internet connection
|
||||
Given there is disconnected user "user"
|
||||
@ -54,7 +50,6 @@ Feature: Start bridge
|
||||
Then "user" is disconnected
|
||||
And "user" has loaded store
|
||||
And "user" does not have running event loop
|
||||
And "user" does not have API auth
|
||||
|
||||
@ignore
|
||||
Scenario: Start with disconnected user, no database file and internet connection
|
||||
@ -64,7 +59,6 @@ Feature: Start bridge
|
||||
Then "user" is disconnected
|
||||
And "user" does not have loaded store
|
||||
And "user" does not have running event loop
|
||||
And "user" does not have API auth
|
||||
|
||||
@ignore
|
||||
Scenario: Start with disconnected user, no database file and no internet connection
|
||||
@ -75,4 +69,3 @@ Feature: Start bridge
|
||||
Then "user" is disconnected
|
||||
And "user" does not have loaded store
|
||||
And "user" does not have running event loop
|
||||
And "user" does not have API auth
|
||||
|
||||
@ -21,7 +21,7 @@ Feature: Login for the first time
|
||||
Scenario: Login without internet connection
|
||||
Given there is no internet connection
|
||||
When "user" logs in
|
||||
Then last response is "failed to login: cannot reach the server"
|
||||
Then last response is "failed to login: no internet connection"
|
||||
|
||||
@ignore-live
|
||||
Scenario: Login user with 2FA
|
||||
|
||||
@ -19,7 +19,7 @@ Feature: Login for the first time
|
||||
Scenario: Login without internet connection
|
||||
Given there is no internet connection
|
||||
When "user" logs in
|
||||
Then last response is "failed to login: cannot reach the server"
|
||||
Then last response is "failed to login: no internet connection"
|
||||
|
||||
@ignore-live
|
||||
Scenario: Login user with 2FA
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package liveapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
@ -43,7 +44,7 @@ func cleanup(client pmapi.Client, addresses *pmapi.AddressList) error {
|
||||
func cleanSystemFolders(client pmapi.Client) error {
|
||||
for _, labelID := range []string{pmapi.InboxLabel, pmapi.SentLabel, pmapi.ArchiveLabel, pmapi.AllMailLabel, pmapi.DraftLabel} {
|
||||
for {
|
||||
messages, total, err := client.ListMessages(&pmapi.MessagesFilter{
|
||||
messages, total, err := client.ListMessages(context.TODO(), &pmapi.MessagesFilter{
|
||||
PageSize: 150,
|
||||
LabelID: labelID,
|
||||
})
|
||||
@ -60,7 +61,7 @@ func cleanSystemFolders(client pmapi.Client) error {
|
||||
messageIDs = append(messageIDs, message.ID)
|
||||
}
|
||||
|
||||
if err := client.DeleteMessages(messageIDs); err != nil {
|
||||
if err := client.DeleteMessages(context.TODO(), messageIDs); err != nil {
|
||||
return errors.Wrap(err, "failed to delete messages")
|
||||
}
|
||||
|
||||
@ -73,7 +74,7 @@ func cleanSystemFolders(client pmapi.Client) error {
|
||||
}
|
||||
|
||||
func cleanCustomLables(client pmapi.Client) error {
|
||||
labels, err := client.ListLabels()
|
||||
labels, err := client.ListLabels(context.TODO())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to list labels")
|
||||
}
|
||||
@ -82,7 +83,7 @@ func cleanCustomLables(client pmapi.Client) error {
|
||||
if err := emptyFolder(client, label.ID); err != nil {
|
||||
return errors.Wrap(err, "failed to empty label")
|
||||
}
|
||||
if err := client.DeleteLabel(label.ID); err != nil {
|
||||
if err := client.DeleteLabel(context.TODO(), label.ID); err != nil {
|
||||
return errors.Wrap(err, "failed to delete label")
|
||||
}
|
||||
}
|
||||
@ -92,7 +93,7 @@ func cleanCustomLables(client pmapi.Client) error {
|
||||
|
||||
func cleanTrash(client pmapi.Client) error {
|
||||
for {
|
||||
_, total, err := client.ListMessages(&pmapi.MessagesFilter{
|
||||
_, total, err := client.ListMessages(context.TODO(), &pmapi.MessagesFilter{
|
||||
PageSize: 1,
|
||||
LabelID: pmapi.TrashLabel,
|
||||
})
|
||||
@ -114,12 +115,12 @@ func cleanTrash(client pmapi.Client) error {
|
||||
}
|
||||
|
||||
func emptyFolder(client pmapi.Client, labelID string) error {
|
||||
err := client.EmptyFolder(labelID, "")
|
||||
err := client.EmptyFolder(context.TODO(), labelID, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
_, total, err := client.ListMessages(&pmapi.MessagesFilter{
|
||||
_, total, err := client.ListMessages(context.TODO(), &pmapi.MessagesFilter{
|
||||
PageSize: 1,
|
||||
LabelID: labelID,
|
||||
})
|
||||
@ -141,5 +142,5 @@ func reorderAddresses(client pmapi.Client, addresses *pmapi.AddressList) error {
|
||||
addressIDs = append(addressIDs, address.ID)
|
||||
}
|
||||
|
||||
return client.ReorderAddresses(addressIDs)
|
||||
return client.ReorderAddresses(context.TODO(), addressIDs)
|
||||
}
|
||||
|
||||
@ -30,27 +30,29 @@ type Controller struct {
|
||||
calls []*fakeCall
|
||||
pmapiByUsername map[string]pmapi.Client
|
||||
messageIDsByUsername map[string][]string
|
||||
clientManager *pmapi.ClientManager
|
||||
clientManager pmapi.Manager
|
||||
|
||||
// State controlled by test.
|
||||
noInternetConnection bool
|
||||
}
|
||||
|
||||
func NewController(cm *pmapi.ClientManager) *Controller {
|
||||
func NewController() (*Controller, pmapi.Manager) {
|
||||
controller := &Controller{
|
||||
lock: &sync.RWMutex{},
|
||||
calls: []*fakeCall{},
|
||||
pmapiByUsername: map[string]pmapi.Client{},
|
||||
messageIDsByUsername: map[string][]string{},
|
||||
clientManager: cm,
|
||||
|
||||
noInternetConnection: false,
|
||||
}
|
||||
|
||||
cm.SetRoundTripper(&fakeTransport{
|
||||
// FIXME(conman): Set connect values here?
|
||||
cm := pmapi.New(pmapi.DefaultConfig)
|
||||
|
||||
cm.SetTransport(&fakeTransport{
|
||||
ctl: controller,
|
||||
transport: http.DefaultTransport,
|
||||
})
|
||||
|
||||
return controller
|
||||
return controller, cm
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package liveapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@ -44,7 +45,7 @@ func (ctl *Controller) AddUserLabel(username string, label *pmapi.Label) error {
|
||||
label.Exclusive = getLabelExclusive(label.Name)
|
||||
label.Name = getLabelNameWithoutPrefix(label.Name)
|
||||
label.Color = pmapi.LabelColors[0]
|
||||
if _, err := client.CreateLabel(label); err != nil {
|
||||
if _, err := client.CreateLabel(context.TODO(), label); err != nil {
|
||||
return errors.Wrap(err, "failed to create label")
|
||||
}
|
||||
return nil
|
||||
@ -72,7 +73,7 @@ func (ctl *Controller) getLabelID(username, labelName string) (string, error) {
|
||||
return "", fmt.Errorf("user %s does not exist", username)
|
||||
}
|
||||
|
||||
labels, err := client.ListLabels()
|
||||
labels, err := client.ListLabels(context.TODO())
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to list labels")
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package liveapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
messageUtils "github.com/ProtonMail/proton-bridge/pkg/message"
|
||||
@ -50,15 +51,17 @@ func (ctl *Controller) AddUserMessage(username string, message *pmapi.Message) (
|
||||
}
|
||||
|
||||
req := &pmapi.ImportMsgReq{
|
||||
AddressID: message.AddressID,
|
||||
Body: body,
|
||||
Unread: message.Unread,
|
||||
Time: message.Time,
|
||||
Flags: message.Flags,
|
||||
LabelIDs: message.LabelIDs,
|
||||
Metadata: &pmapi.ImportMetadata{
|
||||
AddressID: message.AddressID,
|
||||
Unread: message.Unread,
|
||||
Time: message.Time,
|
||||
Flags: message.Flags,
|
||||
LabelIDs: message.LabelIDs,
|
||||
},
|
||||
Message: body,
|
||||
}
|
||||
|
||||
results, err := client.Import([]*pmapi.ImportMsgReq{req})
|
||||
results, err := client.Import(context.TODO(), pmapi.ImportMsgReqs{req})
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to make an import")
|
||||
}
|
||||
@ -82,7 +85,7 @@ func (ctl *Controller) GetMessages(username, labelID string) ([]*pmapi.Message,
|
||||
|
||||
for {
|
||||
// ListMessages returns empty result, not error, asking for page out of range.
|
||||
pageMessages, _, err := client.ListMessages(&pmapi.MessagesFilter{
|
||||
pageMessages, _, err := client.ListMessages(context.TODO(), &pmapi.MessagesFilter{
|
||||
Page: page,
|
||||
PageSize: 150,
|
||||
LabelID: labelID,
|
||||
|
||||
@ -18,6 +18,8 @@
|
||||
package liveapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/pkg/errors"
|
||||
@ -28,19 +30,12 @@ func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, p
|
||||
return godog.ErrPending
|
||||
}
|
||||
|
||||
client := ctl.clientManager.GetClient(user.ID)
|
||||
|
||||
authInfo, err := client.AuthInfo(user.Name)
|
||||
client, _, err := ctl.clientManager.NewClientWithLogin(context.TODO(), user.Name, password)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get auth info")
|
||||
return errors.Wrap(err, "failed to create new client")
|
||||
}
|
||||
|
||||
_, err = client.Auth(user.Name, password, authInfo)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to auth user")
|
||||
}
|
||||
|
||||
salt, err := client.AuthSalt()
|
||||
salt, err := client.AuthSalt(context.TODO())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get salt")
|
||||
}
|
||||
@ -50,7 +45,7 @@ func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, p
|
||||
return errors.Wrap(err, "failed to hash mailbox password")
|
||||
}
|
||||
|
||||
if err := client.Unlock([]byte(mailboxPassword)); err != nil {
|
||||
if err := client.Unlock(context.TODO(), mailboxPassword); err != nil {
|
||||
return errors.Wrap(err, "failed to unlock user")
|
||||
}
|
||||
|
||||
@ -64,7 +59,5 @@ func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, p
|
||||
}
|
||||
|
||||
func (ctl *Controller) ReorderAddresses(user *pmapi.User, addressIDs []string) error {
|
||||
client := ctl.clientManager.GetClient(user.ID)
|
||||
|
||||
return client.ReorderAddresses(addressIDs)
|
||||
return ctl.pmapiByUsername[user.Name].ReorderAddresses(context.TODO(), addressIDs)
|
||||
}
|
||||
|
||||
@ -255,10 +255,14 @@ func messagesContainsMessageRow(account *accounts.TestAccount, allMessages []int
|
||||
matches = false
|
||||
}
|
||||
case "read":
|
||||
unread := 1
|
||||
var unread pmapi.Boolean
|
||||
|
||||
if cell.Value == "true" { //nolint[goconst]
|
||||
unread = 0
|
||||
unread = pmapi.False
|
||||
} else {
|
||||
unread = pmapi.True
|
||||
}
|
||||
|
||||
if message.Unread != unread {
|
||||
matches = false
|
||||
}
|
||||
|
||||
@ -173,10 +173,14 @@ func processMessageTableCell(column, cellValue, username string, message *pmapi.
|
||||
case "body":
|
||||
message.Body = cellValue
|
||||
case "read":
|
||||
unread := 1
|
||||
if cellValue == "true" {
|
||||
unread = 0
|
||||
var unread pmapi.Boolean
|
||||
|
||||
if cellValue == "true" { //nolint[goconst]
|
||||
unread = false
|
||||
} else {
|
||||
unread = true
|
||||
}
|
||||
|
||||
message.Unread = unread
|
||||
case "starred":
|
||||
if cellValue == "true" {
|
||||
|
||||
@ -34,8 +34,10 @@ func UsersChecksFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^"([^"]*)" does not have loaded store$`, userDoesNotHaveLoadedStore)
|
||||
s.Step(`^"([^"]*)" has running event loop$`, userHasRunningEventLoop)
|
||||
s.Step(`^"([^"]*)" does not have running event loop$`, userDoesNotHaveRunningEventLoop)
|
||||
s.Step(`^"([^"]*)" does not have API auth$`, isNotAuthorized)
|
||||
s.Step(`^"([^"]*)" has API auth$`, isAuthorized)
|
||||
|
||||
// FIXME(conman): Write tests for new "auth" system.
|
||||
// s.Step(`^"([^"]*)" does not have API auth$`, isNotAuthorized)
|
||||
// s.Step(`^"([^"]*)" has API auth$`, isAuthorized)
|
||||
}
|
||||
|
||||
func userHasAddressModeInMode(bddUserID, wantAddressMode string) error {
|
||||
@ -162,6 +164,7 @@ func userDoesNotHaveRunningEventLoop(bddUserID string) error {
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
/*
|
||||
func isAuthorized(bddUserID string) error {
|
||||
account := ctx.GetTestAccount(bddUserID)
|
||||
if account == nil {
|
||||
@ -187,3 +190,4 @@ func isNotAuthorized(bddUserID string) error {
|
||||
a.Eventually(ctx.GetTestingT(), func() bool { return !user.IsAuthorized() }, 5*time.Second, 10*time.Millisecond)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user