forked from Silverfish/proton-bridge
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4198737a6 | |||
| 04881b9b78 | |||
| 990b8cda96 | |||
| 27889b8085 | |||
| 2cd7735468 | |||
| 8990f2d1d6 | |||
| 7bc608ce6c | |||
| 01c7daaba7 | |||
| 8408a5fdc0 | |||
| 828fe0e86e | |||
| 5c3179df48 | |||
| 618cb27ac1 | |||
| 83a569b366 | |||
| 70244071ea |
24
Changelog.md
24
Changelog.md
@ -2,6 +2,30 @@
|
|||||||
|
|
||||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
|
## [Bridge 3.0.5] Perth Narrows
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-2178: Bump go-proton-api to fix drafts.
|
||||||
|
* GODT-2180: Allow login with FIDO2.
|
||||||
|
|
||||||
|
## [Bridge 3.0.4] Perth Narrows
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Other: Do not list \Deleted flag for All Mail.
|
||||||
|
* Other: Disable perma-delete for expunge on Spam folder.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Other: Ensure expunge feature test pushes to error stack.
|
||||||
|
* GODT-2170: Use client-side draft update in integration tests.
|
||||||
|
* GODT-2170: Improving test server behaviour.
|
||||||
|
* GODT-2170: Update draft event means delete old and create new message.
|
||||||
|
* GODT-2170: User create draft route: first steps.
|
||||||
|
|
||||||
|
## [Bridge 3.0.3] Perth Narrows
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GPA v0.1.4: fix token expiration mechanism.
|
||||||
|
|
||||||
## [Bridge 3.0.2] Perth Narrows
|
## [Bridge 3.0.2] Perth Narrows
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
2
Makefile
2
Makefile
@ -11,7 +11,7 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
|||||||
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
||||||
|
|
||||||
# Keep version hardcoded so app build works also without Git repository.
|
# Keep version hardcoded so app build works also without Git repository.
|
||||||
BRIDGE_APP_VERSION?=3.0.1+git
|
BRIDGE_APP_VERSION?=3.0.5+git
|
||||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||||
APP_FULL_NAME:=Proton Mail Bridge
|
APP_FULL_NAME:=Proton Mail Bridge
|
||||||
APP_VENDOR:=Proton AG
|
APP_VENDOR:=Proton AG
|
||||||
|
|||||||
4
go.mod
4
go.mod
@ -5,9 +5,9 @@ go 1.18
|
|||||||
require (
|
require (
|
||||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||||
github.com/Masterminds/semver/v3 v3.1.1
|
github.com/Masterminds/semver/v3 v3.1.1
|
||||||
github.com/ProtonMail/gluon v0.14.2-0.20221129150032-c663738a6cee
|
github.com/ProtonMail/gluon v0.14.2-0.20221202093012-ad1570c49c8c
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||||
github.com/ProtonMail/go-proton-api v0.1.2
|
github.com/ProtonMail/go-proton-api v0.2.1
|
||||||
github.com/ProtonMail/go-rfc5322 v0.11.0
|
github.com/ProtonMail/go-rfc5322 v0.11.0
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.4.10
|
github.com/ProtonMail/gopenpgp/v2 v2.4.10
|
||||||
github.com/PuerkitoBio/goquery v1.8.0
|
github.com/PuerkitoBio/goquery v1.8.0
|
||||||
|
|||||||
8
go.sum
8
go.sum
@ -28,8 +28,8 @@ github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs
|
|||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
||||||
github.com/ProtonMail/gluon v0.14.2-0.20221129150032-c663738a6cee h1:rDGqVa4CepqpJF8TDjqnBITqD8OzrLzeg66ibVDCPSc=
|
github.com/ProtonMail/gluon v0.14.2-0.20221202093012-ad1570c49c8c h1:DzVlJERHOHDQjYz/P12VlORS4rF2Ii83cWcYHsXGdng=
|
||||||
github.com/ProtonMail/gluon v0.14.2-0.20221129150032-c663738a6cee/go.mod h1:z2AxLIiBCT1K+0OBHyaDI7AEaO5qI6/BEC2TE42vs4Q=
|
github.com/ProtonMail/gluon v0.14.2-0.20221202093012-ad1570c49c8c/go.mod h1:z2AxLIiBCT1K+0OBHyaDI7AEaO5qI6/BEC2TE42vs4Q=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
@ -43,8 +43,8 @@ github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753/go.mod h1:NB
|
|||||||
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f h1:4IWzKjHzZxdrW9k4zl/qCwenOVHDbVDADPPHFLjs0Oc=
|
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f h1:4IWzKjHzZxdrW9k4zl/qCwenOVHDbVDADPPHFLjs0Oc=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f/go.mod h1:qRZgbeASl2a9OwmsV85aWwRqic0NHPh+9ewGAzb4cgM=
|
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f/go.mod h1:qRZgbeASl2a9OwmsV85aWwRqic0NHPh+9ewGAzb4cgM=
|
||||||
github.com/ProtonMail/go-proton-api v0.1.2 h1:MD0lbo8ohU1O+1mbMU6EkDmVj4BAq5e5cCPkIZgDF9Q=
|
github.com/ProtonMail/go-proton-api v0.2.1 h1:M15/zzfx6EPiskv2+gogUkmvx7Y1SmRRtLT6GiBh5T0=
|
||||||
github.com/ProtonMail/go-proton-api v0.1.2/go.mod h1:jqvJ2HqLHqiPJoEb+BTIB1IF7wvr6p+8ZfA6PO2NRNk=
|
github.com/ProtonMail/go-proton-api v0.2.1/go.mod h1:jqvJ2HqLHqiPJoEb+BTIB1IF7wvr6p+8ZfA6PO2NRNk=
|
||||||
github.com/ProtonMail/go-rfc5322 v0.11.0 h1:o5Obrm4DpmQEffvgsVqG6S4BKwC1Wat+hYwjIp2YcCY=
|
github.com/ProtonMail/go-rfc5322 v0.11.0 h1:o5Obrm4DpmQEffvgsVqG6S4BKwC1Wat+hYwjIp2YcCY=
|
||||||
github.com/ProtonMail/go-rfc5322 v0.11.0/go.mod h1:6oOKr0jXvpoE6pwTx/HukigQpX2J9WUf6h0auplrFTw=
|
github.com/ProtonMail/go-rfc5322 v0.11.0/go.mod h1:6oOKr0jXvpoE6pwTx/HukigQpX2J9WUf6h0auplrFTw=
|
||||||
github.com/ProtonMail/go-srp v0.0.5 h1:xhUioxZgDbCnpo9JehyFhwwsn9JLWkUGfB0oiKXgiGg=
|
github.com/ProtonMail/go-srp v0.0.5 h1:xhUioxZgDbCnpo9JehyFhwwsn9JLWkUGfB0oiKXgiGg=
|
||||||
|
|||||||
@ -182,7 +182,7 @@ func (bridge *Bridge) LoginFull(
|
|||||||
return "", fmt.Errorf("failed to begin login process: %w", err)
|
return "", fmt.Errorf("failed to begin login process: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if auth.TwoFA.Enabled == proton.TOTPEnabled {
|
if auth.TwoFA.Enabled&proton.HasTOTP != 0 {
|
||||||
logrus.WithField("userID", auth.UserID).Info("Requesting TOTP")
|
logrus.WithField("userID", auth.UserID).Info("Requesting TOTP")
|
||||||
|
|
||||||
totp, err := getTOTP()
|
totp, err := getTOTP()
|
||||||
|
|||||||
@ -149,7 +149,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { //nolint:funlen
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if auth.TwoFA.Enabled == proton.TOTPEnabled {
|
if auth.TwoFA.Enabled&proton.HasTOTP != 0 {
|
||||||
code := f.readStringInAttempts("Two factor code", c.ReadLine, isNotEmpty)
|
code := f.readStringInAttempts("Two factor code", c.ReadLine, isNotEmpty)
|
||||||
if code == "" {
|
if code == "" {
|
||||||
f.printAndLogError("Cannot login: need two factor code")
|
f.printAndLogError("Cannot login: need two factor code")
|
||||||
|
|||||||
@ -406,7 +406,7 @@ func (s *Service) Login(ctx context.Context, login *LoginRequest) (*emptypb.Empt
|
|||||||
s.auth = auth
|
s.auth = auth
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case auth.TwoFA.Enabled == proton.TOTPEnabled:
|
case auth.TwoFA.Enabled&proton.HasTOTP != 0:
|
||||||
_ = s.SendEvent(NewLoginTfaRequestedEvent(login.Username))
|
_ = s.SendEvent(NewLoginTfaRequestedEvent(login.Username))
|
||||||
|
|
||||||
case auth.PasswordMode == proton.TwoPasswordMode:
|
case auth.PasswordMode == proton.TwoPasswordMode:
|
||||||
|
|||||||
@ -389,6 +389,18 @@ func (user *User) handleMessageEvents(ctx context.Context, messageEvents []proto
|
|||||||
}
|
}
|
||||||
|
|
||||||
case proton.EventUpdate, proton.EventUpdateFlags:
|
case proton.EventUpdate, proton.EventUpdateFlags:
|
||||||
|
// Draft update means to completely remove old message and upload the new data again.
|
||||||
|
if event.Message.IsDraft() {
|
||||||
|
if err := user.handleUpdateDraftEvent(
|
||||||
|
logging.WithLogrusField(ctx, "action", "update draft"),
|
||||||
|
event,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("failed to handle update draft event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GODT-2028 - Use better events here. It should be possible to have 3 separate events that refrain to
|
// GODT-2028 - Use better events here. It should be possible to have 3 separate events that refrain to
|
||||||
// whether the flags, labels or read only data (header+body) has been changed. This requires fixing proton
|
// whether the flags, labels or read only data (header+body) has been changed. This requires fixing proton
|
||||||
// first so that it correctly reports those cases.
|
// first so that it correctly reports those cases.
|
||||||
@ -400,16 +412,6 @@ func (user *User) handleMessageEvents(ctx context.Context, messageEvents []proto
|
|||||||
return fmt.Errorf("failed to handle update message event: %w", err)
|
return fmt.Errorf("failed to handle update message event: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only issue body updates if the message is a draft.
|
|
||||||
if event.Message.IsDraft() {
|
|
||||||
if err := user.handleUpdateDraftEvent(
|
|
||||||
logging.WithLogrusField(ctx, "action", "update draft"),
|
|
||||||
event,
|
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("failed to handle update draft event: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case proton.EventDelete:
|
case proton.EventDelete:
|
||||||
if err := user.handleDeleteMessageEvent(
|
if err := user.handleDeleteMessageEvent(
|
||||||
logging.WithLogrusField(ctx, "action", "delete message"),
|
logging.WithLogrusField(ctx, "action", "delete message"),
|
||||||
|
|||||||
@ -18,8 +18,10 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/mail"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -31,6 +33,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/pkg/message"
|
"github.com/ProtonMail/proton-bridge/v3/pkg/message"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/pkg/message/parser"
|
||||||
"github.com/bradenaw/juniper/stream"
|
"github.com/bradenaw/juniper/stream"
|
||||||
"github.com/bradenaw/juniper/xslices"
|
"github.com/bradenaw/juniper/xslices"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
@ -326,7 +329,7 @@ func (conn *imapConnector) RemoveMessagesFromMailbox(ctx context.Context, messag
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if mailboxID == proton.SpamLabel || mailboxID == proton.TrashLabel || mailboxID == proton.DraftsLabel {
|
if mailboxID == proton.TrashLabel || mailboxID == proton.DraftsLabel {
|
||||||
var metadata []proton.MessageMetadata
|
var metadata []proton.MessageMetadata
|
||||||
|
|
||||||
// There's currently no limit on how many IDs we can filter on,
|
// There's currently no limit on how many IDs we can filter on,
|
||||||
@ -437,6 +440,18 @@ func (conn *imapConnector) importMessage(
|
|||||||
|
|
||||||
if err := safe.RLockRet(func() error {
|
if err := safe.RLockRet(func() error {
|
||||||
return withAddrKR(conn.apiUser, conn.apiAddrs[conn.addrID], conn.vault.KeyPass(), func(_, addrKR *crypto.KeyRing) error {
|
return withAddrKR(conn.apiUser, conn.apiAddrs[conn.addrID], conn.vault.KeyPass(), func(_, addrKR *crypto.KeyRing) error {
|
||||||
|
messageID := ""
|
||||||
|
|
||||||
|
if slices.Contains(labelIDs, proton.DraftsLabel) {
|
||||||
|
msg, err := conn.createDraft(ctx, literal, addrKR, conn.apiAddrs[conn.addrID])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create draft: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply labels
|
||||||
|
|
||||||
|
messageID = msg.ID
|
||||||
|
} else {
|
||||||
res, err := stream.Collect(ctx, conn.client.ImportMessages(ctx, addrKR, 1, 1, []proton.ImportReq{{
|
res, err := stream.Collect(ctx, conn.client.ImportMessages(ctx, addrKR, 1, 1, []proton.ImportReq{{
|
||||||
Metadata: proton.ImportMetadata{
|
Metadata: proton.ImportMetadata{
|
||||||
AddressID: conn.addrID,
|
AddressID: conn.addrID,
|
||||||
@ -450,7 +465,12 @@ func (conn *imapConnector) importMessage(
|
|||||||
return fmt.Errorf("failed to import message: %w", err)
|
return fmt.Errorf("failed to import message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if full, err = conn.client.GetFullMessage(ctx, res[0].MessageID); err != nil {
|
messageID = res[0].MessageID
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if full, err = conn.client.GetFullMessage(ctx, messageID); err != nil {
|
||||||
return fmt.Errorf("failed to fetch message: %w", err)
|
return fmt.Errorf("failed to fetch message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,6 +517,63 @@ func toIMAPMessage(message proton.MessageMetadata) imap.Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (conn *imapConnector) createDraft(ctx context.Context, literal []byte, addrKR *crypto.KeyRing, sender proton.Address) (proton.Message, error) { //nolint:funlen
|
||||||
|
// Create a new message parser from the reader.
|
||||||
|
parser, err := parser.New(bytes.NewReader(literal))
|
||||||
|
if err != nil {
|
||||||
|
return proton.Message{}, fmt.Errorf("failed to create parser: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
message, err := message.ParseWithParser(parser)
|
||||||
|
if err != nil {
|
||||||
|
return proton.Message{}, fmt.Errorf("failed to parse message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decBody := string(message.PlainBody)
|
||||||
|
if message.RichBody != "" {
|
||||||
|
decBody = string(message.RichBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
draft, err := conn.client.CreateDraft(ctx, addrKR, proton.CreateDraftReq{
|
||||||
|
Message: proton.DraftTemplate{
|
||||||
|
Subject: message.Subject,
|
||||||
|
Body: decBody,
|
||||||
|
MIMEType: message.MIMEType,
|
||||||
|
|
||||||
|
Sender: &mail.Address{Name: sender.DisplayName, Address: sender.Email},
|
||||||
|
ToList: message.ToList,
|
||||||
|
CCList: message.CCList,
|
||||||
|
BCCList: message.BCCList,
|
||||||
|
|
||||||
|
ExternalID: message.ExternalID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return proton.Message{}, fmt.Errorf("failed to create draft: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, att := range message.Attachments {
|
||||||
|
disposition := proton.AttachmentDisposition
|
||||||
|
if att.Disposition == "inline" && att.ContentID != "" {
|
||||||
|
disposition = proton.InlineDisposition
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.client.UploadAttachment(ctx, addrKR, proton.CreateAttachmentReq{
|
||||||
|
MessageID: draft.ID,
|
||||||
|
Filename: att.Name,
|
||||||
|
MIMEType: rfc822.MIMEType(att.MIMEType),
|
||||||
|
Disposition: disposition,
|
||||||
|
ContentID: att.ContentID,
|
||||||
|
Body: att.Data,
|
||||||
|
}); err != nil {
|
||||||
|
return proton.Message{}, fmt.Errorf("failed to add attachment to draft: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return draft, nil
|
||||||
|
}
|
||||||
|
|
||||||
func toIMAPMailbox(label proton.Label, flags, permFlags, attrs imap.FlagSet) imap.Mailbox {
|
func toIMAPMailbox(label proton.Label, flags, permFlags, attrs imap.FlagSet) imap.Mailbox {
|
||||||
if label.Type == proton.LabelTypeLabel {
|
if label.Type == proton.LabelTypeLabel {
|
||||||
label.Path = append([]string{labelPrefix}, label.Path...)
|
label.Path = append([]string{labelPrefix}, label.Path...)
|
||||||
|
|||||||
@ -188,19 +188,9 @@ func sendWithKey( //nolint:funlen
|
|||||||
return proton.Message{}, fmt.Errorf("unsupported MIME type: %v", message.MIMEType)
|
return proton.Message{}, fmt.Errorf("unsupported MIME type: %v", message.MIMEType)
|
||||||
}
|
}
|
||||||
|
|
||||||
encBody, err := addrKR.Encrypt(crypto.NewPlainMessageFromString(decBody), nil)
|
draft, err := createDraft(ctx, client, addrKR, emails, from, to, parentID, message.InReplyTo, proton.DraftTemplate{
|
||||||
if err != nil {
|
|
||||||
return proton.Message{}, fmt.Errorf("failed to encrypt message body: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
armBody, err := encBody.GetArmored()
|
|
||||||
if err != nil {
|
|
||||||
return proton.Message{}, fmt.Errorf("failed to get armored message body: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
draft, err := createDraft(ctx, client, emails, from, to, parentID, message.InReplyTo, proton.DraftTemplate{
|
|
||||||
Subject: message.Subject,
|
Subject: message.Subject,
|
||||||
Body: armBody,
|
Body: decBody,
|
||||||
MIMEType: message.MIMEType,
|
MIMEType: message.MIMEType,
|
||||||
|
|
||||||
Sender: message.Sender,
|
Sender: message.Sender,
|
||||||
@ -312,6 +302,7 @@ func getParentID( //nolint:funlen
|
|||||||
func createDraft(
|
func createDraft(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
client *proton.Client,
|
client *proton.Client,
|
||||||
|
addrKR *crypto.KeyRing,
|
||||||
emails []string,
|
emails []string,
|
||||||
from string,
|
from string,
|
||||||
to []string,
|
to []string,
|
||||||
@ -357,7 +348,7 @@ func createDraft(
|
|||||||
action = proton.ForwardAction
|
action = proton.ForwardAction
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.CreateDraft(ctx, proton.CreateDraftReq{
|
return client.CreateDraft(ctx, addrKR, proton.CreateDraftReq{
|
||||||
Message: template,
|
Message: template,
|
||||||
ParentID: parentID,
|
ParentID: parentID,
|
||||||
Action: action,
|
Action: action,
|
||||||
|
|||||||
@ -333,6 +333,8 @@ func newSystemMailboxCreatedUpdate(labelID imap.MailboxID, labelName string) *im
|
|||||||
}
|
}
|
||||||
|
|
||||||
attrs := imap.NewFlagSet(imap.AttrNoInferiors)
|
attrs := imap.NewFlagSet(imap.AttrNoInferiors)
|
||||||
|
permanentFlags := defaultPermanentFlags
|
||||||
|
flags := defaultFlags
|
||||||
|
|
||||||
switch labelID {
|
switch labelID {
|
||||||
case proton.TrashLabel:
|
case proton.TrashLabel:
|
||||||
@ -343,6 +345,8 @@ func newSystemMailboxCreatedUpdate(labelID imap.MailboxID, labelName string) *im
|
|||||||
|
|
||||||
case proton.AllMailLabel:
|
case proton.AllMailLabel:
|
||||||
attrs = attrs.Add(imap.AttrAll)
|
attrs = attrs.Add(imap.AttrAll)
|
||||||
|
flags = imap.NewFlagSet(imap.FlagSeen, imap.FlagFlagged)
|
||||||
|
permanentFlags = imap.NewFlagSet(imap.FlagSeen, imap.FlagFlagged)
|
||||||
|
|
||||||
case proton.ArchiveLabel:
|
case proton.ArchiveLabel:
|
||||||
attrs = attrs.Add(imap.AttrArchive)
|
attrs = attrs.Add(imap.AttrArchive)
|
||||||
@ -360,8 +364,8 @@ func newSystemMailboxCreatedUpdate(labelID imap.MailboxID, labelName string) *im
|
|||||||
return imap.NewMailboxCreated(imap.Mailbox{
|
return imap.NewMailboxCreated(imap.Mailbox{
|
||||||
ID: labelID,
|
ID: labelID,
|
||||||
Name: []string{labelName},
|
Name: []string{labelName},
|
||||||
Flags: defaultFlags,
|
Flags: flags,
|
||||||
PermanentFlags: defaultPermanentFlags,
|
PermanentFlags: permanentFlags,
|
||||||
Attributes: attrs,
|
Attributes: attrs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,6 @@ package tests
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/ProtonMail/go-proton-api"
|
|
||||||
"github.com/ProtonMail/go-proton-api/server"
|
"github.com/ProtonMail/go-proton-api/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,8 +35,6 @@ type API interface {
|
|||||||
RemoveAddress(userID, addrID string) error
|
RemoveAddress(userID, addrID string) error
|
||||||
RemoveAddressKey(userID, addrID, keyID string) error
|
RemoveAddressKey(userID, addrID, keyID string) error
|
||||||
|
|
||||||
UpdateDraft(userID, draftID string, changes proton.DraftTemplate) error
|
|
||||||
|
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -98,7 +98,7 @@ func TestFeatures(testingT *testing.T) {
|
|||||||
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has the following messages in "([^"]*)":$`, s.theAddressOfAccountHasTheFollowingMessagesInMailbox)
|
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has the following messages in "([^"]*)":$`, s.theAddressOfAccountHasTheFollowingMessagesInMailbox)
|
||||||
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has (\d+) messages in "([^"]*)"$`, s.theAddressOfAccountHasMessagesInMailbox)
|
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has (\d+) messages in "([^"]*)"$`, s.theAddressOfAccountHasMessagesInMailbox)
|
||||||
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has no keys$`, s.theAddressOfAccountHasNoKeys)
|
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has no keys$`, s.theAddressOfAccountHasNoKeys)
|
||||||
ctx.Step(`^the following fields where changed in draft (\d+) for address "([^"]*)" of account "([^"]*)":$`, s.addressDraftChanged)
|
ctx.Step(`^the following fields were changed in draft (\d+) for address "([^"]*)" of account "([^"]*)":$`, s.theFollowingFieldsWereChangedInDraftForAddressOfAccount)
|
||||||
|
|
||||||
// ==== BRIDGE ====
|
// ==== BRIDGE ====
|
||||||
ctx.Step(`^bridge starts$`, s.bridgeStarts)
|
ctx.Step(`^bridge starts$`, s.bridgeStarts)
|
||||||
|
|||||||
@ -230,36 +230,28 @@ func (t *testCtx) getMBoxID(userID string, name string) string {
|
|||||||
// getDraftID will return the API ID of draft message with draftIndex, where
|
// getDraftID will return the API ID of draft message with draftIndex, where
|
||||||
// draftIndex is similar to sequential ID i.e. 1 represents the first message
|
// draftIndex is similar to sequential ID i.e. 1 represents the first message
|
||||||
// of draft folder sorted by API creation time.
|
// of draft folder sorted by API creation time.
|
||||||
func (t *testCtx) getDraftID(username string, draftIndex int) string {
|
func (t *testCtx) getDraftID(username string, draftIndex int) (string, error) {
|
||||||
if draftIndex < 1 {
|
|
||||||
panic(fmt.Sprintf("draft index suppose to be non-zero positive integer, but have %d", draftIndex))
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var draftID string
|
var draftID string
|
||||||
|
|
||||||
if err := t.withClient(ctx, username, func(ctx context.Context, client *proton.Client) error {
|
if err := t.withClient(ctx, username, func(ctx context.Context, client *proton.Client) error {
|
||||||
messages, err := client.GetMessageMetadata(
|
messages, err := client.GetMessageMetadata(ctx, proton.MessageFilter{LabelID: proton.DraftsLabel})
|
||||||
ctx, proton.MessageFilter{LabelID: proton.DraftsLabel},
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return fmt.Errorf("failed to get message metadata: %w", err)
|
||||||
}
|
} else if len(messages) < draftIndex {
|
||||||
|
return fmt.Errorf("draft index %d is out of range", draftIndex)
|
||||||
if len(messages) < draftIndex {
|
|
||||||
panic("draft index too high")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
draftID = messages[draftIndex-1].ID
|
draftID = messages[draftIndex-1].ID
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
panic(err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return draftID
|
return draftID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *testCtx) getLastCall(method, pathExp string) (server.Call, error) {
|
func (t *testCtx) getLastCall(method, pathExp string) (server.Call, error) {
|
||||||
|
|||||||
@ -27,6 +27,7 @@ Feature: IMAP create messages
|
|||||||
And IMAP client "1" eventually sees the following messages in "Drafts":
|
And IMAP client "1" eventually sees the following messages in "Drafts":
|
||||||
| from | to | subject | body |
|
| from | to | subject | body |
|
||||||
| user@pm.me | john.doe@email.com | foo | bar |
|
| user@pm.me | john.doe@email.com | foo | bar |
|
||||||
|
# This fails now
|
||||||
And IMAP client "1" eventually sees the following messages in "All Mail":
|
And IMAP client "1" eventually sees the following messages in "All Mail":
|
||||||
| from | to | subject | body |
|
| from | to | subject | body |
|
||||||
| user@pm.me | john.doe@email.com | foo | bar |
|
| user@pm.me | john.doe@email.com | foo | bar |
|
||||||
|
|||||||
@ -16,12 +16,14 @@ Feature: IMAP remove messages from mailbox
|
|||||||
And IMAP client "1" marks message 2 as deleted
|
And IMAP client "1" marks message 2 as deleted
|
||||||
Then IMAP client "1" sees that message 2 has the flag "\Deleted"
|
Then IMAP client "1" sees that message 2 has the flag "\Deleted"
|
||||||
When IMAP client "1" expunges
|
When IMAP client "1" expunges
|
||||||
|
And it succeeds
|
||||||
Then IMAP client "1" sees 9 messages in "Folders/mbox"
|
Then IMAP client "1" sees 9 messages in "Folders/mbox"
|
||||||
|
|
||||||
Scenario: Mark all messages as deleted and EXPUNGE
|
Scenario: Mark all messages as deleted and EXPUNGE
|
||||||
When IMAP client "1" selects "Folders/mbox"
|
When IMAP client "1" selects "Folders/mbox"
|
||||||
And IMAP client "1" marks all messages as deleted
|
And IMAP client "1" marks all messages as deleted
|
||||||
And IMAP client "1" expunges
|
And IMAP client "1" expunges
|
||||||
|
And it succeeds
|
||||||
Then IMAP client "1" sees 0 messages in "Folders/mbox"
|
Then IMAP client "1" sees 0 messages in "Folders/mbox"
|
||||||
|
|
||||||
Scenario: Mark messages as undeleted and EXPUNGE
|
Scenario: Mark messages as undeleted and EXPUNGE
|
||||||
@ -30,11 +32,11 @@ Feature: IMAP remove messages from mailbox
|
|||||||
But IMAP client "1" marks message 2 as not deleted
|
But IMAP client "1" marks message 2 as not deleted
|
||||||
And IMAP client "1" marks message 3 as not deleted
|
And IMAP client "1" marks message 3 as not deleted
|
||||||
When IMAP client "1" expunges
|
When IMAP client "1" expunges
|
||||||
|
And it succeeds
|
||||||
Then IMAP client "1" sees 2 messages in "Folders/mbox"
|
Then IMAP client "1" sees 2 messages in "Folders/mbox"
|
||||||
|
|
||||||
# TODO(GODT-1989): Re-enable!
|
Scenario: Not possible to delete from All Mail and expunge does nothing
|
||||||
# Scenario: Not possible to delete from All Mail and expunge does nothing
|
When IMAP client "1" selects "All Mail"
|
||||||
# When IMAP client "1" selects "All Mail"
|
And IMAP client "1" marks message 2 as deleted
|
||||||
# And IMAP client "1" marks message 2 as deleted
|
And IMAP client "1" expunges
|
||||||
# And IMAP client "1" expunges
|
Then it fails
|
||||||
# Then IMAP client "1" eventually sees 10 messages in "All Mail"
|
|
||||||
@ -6,8 +6,8 @@ Feature: IMAP remove messages from Trash
|
|||||||
| mbox | folder |
|
| mbox | folder |
|
||||||
| label | label |
|
| label | label |
|
||||||
|
|
||||||
Scenario Outline: Message in Trash or Spam and some other label is not permanently deleted
|
Scenario Outline: Message in Trash and some other label is not permanently deleted
|
||||||
Given the address "user@pm.me" of account "user@pm.me" has the following messages in "<mailbox>":
|
Given the address "user@pm.me" of account "user@pm.me" has the following messages in "Trash":
|
||||||
| from | to | subject | body |
|
| from | to | subject | body |
|
||||||
| john.doe@mail.com | user@pm.me | foo | hello |
|
| john.doe@mail.com | user@pm.me | foo | hello |
|
||||||
| jane.doe@mail.com | name@pm.me | bar | world |
|
| jane.doe@mail.com | name@pm.me | bar | world |
|
||||||
@ -15,27 +15,22 @@ Feature: IMAP remove messages from Trash
|
|||||||
And the user logs in with username "user@pm.me" and password "password"
|
And the user logs in with username "user@pm.me" and password "password"
|
||||||
And user "user@pm.me" finishes syncing
|
And user "user@pm.me" finishes syncing
|
||||||
And user "user@pm.me" connects and authenticates IMAP client "1"
|
And user "user@pm.me" connects and authenticates IMAP client "1"
|
||||||
And IMAP client "1" selects "<mailbox>"
|
And IMAP client "1" selects "Trash"
|
||||||
When IMAP client "1" copies the message with subject "foo" from "<mailbox>" to "Labels/label"
|
When IMAP client "1" copies the message with subject "foo" from "Trash" to "Labels/label"
|
||||||
Then it succeeds
|
Then it succeeds
|
||||||
When IMAP client "1" marks the message with subject "foo" as deleted
|
When IMAP client "1" marks the message with subject "foo" as deleted
|
||||||
Then it succeeds
|
Then it succeeds
|
||||||
And IMAP client "1" sees 2 messages in "<mailbox>"
|
And IMAP client "1" sees 2 messages in "Trash"
|
||||||
And IMAP client "1" sees 2 messages in "All Mail"
|
And IMAP client "1" sees 2 messages in "All Mail"
|
||||||
And IMAP client "1" sees 1 messages in "Labels/label"
|
And IMAP client "1" sees 1 messages in "Labels/label"
|
||||||
When IMAP client "1" expunges
|
When IMAP client "1" expunges
|
||||||
Then it succeeds
|
Then it succeeds
|
||||||
And IMAP client "1" sees 1 messages in "<mailbox>"
|
And IMAP client "1" sees 1 messages in "Trash"
|
||||||
And IMAP client "1" sees 2 messages in "All Mail"
|
And IMAP client "1" sees 2 messages in "All Mail"
|
||||||
And IMAP client "1" sees 1 messages in "Labels/label"
|
And IMAP client "1" sees 1 messages in "Labels/label"
|
||||||
|
|
||||||
Examples:
|
Scenario Outline: Message in Trash only is permanently deleted
|
||||||
| mailbox |
|
Given the address "user@pm.me" of account "user@pm.me" has the following messages in "Trash":
|
||||||
| Spam |
|
|
||||||
| Trash |
|
|
||||||
|
|
||||||
Scenario Outline: Message in Trash or Spam only is permanently deleted
|
|
||||||
Given the address "user@pm.me" of account "user@pm.me" has the following messages in "<mailbox>":
|
|
||||||
| from | to | subject | body |
|
| from | to | subject | body |
|
||||||
| john.doe@mail.com | user@pm.me | foo | hello |
|
| john.doe@mail.com | user@pm.me | foo | hello |
|
||||||
| jane.doe@mail.com | name@pm.me | bar | world |
|
| jane.doe@mail.com | name@pm.me | bar | world |
|
||||||
@ -43,17 +38,12 @@ Feature: IMAP remove messages from Trash
|
|||||||
And the user logs in with username "user@pm.me" and password "password"
|
And the user logs in with username "user@pm.me" and password "password"
|
||||||
And user "user@pm.me" finishes syncing
|
And user "user@pm.me" finishes syncing
|
||||||
And user "user@pm.me" connects and authenticates IMAP client "1"
|
And user "user@pm.me" connects and authenticates IMAP client "1"
|
||||||
And IMAP client "1" selects "<mailbox>"
|
And IMAP client "1" selects "Trash"
|
||||||
When IMAP client "1" marks the message with subject "foo" as deleted
|
When IMAP client "1" marks the message with subject "foo" as deleted
|
||||||
Then it succeeds
|
Then it succeeds
|
||||||
And IMAP client "1" sees 2 messages in "<mailbox>"
|
And IMAP client "1" sees 2 messages in "Trash"
|
||||||
And IMAP client "1" sees 2 messages in "All Mail"
|
And IMAP client "1" sees 2 messages in "All Mail"
|
||||||
When IMAP client "1" expunges
|
When IMAP client "1" expunges
|
||||||
Then it succeeds
|
Then it succeeds
|
||||||
And IMAP client "1" sees 1 messages in "<mailbox>"
|
And IMAP client "1" sees 1 messages in "Trash"
|
||||||
And IMAP client "1" eventually sees 1 messages in "All Mail"
|
And IMAP client "1" eventually sees 1 messages in "All Mail"
|
||||||
|
|
||||||
Examples:
|
|
||||||
| mailbox |
|
|
||||||
| Spam |
|
|
||||||
| Trash |
|
|
||||||
|
|||||||
@ -11,10 +11,15 @@ Feature: IMAP Draft messages
|
|||||||
|
|
||||||
This is a dra
|
This is a dra
|
||||||
"""
|
"""
|
||||||
|
Then IMAP client "1" eventually sees the following messages in "Drafts":
|
||||||
|
| body |
|
||||||
|
| This is a dra |
|
||||||
|
And IMAP client "1" sees 1 messages in "Drafts"
|
||||||
|
|
||||||
Scenario: Draft edited locally
|
Scenario: Draft edited locally
|
||||||
When IMAP client "1" marks message 1 as deleted
|
When IMAP client "1" marks message 1 as deleted
|
||||||
And IMAP client "1" expunges
|
And IMAP client "1" expunges
|
||||||
|
And it succeeds
|
||||||
And IMAP client "1" appends the following message to "Drafts":
|
And IMAP client "1" appends the following message to "Drafts":
|
||||||
"""
|
"""
|
||||||
Subject: Basic Draft
|
Subject: Basic Draft
|
||||||
@ -30,7 +35,7 @@ Feature: IMAP Draft messages
|
|||||||
And IMAP client "1" sees 1 messages in "Drafts"
|
And IMAP client "1" sees 1 messages in "Drafts"
|
||||||
|
|
||||||
Scenario: Draft edited remotely
|
Scenario: Draft edited remotely
|
||||||
When the following fields where changed in draft 1 for address "user@pm.me" of account "user@pm.me":
|
When the following fields were changed in draft 1 for address "user@pm.me" of account "user@pm.me":
|
||||||
| to | subject | body |
|
| to | subject | body |
|
||||||
| someone@proton.me | Basic Draft | This is a draft body, but longer |
|
| someone@proton.me | Basic Draft | This is a draft body, but longer |
|
||||||
Then IMAP client "1" eventually sees the following messages in "Drafts":
|
Then IMAP client "1" eventually sees the following messages in "Drafts":
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import (
|
|||||||
"github.com/emersion/go-imap"
|
"github.com/emersion/go-imap"
|
||||||
id "github.com/emersion/go-imap-id"
|
id "github.com/emersion/go-imap-id"
|
||||||
"github.com/emersion/go-imap/client"
|
"github.com/emersion/go-imap/client"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -280,7 +281,9 @@ func (s *scenario) imapClientSeesTheFollowingMessagesInMailbox(clientID, mailbox
|
|||||||
|
|
||||||
func (s *scenario) imapClientEventuallySeesTheFollowingMessagesInMailbox(clientID, mailbox string, table *godog.Table) error {
|
func (s *scenario) imapClientEventuallySeesTheFollowingMessagesInMailbox(clientID, mailbox string, table *godog.Table) error {
|
||||||
return eventually(func() error {
|
return eventually(func() error {
|
||||||
return s.imapClientSeesTheFollowingMessagesInMailbox(clientID, mailbox, table)
|
err := s.imapClientSeesTheFollowingMessagesInMailbox(clientID, mailbox, table)
|
||||||
|
logrus.WithError(err).Trace("Matching eventually")
|
||||||
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +377,9 @@ func (s *scenario) imapClientSeesThatMessageHasTheFlag(clientID string, seq int,
|
|||||||
func (s *scenario) imapClientExpunges(clientID string) error {
|
func (s *scenario) imapClientExpunges(clientID string) error {
|
||||||
_, client := s.t.getIMAPClient(clientID)
|
_, client := s.t.getIMAPClient(clientID)
|
||||||
|
|
||||||
return client.Expunge(nil)
|
s.t.pushError(client.Expunge(nil))
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *scenario) imapClientAppendsTheFollowingMessageToMailbox(clientID string, mailbox string, docString *godog.DocString) error {
|
func (s *scenario) imapClientAppendsTheFollowingMessageToMailbox(clientID string, mailbox string, docString *godog.DocString) error {
|
||||||
|
|||||||
@ -144,7 +144,7 @@ func matchMessages(have, want []Message) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if !IsSub(ToAny(have), ToAny(want)) {
|
if !IsSub(ToAny(have), ToAny(want)) {
|
||||||
return fmt.Errorf("missing messages: have %+v, want %+v", have, want)
|
return fmt.Errorf("missing messages: have %#v, want %#v", have, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -226,7 +226,7 @@ func (s *scenario) theAddressOfAccountHasNoKeys(address, username string) error
|
|||||||
// accountDraftChanged changes the draft attributes, where draftIndex is
|
// accountDraftChanged changes the draft attributes, where draftIndex is
|
||||||
// similar to sequential ID i.e. 1 represents the first message of draft folder
|
// similar to sequential ID i.e. 1 represents the first message of draft folder
|
||||||
// sorted by API creation time.
|
// sorted by API creation time.
|
||||||
func (s *scenario) addressDraftChanged(draftIndex int, address, username string, table *godog.Table) error {
|
func (s *scenario) theFollowingFieldsWereChangedInDraftForAddressOfAccount(draftIndex int, address, username string, table *godog.Table) error {
|
||||||
wantMessages, err := unmarshalTable[Message](table)
|
wantMessages, err := unmarshalTable[Message](table)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -236,35 +236,49 @@ func (s *scenario) addressDraftChanged(draftIndex int, address, username string,
|
|||||||
return fmt.Errorf("expected to have one row in table but got %d instead", len(wantMessages))
|
return fmt.Errorf("expected to have one row in table but got %d instead", len(wantMessages))
|
||||||
}
|
}
|
||||||
|
|
||||||
draftID := s.t.getDraftID(username, draftIndex)
|
draftID, err := s.t.getDraftID(username, draftIndex)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get draft ID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
encBody := []byte{}
|
|
||||||
|
|
||||||
if wantMessages[0].Body != "" {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := s.t.withClient(ctx, username, func(ctx context.Context, c *proton.Client) error {
|
return s.t.withClient(ctx, username, func(ctx context.Context, c *proton.Client) error {
|
||||||
return s.t.withAddrKR(ctx, c, username, s.t.getUserAddrID(s.t.getUserID(username), address),
|
return s.t.withAddrKR(ctx, c, username, s.t.getUserAddrID(s.t.getUserID(username), address), func(_ context.Context, addrKR *crypto.KeyRing) error {
|
||||||
func(ctx context.Context, addrKR *crypto.KeyRing) error {
|
var changes proton.DraftTemplate
|
||||||
var err error
|
|
||||||
encBody, err = proton.EncryptRFC822(addrKR, wantMessages[0].Build())
|
if wantMessages[0].From != "" {
|
||||||
return err
|
return fmt.Errorf("changing from address is not supported")
|
||||||
})
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changes := proton.DraftTemplate{
|
|
||||||
Subject: wantMessages[0].Subject,
|
|
||||||
Body: string(encBody),
|
|
||||||
}
|
|
||||||
if wantMessages[0].To != "" {
|
if wantMessages[0].To != "" {
|
||||||
changes.ToList = []*mail.Address{{Address: wantMessages[0].To}}
|
changes.ToList = []*mail.Address{{Address: wantMessages[0].To}}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.t.api.UpdateDraft(s.t.getUserID(username), draftID, changes)
|
if wantMessages[0].CC != "" {
|
||||||
|
changes.CCList = []*mail.Address{{Address: wantMessages[0].CC}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if wantMessages[0].BCC != "" {
|
||||||
|
changes.BCCList = []*mail.Address{{Address: wantMessages[0].BCC}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if wantMessages[0].Subject != "" {
|
||||||
|
changes.Subject = wantMessages[0].Subject
|
||||||
|
}
|
||||||
|
|
||||||
|
if wantMessages[0].Body != "" {
|
||||||
|
changes.Body = wantMessages[0].Body
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := c.UpdateDraft(ctx, draftID, addrKR, proton.UpdateDraftReq{Message: changes}); err != nil {
|
||||||
|
return fmt.Errorf("failed to update draft: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *scenario) userLogsInWithUsernameAndPassword(username, password string) error {
|
func (s *scenario) userLogsInWithUsernameAndPassword(username, password string) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user