mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-17 23:56:56 +00:00
We build too many walls and not enough bridges
This commit is contained in:
47
test/fakeapi/attachments.go
Normal file
47
test/fakeapi/attachments.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2020 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 (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (api *FakePMAPI) GetAttachment(attachmentID string) (io.ReadCloser, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/attachments/"+attachmentID, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := strings.NewReader("data")
|
||||
return ioutil.NopCloser(data), nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) CreateAttachment(attachment *pmapi.Attachment, data io.Reader, signature io.Reader) (*pmapi.Attachment, error) {
|
||||
if err := api.checkAndRecordCall(POST, "/attachments", nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attachment.KeyPackets = base64.StdEncoding.EncodeToString(bytes)
|
||||
return attachment, nil
|
||||
}
|
||||
153
test/fakeapi/auth.go
Normal file
153
test/fakeapi/auth.go
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright (c) 2020 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 (
|
||||
"strings"
|
||||
|
||||
"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,
|
||||
}
|
||||
|
||||
api.sendAuth(auth)
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) Auth2FA(twoFactorCode string, auth *pmapi.Auth) (*pmapi.Auth2FA, error) {
|
||||
if err := api.checkInternetAndRecordCall(POST, "/auth/2fa", &pmapi.Auth2FAReq{
|
||||
TwoFactorCode: twoFactorCode,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if api.uid == "" {
|
||||
return nil, pmapi.ErrInvalidToken
|
||||
}
|
||||
|
||||
session, ok := api.controller.sessionsByUID[api.uid]
|
||||
if !ok {
|
||||
return nil, pmapi.ErrInvalidToken
|
||||
}
|
||||
|
||||
session.hasFullScope = true
|
||||
|
||||
return &pmapi.Auth2FA{
|
||||
Scope: "full",
|
||||
}, 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,
|
||||
}
|
||||
api.sendAuth(auth)
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) Logout() error {
|
||||
if err := api.checkAndRecordCall(DELETE, "/auth", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
// Logout will also emit change to auth channel
|
||||
api.sendAuth(nil)
|
||||
api.controller.deleteSession(api.uid)
|
||||
api.unsetUser()
|
||||
return nil
|
||||
}
|
||||
50
test/fakeapi/contacts.go
Normal file
50
test/fakeapi/contacts.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2020 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 (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (api *FakePMAPI) DecryptAndVerifyCards(cards []pmapi.Card) ([]pmapi.Card, error) {
|
||||
return cards, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) GetContactEmailByEmail(email string, page int, pageSize int) ([]pmapi.ContactEmail, error) {
|
||||
v := url.Values{}
|
||||
v.Set("Page", strconv.Itoa(page))
|
||||
if pageSize > 0 {
|
||||
v.Set("PageSize", strconv.Itoa(pageSize))
|
||||
}
|
||||
v.Set("Email", email)
|
||||
if err := api.checkAndRecordCall(GET, "/contacts/emails?"+v.Encode(), nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []pmapi.ContactEmail{}, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) GetContactByID(contactID string) (pmapi.Contact, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/contacts/"+contactID, nil); err != nil {
|
||||
return pmapi.Contact{}, err
|
||||
}
|
||||
return pmapi.Contact{}, fmt.Errorf("contact %s does not exist", contactID)
|
||||
}
|
||||
71
test/fakeapi/controller.go
Normal file
71
test/fakeapi/controller.go
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2020 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 (
|
||||
"sync"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
// Internal states.
|
||||
lock *sync.RWMutex
|
||||
fakeAPIs []*FakePMAPI
|
||||
calls []*fakeCall
|
||||
labelIDGenerator idGenerator
|
||||
messageIDGenerator idGenerator
|
||||
tokenGenerator idGenerator
|
||||
|
||||
// State controlled by test.
|
||||
noInternetConnection bool
|
||||
usersByUsername map[string]*fakeUser
|
||||
sessionsByUID map[string]*fakeSession
|
||||
addressesByUsername map[string]*pmapi.AddressList
|
||||
labelsByUsername map[string][]*pmapi.Label
|
||||
messagesByUsername map[string][]*pmapi.Message
|
||||
|
||||
log *logrus.Entry
|
||||
}
|
||||
|
||||
func NewController() *Controller {
|
||||
return &Controller{
|
||||
lock: &sync.RWMutex{},
|
||||
fakeAPIs: []*FakePMAPI{},
|
||||
calls: []*fakeCall{},
|
||||
labelIDGenerator: 100, // We cannot use system label IDs.
|
||||
messageIDGenerator: 0,
|
||||
tokenGenerator: 1000, // No specific reason; 1000 simply feels right.
|
||||
|
||||
noInternetConnection: false,
|
||||
usersByUsername: map[string]*fakeUser{},
|
||||
sessionsByUID: map[string]*fakeSession{},
|
||||
addressesByUsername: map[string]*pmapi.AddressList{},
|
||||
labelsByUsername: map[string][]*pmapi.Label{},
|
||||
messagesByUsername: map[string][]*pmapi.Message{},
|
||||
|
||||
log: logrus.WithField("pkg", "fakeapi-controller"),
|
||||
}
|
||||
}
|
||||
|
||||
func (cntrl *Controller) GetClient(userID string) *FakePMAPI {
|
||||
fakeAPI := New(cntrl)
|
||||
cntrl.fakeAPIs = append(cntrl.fakeAPIs, fakeAPI)
|
||||
return fakeAPI
|
||||
}
|
||||
93
test/fakeapi/controller_calls.go
Normal file
93
test/fakeapi/controller_calls.go
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2020 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/nsf/jsondiff"
|
||||
)
|
||||
|
||||
type method string
|
||||
|
||||
const (
|
||||
GET method = "GET"
|
||||
POST method = "POST"
|
||||
PUT method = "PUT"
|
||||
DELETE method = "DELETE"
|
||||
)
|
||||
|
||||
type fakeCall struct {
|
||||
method method
|
||||
path string
|
||||
request []byte
|
||||
}
|
||||
|
||||
func (cntrl *Controller) recordCall(method method, path string, req interface{}) {
|
||||
cntrl.lock.Lock()
|
||||
defer cntrl.lock.Unlock()
|
||||
|
||||
request := []byte{}
|
||||
if req != nil {
|
||||
var err error
|
||||
request, err = json.Marshal(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
cntrl.calls = append(cntrl.calls, &fakeCall{
|
||||
method: method,
|
||||
path: path,
|
||||
request: request,
|
||||
})
|
||||
}
|
||||
|
||||
func (cntrl *Controller) PrintCalls() {
|
||||
fmt.Println("API calls:")
|
||||
for idx, call := range cntrl.calls {
|
||||
fmt.Printf("%02d: [%s] %s\n", idx+1, call.method, call.path)
|
||||
if call.request != nil && string(call.request) != "null" {
|
||||
fmt.Printf("\t%s\n", call.request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cntrl *Controller) WasCalled(method, path string, expectedRequest []byte) bool {
|
||||
for _, call := range cntrl.calls {
|
||||
if string(call.method) != method && call.path != path {
|
||||
continue
|
||||
}
|
||||
diff, _ := jsondiff.Compare(call.request, expectedRequest, &jsondiff.Options{})
|
||||
isSuperset := diff == jsondiff.FullMatch || diff == jsondiff.SupersetMatch
|
||||
if isSuperset {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (cntrl *Controller) GetCalls(method, path string) [][]byte {
|
||||
requests := [][]byte{}
|
||||
for _, call := range cntrl.calls {
|
||||
if string(call.method) == method && call.path == path {
|
||||
requests = append(requests, call.request)
|
||||
}
|
||||
}
|
||||
return requests
|
||||
}
|
||||
142
test/fakeapi/controller_control.go
Normal file
142
test/fakeapi/controller_control.go
Normal file
@ -0,0 +1,142 @@
|
||||
// Copyright (c) 2020 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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
var systemLabelNameToID = map[string]string{ //nolint[gochecknoglobals]
|
||||
"INBOX": pmapi.InboxLabel,
|
||||
"Trash": pmapi.TrashLabel,
|
||||
"Spam": pmapi.SpamLabel,
|
||||
"All Mail": pmapi.AllMailLabel,
|
||||
"Archive": pmapi.ArchiveLabel,
|
||||
"Sent": pmapi.SentLabel,
|
||||
"Drafts": pmapi.DraftLabel,
|
||||
}
|
||||
|
||||
func (cntrl *Controller) TurnInternetConnectionOff() {
|
||||
cntrl.log.Warn("Turning OFF internet")
|
||||
cntrl.noInternetConnection = true
|
||||
}
|
||||
|
||||
func (cntrl *Controller) TurnInternetConnectionOn() {
|
||||
cntrl.log.Warn("Turning ON internet")
|
||||
cntrl.noInternetConnection = false
|
||||
}
|
||||
|
||||
func (cntrl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password string, twoFAEnabled bool) error {
|
||||
cntrl.usersByUsername[user.Name] = &fakeUser{
|
||||
user: user,
|
||||
password: password,
|
||||
has2FA: twoFAEnabled,
|
||||
}
|
||||
cntrl.addressesByUsername[user.Name] = addresses
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cntrl *Controller) AddUserLabel(username string, label *pmapi.Label) error {
|
||||
if _, ok := cntrl.labelsByUsername[username]; !ok {
|
||||
cntrl.labelsByUsername[username] = []*pmapi.Label{}
|
||||
}
|
||||
|
||||
labelName := getLabelNameWithoutPrefix(label.Name)
|
||||
for _, existingLabel := range cntrl.labelsByUsername[username] {
|
||||
if existingLabel.Name == labelName {
|
||||
return fmt.Errorf("folder or label %s already exists", label.Name)
|
||||
}
|
||||
}
|
||||
|
||||
label.Exclusive = getLabelExclusive(label.Name)
|
||||
prefix := "label"
|
||||
if label.Exclusive == 1 {
|
||||
prefix = "folder"
|
||||
}
|
||||
label.ID = cntrl.labelIDGenerator.next(prefix)
|
||||
label.Name = labelName
|
||||
cntrl.labelsByUsername[username] = append(cntrl.labelsByUsername[username], label)
|
||||
cntrl.resetUsers()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cntrl *Controller) GetLabelIDs(username string, labelNames []string) ([]string, error) {
|
||||
labelIDs := []string{}
|
||||
for _, labelName := range labelNames {
|
||||
labelID, err := cntrl.getLabelID(username, labelName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
labelIDs = append(labelIDs, labelID)
|
||||
}
|
||||
return labelIDs, nil
|
||||
}
|
||||
|
||||
func (cntrl *Controller) getLabelID(username, labelName string) (string, error) {
|
||||
if labelID, ok := systemLabelNameToID[labelName]; ok {
|
||||
return labelID, nil
|
||||
}
|
||||
labelName = getLabelNameWithoutPrefix(labelName)
|
||||
for _, label := range cntrl.labelsByUsername[username] {
|
||||
if label.Name == labelName {
|
||||
return label.ID, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("label %s:%s does not exist", username, labelName)
|
||||
}
|
||||
|
||||
func getLabelNameWithoutPrefix(name string) string {
|
||||
if strings.HasPrefix(name, "Folders/") {
|
||||
return strings.TrimPrefix(name, "Folders/")
|
||||
}
|
||||
if strings.HasPrefix(name, "Labels/") {
|
||||
return strings.TrimPrefix(name, "Labels/")
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func getLabelExclusive(name string) int {
|
||||
if strings.HasPrefix(name, "Folders/") {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (cntrl *Controller) AddUserMessage(username string, message *pmapi.Message) error {
|
||||
if _, ok := cntrl.messagesByUsername[username]; !ok {
|
||||
cntrl.messagesByUsername[username] = []*pmapi.Message{}
|
||||
}
|
||||
message.ID = cntrl.messageIDGenerator.next("")
|
||||
message.LabelIDs = append(message.LabelIDs, pmapi.AllMailLabel)
|
||||
cntrl.messagesByUsername[username] = append(cntrl.messagesByUsername[username], message)
|
||||
cntrl.resetUsers()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cntrl *Controller) resetUsers() {
|
||||
for _, fakeAPI := range cntrl.fakeAPIs {
|
||||
_ = fakeAPI.setUser(fakeAPI.username)
|
||||
}
|
||||
}
|
||||
|
||||
func (cntrl *Controller) GetMessageID(username, messageIndex string) string {
|
||||
return messageIndex
|
||||
}
|
||||
57
test/fakeapi/controller_session.go
Normal file
57
test/fakeapi/controller_session.go
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2020 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 (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type fakeSession struct {
|
||||
username string
|
||||
uid, refreshToken string
|
||||
hasFullScope bool
|
||||
}
|
||||
|
||||
var errWrongNameOrPassword = errors.New("Incorrect login credentials. Please try again") //nolint[stylecheck]
|
||||
|
||||
func (cntrl *Controller) createSessionIfAuthorized(username, password string) (*fakeSession, error) {
|
||||
// get user
|
||||
user, ok := cntrl.usersByUsername[username]
|
||||
if !ok || user.password != password {
|
||||
return nil, errWrongNameOrPassword
|
||||
}
|
||||
|
||||
// create session
|
||||
session := &fakeSession{
|
||||
username: username,
|
||||
uid: cntrl.tokenGenerator.next("uid"),
|
||||
hasFullScope: !user.has2FA,
|
||||
}
|
||||
cntrl.refreshTheTokensForSession(session)
|
||||
|
||||
cntrl.sessionsByUID[session.uid] = session
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (cntrl *Controller) refreshTheTokensForSession(session *fakeSession) {
|
||||
session.refreshToken = cntrl.tokenGenerator.next("refresh")
|
||||
}
|
||||
|
||||
func (cntrl *Controller) deleteSession(uid string) {
|
||||
delete(cntrl.sessionsByUID, uid)
|
||||
}
|
||||
37
test/fakeapi/controller_user.go
Normal file
37
test/fakeapi/controller_user.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2020 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 "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
|
||||
type fakeUser struct {
|
||||
user *pmapi.User
|
||||
password string
|
||||
has2FA bool
|
||||
}
|
||||
|
||||
func (fu *fakeUser) get2FAInfo() *pmapi.TwoFactorInfo {
|
||||
twoFAEnabled := 0
|
||||
if fu.has2FA {
|
||||
twoFAEnabled = 1
|
||||
}
|
||||
return &pmapi.TwoFactorInfo{
|
||||
Enabled: twoFAEnabled,
|
||||
TOTP: 0,
|
||||
}
|
||||
}
|
||||
60
test/fakeapi/counts.go
Normal file
60
test/fakeapi/counts.go
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2020 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 "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
|
||||
func (api *FakePMAPI) CountMessages(addressID string) ([]*pmapi.MessagesCount, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/messages/count?AddressID="+addressID, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return api.getCounts(addressID), nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) getAllCounts() []*pmapi.MessagesCount {
|
||||
return api.getCounts("")
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) getCounts(addressID string) []*pmapi.MessagesCount {
|
||||
allCounts := map[string]*pmapi.MessagesCount{}
|
||||
for _, message := range api.messages {
|
||||
if addressID != "" && message.AddressID != addressID {
|
||||
continue
|
||||
}
|
||||
for _, labelID := range message.LabelIDs {
|
||||
if counts, ok := allCounts[labelID]; ok {
|
||||
counts.Total++
|
||||
if message.Unread == 1 {
|
||||
counts.Unread++
|
||||
}
|
||||
} else {
|
||||
allCounts[labelID] = &pmapi.MessagesCount{
|
||||
LabelID: labelID,
|
||||
Total: 1,
|
||||
Unread: message.Unread,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res := []*pmapi.MessagesCount{}
|
||||
for _, counts := range allCounts {
|
||||
res = append(res, counts)
|
||||
}
|
||||
return res
|
||||
}
|
||||
105
test/fakeapi/events.go
Normal file
105
test/fakeapi/events.go
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2020 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 (
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (api *FakePMAPI) GetEvent(eventID string) (*pmapi.Event, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/events/"+eventID, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Request for empty ID returns the latest event.
|
||||
if eventID == "" {
|
||||
return api.events[len(api.events)-1], nil
|
||||
}
|
||||
// Otherwise it tries to find specific ID and return all next events merged into one.
|
||||
var foundEvent *pmapi.Event
|
||||
mergedEvent := &pmapi.Event{}
|
||||
for _, event := range api.events {
|
||||
if event.EventID == eventID {
|
||||
foundEvent = event
|
||||
continue
|
||||
}
|
||||
if foundEvent != nil {
|
||||
mergedEvent.EventID = event.EventID
|
||||
mergedEvent.Refresh |= event.Refresh
|
||||
mergedEvent.Messages = append(mergedEvent.Messages, event.Messages...)
|
||||
mergedEvent.MessageCounts = append(mergedEvent.MessageCounts, event.MessageCounts...)
|
||||
mergedEvent.Labels = append(mergedEvent.Labels, event.Labels...)
|
||||
mergedEvent.Notices = append(mergedEvent.Notices, event.Notices...)
|
||||
mergedEvent.User = event.User
|
||||
}
|
||||
}
|
||||
// If there isn't next event, return the same one.
|
||||
if mergedEvent.EventID == "" {
|
||||
return foundEvent, nil
|
||||
}
|
||||
return mergedEvent, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) addEventLabel(action pmapi.EventAction, label *pmapi.Label) {
|
||||
api.addEvent(&pmapi.Event{
|
||||
EventID: api.eventIDGenerator.next("event"),
|
||||
Labels: []*pmapi.EventLabel{{
|
||||
EventItem: pmapi.EventItem{
|
||||
ID: label.ID,
|
||||
Action: action,
|
||||
},
|
||||
Label: label,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) addEventMessage(action pmapi.EventAction, message *pmapi.Message) {
|
||||
created := message
|
||||
updated := &pmapi.EventMessageUpdated{
|
||||
ID: message.ID,
|
||||
Subject: &message.Subject,
|
||||
Unread: &message.Unread,
|
||||
Flags: &message.Flags,
|
||||
Sender: message.Sender,
|
||||
ToList: &message.ToList,
|
||||
CCList: &message.CCList,
|
||||
BCCList: &message.BCCList,
|
||||
Time: message.Time,
|
||||
LabelIDs: message.LabelIDs,
|
||||
}
|
||||
if action == pmapi.EventCreate {
|
||||
updated = nil
|
||||
} else {
|
||||
created = nil
|
||||
}
|
||||
api.addEvent(&pmapi.Event{
|
||||
EventID: api.eventIDGenerator.next("event"),
|
||||
Messages: []*pmapi.EventMessage{{
|
||||
EventItem: pmapi.EventItem{
|
||||
ID: message.ID,
|
||||
Action: action,
|
||||
},
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
}},
|
||||
MessageCounts: api.getAllCounts(),
|
||||
})
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) addEvent(event *pmapi.Event) {
|
||||
api.events = append(api.events, event)
|
||||
}
|
||||
158
test/fakeapi/fakeapi.go
Normal file
158
test/fakeapi/fakeapi.go
Normal file
@ -0,0 +1,158 @@
|
||||
// Copyright (c) 2020 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var errBadRequest = errors.New("NOT OK: 400 Bad Request")
|
||||
|
||||
type FakePMAPI struct {
|
||||
username string
|
||||
controller *Controller
|
||||
eventIDGenerator idGenerator
|
||||
|
||||
auths chan<- *pmapi.Auth
|
||||
user *pmapi.User
|
||||
addresses *pmapi.AddressList
|
||||
labels []*pmapi.Label
|
||||
messages []*pmapi.Message
|
||||
events []*pmapi.Event
|
||||
|
||||
// uid represents the API UID. It is the unique session ID.
|
||||
uid, lastToken string
|
||||
|
||||
log *logrus.Entry
|
||||
}
|
||||
|
||||
func New(controller *Controller) *FakePMAPI {
|
||||
fakePMAPI := &FakePMAPI{
|
||||
controller: controller,
|
||||
log: logrus.WithField("pkg", "fakeapi"),
|
||||
}
|
||||
fakePMAPI.addEvent(&pmapi.Event{
|
||||
EventID: fakePMAPI.eventIDGenerator.last("event"),
|
||||
Refresh: 0,
|
||||
More: 0,
|
||||
})
|
||||
return fakePMAPI
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) checkAndRecordCall(method method, path string, request interface{}) error {
|
||||
if err := api.checkInternetAndRecordCall(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
|
||||
}
|
||||
}
|
||||
|
||||
// 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).
|
||||
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) {
|
||||
if auth != nil {
|
||||
auth.DANGEROUSLYSetUID(api.uid)
|
||||
}
|
||||
if api.auths != nil {
|
||||
api.auths <- auth
|
||||
}
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) setUser(username string) error {
|
||||
api.username = username
|
||||
api.log = api.log.WithField("username", username)
|
||||
|
||||
user, ok := api.controller.usersByUsername[username]
|
||||
if !ok {
|
||||
return fmt.Errorf("user %s does not exist", username)
|
||||
}
|
||||
api.user = user.user
|
||||
|
||||
addresses, ok := api.controller.addressesByUsername[username]
|
||||
if !ok {
|
||||
addresses = &pmapi.AddressList{}
|
||||
}
|
||||
api.addresses = addresses
|
||||
|
||||
labels, ok := api.controller.labelsByUsername[username]
|
||||
if !ok {
|
||||
labels = []*pmapi.Label{}
|
||||
}
|
||||
api.labels = labels
|
||||
|
||||
messages, ok := api.controller.messagesByUsername[username]
|
||||
if !ok {
|
||||
messages = []*pmapi.Message{}
|
||||
}
|
||||
api.messages = messages
|
||||
|
||||
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.user = nil
|
||||
api.labels = nil
|
||||
api.messages = nil
|
||||
api.events = nil
|
||||
}
|
||||
31
test/fakeapi/idgenerator.go
Normal file
31
test/fakeapi/idgenerator.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2020 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 "fmt"
|
||||
|
||||
type idGenerator int
|
||||
|
||||
func (g *idGenerator) last(prefix string) string {
|
||||
return fmt.Sprintf("%s%d", prefix, *g)
|
||||
}
|
||||
|
||||
func (g *idGenerator) next(prefix string) string {
|
||||
(*g)++
|
||||
return fmt.Sprintf("%s%d", prefix, *g)
|
||||
}
|
||||
65
test/fakeapi/keys.go
Normal file
65
test/fakeapi/keys.go
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2020 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 "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.
|
||||
const publicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: OpenPGP.js v0.7.1
|
||||
Comment: http://openpgpjs.org
|
||||
|
||||
xsBNBFRJbc0BCAC0mMLZPDBbtSCWvxwmOfXfJkE2+ssM3ux21LhD/bPiWefE
|
||||
WSHlCjJ8PqPHy7snSiUuxuj3f9AvXPvg+mjGLBwu1/QsnSP24sl3qD2onl39
|
||||
vPiLJXUqZs20ZRgnvX70gjkgEzMFBxINiy2MTIG+4RU8QA7y8KzWev0btqKi
|
||||
MeVa+GLEHhgZ2KPOn4Jv1q4bI9hV0C9NUe2tTXS6/Vv3vbCY7lRR0kbJ65T5
|
||||
c8CmpqJuASIJNrSXM/Q3NnnsY4kBYH0s5d2FgbASQvzrjuC2rngUg0EoPsrb
|
||||
DEVRA2/BCJonw7aASiNCrSP92lkZdtYlax/pcoE/mQ4WSwySFmcFT7yFABEB
|
||||
AAHNBlVzZXJJRMLAcgQQAQgAJgUCVEltzwYLCQgHAwIJED62JZ7fId8kBBUI
|
||||
AgoDFgIBAhsDAh4BAAD0nQf9EtH9TC0JqSs8q194Zo244jjlJFM3EzxOSULq
|
||||
0zbywlLORfyoo/O8jU/HIuGz+LT98JDtnltTqfjWgu6pS3ZL2/L4AGUKEoB7
|
||||
OI6oIdRwzMc61sqI+Qpbzxo7rzufH4CiXZc6cxORUgL550xSCcqnq0q1mds7
|
||||
h5roKDzxMW6WLiEsc1dN8IQKzC7Ec5wA7U4oNGsJ3TyI8jkIs0IhXrRCd26K
|
||||
0TW8Xp6GCsfblWXosR13y89WVNgC+xrrJKTZEisc0tRlneIgjcwEUvwfIg2n
|
||||
9cDUFA/5BsfzTW5IurxqDEziIVP0L44PXjtJrBQaGMPlEbtP5i2oi3OADVX2
|
||||
XbvsRc7ATQRUSW3PAQgAkPnu5fps5zhOB/e618v/iF3KiogxUeRhA68TbvA+
|
||||
xnFfTxCx2Vo14aOL0CnaJ8gO5yRSqfomL2O1kMq07N1MGbqucbmc+aSfoElc
|
||||
+Gd5xBE/w3RcEhKcAaYTi35vG22zlZup4x3ElioyIarOssFEkQgNNyDf5AXZ
|
||||
jdHLA6qVxeqAb/Ff74+y9HUmLPSsRU9NwFzvK3Jv8C/ubHVLzTYdFgYkc4W1
|
||||
Uug9Ou08K+/4NEMrwnPFBbZdJAuUjQz2zW2ZiEKiBggiorH2o5N3mYUnWEmU
|
||||
vqL3EOS8TbWo8UBIW3DDm2JiZR8VrEgvBtc9mVDUj/x+5pR07Fy1D6DjRmAc
|
||||
9wARAQABwsBfBBgBCAATBQJUSW3SCRA+tiWe3yHfJAIbDAAA/iwH/ik9RKZM
|
||||
B9Ir0x5mGpKPuqhugwrc3d04m1sOdXJm2NtD4ddzSEvzHwaPNvEvUl5v7FVM
|
||||
zf6+6mYGWHyNP4+e7RtwYLlRpud6smuGyDSsotUYyumiqP6680ZIeWVQ+a1T
|
||||
ThNs878mAJy1FhvQFdTmA8XIC616hDFpamQKPlpoO1a0wZnQhrPwT77HDYEE
|
||||
a+hqY4Jr/a7ui40S+7xYRHKL/7ZAS4/grWllhU3dbNrwSzrOKwrA/U0/9t73
|
||||
8Ap6JL71YymDeaL4sutcoaahda1pTrMWePtrCltz6uySwbZs7GXoEzjX3EAH
|
||||
+6qhkUJtzMaE3YEFEoQMGzcDTUEfXCJ3zJw=
|
||||
=yT9U
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
`
|
||||
|
||||
func (api *FakePMAPI) GetPublicKeysForEmail(email string) (keys []pmapi.PublicKey, internal bool, err error) {
|
||||
if err := api.checkAndRecordCall(GET, "/keys?Email="+email, nil); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return []pmapi.PublicKey{{
|
||||
PublicKey: publicKey,
|
||||
}}, true, nil
|
||||
}
|
||||
90
test/fakeapi/labels.go
Normal file
90
test/fakeapi/labels.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2020 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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (api *FakePMAPI) isLabelFolder(labelID string) bool {
|
||||
for _, label := range api.labels {
|
||||
if label.ID == labelID {
|
||||
return label.Exclusive == 1
|
||||
}
|
||||
}
|
||||
return labelID == pmapi.InboxLabel || labelID == pmapi.ArchiveLabel || labelID == pmapi.SentLabel
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) ListLabels() ([]*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) {
|
||||
if err := api.checkAndRecordCall(POST, "/labels", &pmapi.LabelReq{Label: label}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, existingLabel := range api.labels {
|
||||
if existingLabel.Name == label.Name {
|
||||
return nil, fmt.Errorf("folder or label %s already exists", label.Name)
|
||||
}
|
||||
}
|
||||
prefix := "label"
|
||||
if label.Exclusive == 1 {
|
||||
prefix = "folder"
|
||||
}
|
||||
label.ID = api.controller.labelIDGenerator.next(prefix)
|
||||
api.labels = append(api.labels, label)
|
||||
api.addEventLabel(pmapi.EventCreate, label)
|
||||
return label, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) UpdateLabel(label *pmapi.Label) (*pmapi.Label, error) {
|
||||
if err := api.checkAndRecordCall(PUT, "/labels", &pmapi.LabelReq{Label: label}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for idx, existingLabel := range api.labels {
|
||||
if existingLabel.ID == label.ID {
|
||||
// Request doesn't have to include all properties and these have to stay the same.
|
||||
label.Type = existingLabel.Type
|
||||
label.Exclusive = existingLabel.Exclusive
|
||||
api.labels[idx] = label
|
||||
api.addEventLabel(pmapi.EventUpdate, label)
|
||||
return label, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("label %s does not exist", label.ID)
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) DeleteLabel(labelID string) error {
|
||||
if err := api.checkAndRecordCall(DELETE, "/labels/"+labelID, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
for idx, existingLabel := range api.labels {
|
||||
if existingLabel.ID == labelID {
|
||||
api.labels = append(api.labels[:idx], api.labels[idx+1:]...)
|
||||
api.addEventLabel(pmapi.EventDelete, existingLabel)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("label %s does not exist", labelID)
|
||||
}
|
||||
374
test/fakeapi/messages.go
Normal file
374
test/fakeapi/messages.go
Normal file
@ -0,0 +1,374 @@
|
||||
// Copyright (c) 2020 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var errWasNotUpdated = errors.New("message was not updated")
|
||||
|
||||
func (api *FakePMAPI) GetMessage(apiID string) (*pmapi.Message, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/messages/"+apiID, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, message := range api.messages {
|
||||
if message.ID == apiID {
|
||||
return message, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("message %s not found", apiID)
|
||||
}
|
||||
|
||||
// ListMessages does not implement following filters:
|
||||
// * Sort (it sorts by ID only), but Desc works
|
||||
// * Keyword
|
||||
// * To
|
||||
// * Subject
|
||||
// * ID
|
||||
// * Attachments
|
||||
// * AutoWildcard
|
||||
func (api *FakePMAPI) ListMessages(filter *pmapi.MessagesFilter) ([]*pmapi.Message, int, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/messages", filter); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
pageSize := filter.PageSize
|
||||
if pageSize > 150 {
|
||||
pageSize = 150
|
||||
}
|
||||
|
||||
messages := []*pmapi.Message{}
|
||||
messageCount := 0
|
||||
|
||||
skipByIDBegin := true
|
||||
skipByIDEnd := false
|
||||
skipByPaging := pageSize * filter.Page
|
||||
|
||||
for idx := 0; idx < len(api.messages); idx++ {
|
||||
var message *pmapi.Message
|
||||
if !*filter.Desc {
|
||||
message = api.messages[idx]
|
||||
if filter.BeginID == "" || message.ID == filter.BeginID {
|
||||
skipByIDBegin = false
|
||||
}
|
||||
} else {
|
||||
message = api.messages[len(api.messages)-1-idx]
|
||||
if filter.EndID == "" || message.ID == filter.EndID {
|
||||
skipByIDBegin = false
|
||||
}
|
||||
}
|
||||
if skipByIDBegin || skipByIDEnd {
|
||||
continue
|
||||
}
|
||||
if !*filter.Desc {
|
||||
if message.ID == filter.EndID {
|
||||
skipByIDEnd = true
|
||||
}
|
||||
} else {
|
||||
if message.ID == filter.BeginID {
|
||||
skipByIDEnd = true
|
||||
}
|
||||
}
|
||||
if !isMessageMatchingFilter(filter, message) {
|
||||
continue
|
||||
}
|
||||
messageCount++
|
||||
|
||||
if skipByPaging > 0 {
|
||||
skipByPaging--
|
||||
continue
|
||||
}
|
||||
if len(messages) == pageSize || (filter.Limit != 0 && len(messages) == filter.Limit) {
|
||||
continue
|
||||
}
|
||||
messages = append(messages, copyFilteredMessage(message))
|
||||
}
|
||||
|
||||
return messages, messageCount, nil
|
||||
}
|
||||
|
||||
func isMessageMatchingFilter(filter *pmapi.MessagesFilter, message *pmapi.Message) bool {
|
||||
if filter.ExternalID != "" && filter.ExternalID != message.ExternalID {
|
||||
return false
|
||||
}
|
||||
if filter.ConversationID != "" && filter.ConversationID != message.ConversationID {
|
||||
return false
|
||||
}
|
||||
if filter.AddressID != "" && filter.AddressID != message.AddressID {
|
||||
return false
|
||||
}
|
||||
if filter.From != "" && filter.From != message.Sender.Address {
|
||||
return false
|
||||
}
|
||||
if filter.LabelID != "" && !hasItem(message.LabelIDs, filter.LabelID) {
|
||||
return false
|
||||
}
|
||||
if filter.Begin != 0 && filter.Begin > message.Time {
|
||||
return false
|
||||
}
|
||||
if filter.End != 0 && filter.End < message.Time {
|
||||
return false
|
||||
}
|
||||
if filter.Unread != nil {
|
||||
wantUnread := 0
|
||||
if *filter.Unread {
|
||||
wantUnread = 1
|
||||
}
|
||||
if message.Unread != wantUnread {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func copyFilteredMessage(message *pmapi.Message) *pmapi.Message {
|
||||
filteredMessage := &pmapi.Message{}
|
||||
*filteredMessage = *message
|
||||
filteredMessage.Body = ""
|
||||
filteredMessage.Header = nil
|
||||
return filteredMessage
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) CreateDraft(message *pmapi.Message, parentID string, action int) (*pmapi.Message, error) {
|
||||
if err := api.checkAndRecordCall(POST, "/messages", &pmapi.DraftReq{
|
||||
Message: message,
|
||||
ParentID: parentID,
|
||||
Action: action,
|
||||
AttachmentKeyPackets: []string{},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parentID != "" {
|
||||
if _, err := api.GetMessage(parentID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if message.Subject == "" {
|
||||
message.Subject = "(No Subject)"
|
||||
}
|
||||
message.LabelIDs = append(message.LabelIDs, pmapi.DraftLabel)
|
||||
message.LabelIDs = append(message.LabelIDs, pmapi.AllMailLabel)
|
||||
message.ID = api.controller.messageIDGenerator.next("")
|
||||
api.addMessage(message)
|
||||
return message, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) SendMessage(messageID string, sendMessageRequest *pmapi.SendMessageReq) (sent, parent *pmapi.Message, err error) {
|
||||
if err := api.checkAndRecordCall(POST, "/messages/"+messageID, sendMessageRequest); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
message, err := api.GetMessage(messageID)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "draft does not exist")
|
||||
}
|
||||
message.Time = time.Now().Unix()
|
||||
message.LabelIDs = append(message.LabelIDs, pmapi.SentLabel)
|
||||
api.addEventMessage(pmapi.EventUpdate, message)
|
||||
return message, nil, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) Import(importMessageRequests []*pmapi.ImportMsgReq) ([]*pmapi.ImportMsgRes, error) {
|
||||
msgRes := []*pmapi.ImportMsgRes{}
|
||||
for _, msgReq := range importMessageRequests {
|
||||
mailMessage, err := mail.ReadMessage(bytes.NewBuffer(msgReq.Body))
|
||||
if err != nil {
|
||||
msgRes = append(msgRes, &pmapi.ImportMsgRes{
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
messageID := api.controller.messageIDGenerator.next("")
|
||||
message := &pmapi.Message{
|
||||
ID: messageID,
|
||||
AddressID: msgReq.AddressID,
|
||||
Sender: &mail.Address{Address: mailMessage.Header.Get("From")},
|
||||
ToList: []*mail.Address{{Address: mailMessage.Header.Get("To")}},
|
||||
Subject: mailMessage.Header.Get("Subject"),
|
||||
Unread: msgReq.Unread,
|
||||
LabelIDs: msgReq.LabelIDs,
|
||||
Body: string(msgReq.Body),
|
||||
Flags: msgReq.Flags,
|
||||
Time: msgReq.Time,
|
||||
}
|
||||
msgRes = append(msgRes, &pmapi.ImportMsgRes{
|
||||
Error: nil,
|
||||
MessageID: messageID,
|
||||
})
|
||||
api.addMessage(message)
|
||||
}
|
||||
return msgRes, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) addMessage(message *pmapi.Message) {
|
||||
api.messages = append(api.messages, message)
|
||||
api.addEventMessage(pmapi.EventCreate, message)
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) DeleteMessages(apiIDs []string) error {
|
||||
err := api.deleteMessages(PUT, "/messages/delete", &pmapi.MessagesActionReq{
|
||||
IDs: apiIDs,
|
||||
}, func(message *pmapi.Message) bool {
|
||||
return hasItem(apiIDs, message.ID)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(apiIDs) == 0 {
|
||||
return errBadRequest
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) EmptyFolder(labelID string, addressID string) error {
|
||||
err := api.deleteMessages(DELETE, "/messages/empty?LabelID="+labelID+"&AddressID="+addressID, nil, func(message *pmapi.Message) bool {
|
||||
return hasItem(message.LabelIDs, labelID) && message.AddressID == addressID
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if labelID == "" {
|
||||
return errBadRequest
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) deleteMessages(method method, path string, request interface{}, shouldBeDeleted func(*pmapi.Message) bool) error {
|
||||
if err := api.checkAndRecordCall(method, path, request); err != nil {
|
||||
return err
|
||||
}
|
||||
newMessages := []*pmapi.Message{}
|
||||
for _, message := range api.messages {
|
||||
if shouldBeDeleted(message) {
|
||||
if hasItem(message.LabelIDs, pmapi.TrashLabel) {
|
||||
api.addEventMessage(pmapi.EventDelete, message)
|
||||
continue
|
||||
}
|
||||
message.LabelIDs = []string{pmapi.TrashLabel, pmapi.AllMailLabel}
|
||||
api.addEventMessage(pmapi.EventUpdate, message)
|
||||
}
|
||||
newMessages = append(newMessages, message)
|
||||
}
|
||||
api.messages = newMessages
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) LabelMessages(apiIDs []string, labelID string) error {
|
||||
return api.updateMessages(PUT, "/messages/label", &pmapi.LabelMessagesReq{
|
||||
IDs: apiIDs,
|
||||
LabelID: labelID,
|
||||
}, apiIDs, func(message *pmapi.Message) error {
|
||||
if labelID == "" {
|
||||
return errBadRequest
|
||||
}
|
||||
if labelID == pmapi.TrashLabel {
|
||||
message.LabelIDs = []string{pmapi.TrashLabel, pmapi.AllMailLabel}
|
||||
return nil
|
||||
}
|
||||
if api.isLabelFolder(labelID) {
|
||||
labelIDs := []string{}
|
||||
for _, existingLabelID := range message.LabelIDs {
|
||||
if !api.isLabelFolder(existingLabelID) {
|
||||
labelIDs = append(labelIDs, existingLabelID)
|
||||
}
|
||||
}
|
||||
message.LabelIDs = labelIDs
|
||||
}
|
||||
message.LabelIDs = addItem(message.LabelIDs, labelID)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) UnlabelMessages(apiIDs []string, labelID string) error {
|
||||
return api.updateMessages(PUT, "/messages/unlabel", &pmapi.LabelMessagesReq{
|
||||
IDs: apiIDs,
|
||||
LabelID: labelID,
|
||||
}, apiIDs, func(message *pmapi.Message) error {
|
||||
if labelID == "" {
|
||||
return errBadRequest
|
||||
}
|
||||
// All Mail and Sent cannot be unlabeled, but API will not throw error.
|
||||
if labelID == pmapi.AllMailLabel || labelID == pmapi.SentLabel {
|
||||
return errWasNotUpdated
|
||||
}
|
||||
|
||||
message.LabelIDs = removeItem(message.LabelIDs, labelID)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) MarkMessagesRead(apiIDs []string) error {
|
||||
return api.updateMessages(PUT, "/messages/read", &pmapi.MessagesActionReq{
|
||||
IDs: apiIDs,
|
||||
}, apiIDs, func(message *pmapi.Message) error {
|
||||
if message.Unread == 0 {
|
||||
return errWasNotUpdated
|
||||
}
|
||||
message.Unread = 0
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) MarkMessagesUnread(apiIDs []string) error {
|
||||
err := api.updateMessages(PUT, "/messages/unread", &pmapi.MessagesActionReq{
|
||||
IDs: apiIDs,
|
||||
}, apiIDs, func(message *pmapi.Message) error {
|
||||
if message.Unread == 1 {
|
||||
return errWasNotUpdated
|
||||
}
|
||||
message.Unread = 1
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) updateMessages(method method, path string, request interface{}, apiIDs []string, updateCallback func(*pmapi.Message) error) error { //nolint[unparam]
|
||||
if err := api.checkAndRecordCall(method, path, request); err != nil {
|
||||
return err
|
||||
}
|
||||
// API will return error if you send request for no apiIDs
|
||||
if len(apiIDs) == 0 {
|
||||
return errBadRequest
|
||||
}
|
||||
for _, message := range api.messages {
|
||||
if hasItem(apiIDs, message.ID) {
|
||||
err := updateCallback(message)
|
||||
if err != nil {
|
||||
if err == errWasNotUpdated {
|
||||
continue
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
api.addEventMessage(pmapi.EventUpdate, message)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
44
test/fakeapi/reports.go
Normal file
44
test/fakeapi/reports.go
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2020 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) ReportBugWithEmailClient(os, osVersion, title, description, username, email, emailClient string) error {
|
||||
return api.checkInternetAndRecordCall(POST, "/reports/bug", &pmapi.ReportReq{
|
||||
OS: os,
|
||||
OSVersion: osVersion,
|
||||
Title: title,
|
||||
Description: description,
|
||||
Username: username,
|
||||
Email: email,
|
||||
Browser: emailClient,
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
61
test/fakeapi/user.go
Normal file
61
test/fakeapi/user.go
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2020 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 (
|
||||
pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (api *FakePMAPI) GetMailSettings() (pmapi.MailSettings, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/settings/mail", nil); err != nil {
|
||||
return pmapi.MailSettings{}, err
|
||||
}
|
||||
return pmapi.MailSettings{}, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) Unlock(mailboxPassword string) (*pmcrypto.KeyRing, error) {
|
||||
return &pmcrypto.KeyRing{
|
||||
FirstKeyID: "key",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) UnlockAddresses(password []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) CurrentUser() (*pmapi.User, error) {
|
||||
return api.UpdateUser()
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) UpdateUser() (*pmapi.User, error) {
|
||||
if err := api.checkAndRecordCall(GET, "/users", nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return api.user, nil
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) Addresses() pmapi.AddressList {
|
||||
return *api.addresses
|
||||
}
|
||||
|
||||
func (api *FakePMAPI) KeyRingForAddressID(addrID string) *pmcrypto.KeyRing {
|
||||
return &pmcrypto.KeyRing{
|
||||
FirstKeyID: "key",
|
||||
}
|
||||
}
|
||||
46
test/fakeapi/utils.go
Normal file
46
test/fakeapi/utils.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2020 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
|
||||
|
||||
func hasItem(items []string, value string) bool {
|
||||
for _, item := range items {
|
||||
if item == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func removeItem(items []string, value string) []string {
|
||||
newItems := []string{}
|
||||
for _, item := range items {
|
||||
if item != value {
|
||||
newItems = append(newItems, item)
|
||||
}
|
||||
}
|
||||
return newItems
|
||||
}
|
||||
|
||||
func addItem(items []string, value string) []string {
|
||||
for _, item := range items {
|
||||
if item == value {
|
||||
return items
|
||||
}
|
||||
}
|
||||
return append(items, value)
|
||||
}
|
||||
Reference in New Issue
Block a user