chore: Merge remote-tracking branch 'origin/devel' into release/trift

This commit is contained in:
Romain LE JEUNE
2023-07-04 13:26:23 +02:00
16 changed files with 276 additions and 65 deletions

4
go.mod
View File

@ -5,9 +5,9 @@ go 1.20
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.2.0 github.com/Masterminds/semver/v3 v3.2.0
github.com/ProtonMail/gluon v0.16.1-0.20230628130101-52391e3cbf12 github.com/ProtonMail/gluon v0.16.1-0.20230704083024-d901d16834de
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.4.1-0.20230628092916-81cb3f87f184 github.com/ProtonMail/go-proton-api v0.4.1-0.20230704060229-a77a437ec052
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton
github.com/PuerkitoBio/goquery v1.8.1 github.com/PuerkitoBio/goquery v1.8.1
github.com/abiosoft/ishell v2.0.0+incompatible github.com/abiosoft/ishell v2.0.0+incompatible

13
go.sum
View File

@ -23,12 +23,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.16.1-0.20230628083422-e2cfbe8c5823 h1:mOaTSgPEUu0KZjH1SN7ZxqWIljlZFvyST48coZlvC1o= github.com/ProtonMail/gluon v0.16.1-0.20230704083024-d901d16834de h1:th289W4w6aE6IAdHwZgfHIgtK1ew9ZvAdXnie0BtVuw=
github.com/ProtonMail/gluon v0.16.1-0.20230628083422-e2cfbe8c5823/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo= github.com/ProtonMail/gluon v0.16.1-0.20230704083024-d901d16834de/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo=
github.com/ProtonMail/gluon v0.16.1-0.20230628112102-80c05f878b53 h1:38aw1TGmZlmY8l7uo+RRHnm97nZJz8xkEG//yPUS+9k=
github.com/ProtonMail/gluon v0.16.1-0.20230628112102-80c05f878b53/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo=
github.com/ProtonMail/gluon v0.16.1-0.20230628130101-52391e3cbf12 h1:/3S2RdUUASLW/JD+17yeJ+tanU6yWD4yzQSEXdilMMk=
github.com/ProtonMail/gluon v0.16.1-0.20230628130101-52391e3cbf12/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo=
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-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
@ -39,8 +35,8 @@ github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4= github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/go-proton-api v0.4.1-0.20230628092916-81cb3f87f184 h1:gw8sgQMCIDS/lw5xbF2iqlTfvY0HhuafjlGsKcN3VsE= github.com/ProtonMail/go-proton-api v0.4.1-0.20230704060229-a77a437ec052 h1:uIq0RX4gU9PSZ9x5b2LmJUXNOuBXRRVSOkM1RGnSy68=
github.com/ProtonMail/go-proton-api v0.4.1-0.20230628092916-81cb3f87f184/go.mod h1:+aTJoYu8bqzGECXL2DOdiZTZ64bGn3w0NC8VcFpJrFM= github.com/ProtonMail/go-proton-api v0.4.1-0.20230704060229-a77a437ec052/go.mod h1:+aTJoYu8bqzGECXL2DOdiZTZ64bGn3w0NC8VcFpJrFM=
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI= github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk= github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton h1:YS6M20yvjCJPR1r4ADW5TPn6rahs4iAyZaACei86bEc= github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton h1:YS6M20yvjCJPR1r4ADW5TPn6rahs4iAyZaACei86bEc=
@ -270,7 +266,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=

View File

@ -24,7 +24,7 @@ import (
func (bridge *Bridge) ReportBugClicked() { func (bridge *Bridge) ReportBugClicked() {
safe.Lock(func() { safe.Lock(func() {
for _, user := range bridge.users { for _, user := range bridge.users {
user.ReportBugSent() user.ReportBugClicked()
} }
}, bridge.usersLock) }, bridge.usersLock)
} }

View File

@ -0,0 +1,175 @@
// Copyright (c) 2023 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package bridge_test
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"io"
"net"
"strings"
"testing"
"time"
"github.com/ProtonMail/gluon/rfc822"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/go-proton-api/server"
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
go_imap "github.com/emersion/go-imap"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
)
func TestBridge_HandleDraftsSendFromOtherClient(t *testing.T) {
getGluonHeaderID := func(literal []byte) (string, string) {
h, err := rfc822.NewHeader(literal)
require.NoError(t, err)
gluonID, ok := h.GetChecked("X-Pm-Gluon-Id")
require.True(t, ok)
externalID, ok := h.GetChecked("Message-Id")
require.True(t, ok)
return gluonID, externalID
}
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
_, _, err := s.CreateUser("imap", password)
require.NoError(t, err)
_, _, err = s.CreateUser("bar", password)
require.NoError(t, err)
// The initial user should be fully synced.
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
waiter := waitForIMAPServerReady(b)
defer waiter.Done()
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
defer done()
userID, err := b.LoginFull(ctx, "imap", password, nil, nil)
require.NoError(t, err)
require.Equal(t, userID, (<-syncCh).UserID)
waiter.Wait()
info, err := b.GetUserInfo(userID)
require.NoError(t, err)
require.True(t, info.State == bridge.Connected)
client, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
require.NoError(t, err)
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
defer func() { _ = client.Logout() }()
// Create first draft in client.
literal := fmt.Sprintf(`From: %v
To: %v
Date: Fri, 3 Feb 2023 01:04:32 +0100
Subject: Foo
Hello
`, info.Addresses[0], "bar@proton.local")
require.NoError(t, client.Append("Drafts", nil, time.Now(), strings.NewReader(literal)))
// Verify the draft is available in client.
require.Eventually(t, func() bool {
status, err := client.Status("Drafts", []go_imap.StatusItem{go_imap.StatusMessages})
require.NoError(t, err)
return status.Messages == 1
}, 2*time.Second, time.Second)
// Retrieve the new literal so we can have the Proton Message ID.
messages, err := clientFetch(client, "Drafts")
require.NoError(t, err)
require.Equal(t, 1, len(messages))
newLiteral, err := io.ReadAll(messages[0].GetBody(must(go_imap.ParseBodySectionName("BODY[]"))))
require.NoError(t, err)
logrus.Info(string(newLiteral))
newLiteralID, newLiteralExternID := getGluonHeaderID(newLiteral)
// Modify new literal.
newLiteralModified := append(newLiteral, []byte(" world from client2")...) //nolint:gocritic
func() {
smtpClient, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(b.GetSMTPPort())))
require.NoError(t, err)
defer func() { _ = smtpClient.Close() }()
// Upgrade to TLS.
require.NoError(t, smtpClient.StartTLS(&tls.Config{InsecureSkipVerify: true}))
// Authorize with SASL PLAIN.
require.NoError(t, smtpClient.Auth(sasl.NewPlainClient(
info.Addresses[0],
info.Addresses[0],
string(info.BridgePass)),
))
// Send the message.
require.NoError(t, smtpClient.SendMail(
info.Addresses[0],
[]string{"bar@proton.local"},
bytes.NewReader(newLiteralModified),
))
}()
// Append message to Sent as the imap client would.
require.NoError(t, client.Append("Sent", nil, time.Now(), strings.NewReader(literal)))
// Verify the sent message gets updated with the new literal.
require.Eventually(t, func() bool {
// Check if sent message matches the latest draft.
messagesClient1, err := clientFetch(client, "Sent", "BODY[TEXT]", "BODY[]")
require.NoError(t, err)
if len(messagesClient1) != 1 {
return false
}
sentLiteral, err := io.ReadAll(messagesClient1[0].GetBody(must(go_imap.ParseBodySectionName("BODY[]"))))
require.NoError(t, err)
sentLiteralID, sentLiteralExternID := getGluonHeaderID(sentLiteral)
sentLiteralText, err := io.ReadAll(messagesClient1[0].GetBody(must(go_imap.ParseBodySectionName("BODY[TEXT]"))))
require.NoError(t, err)
sentLiteralStr := string(sentLiteralText)
literalMatches := sentLiteralStr == "Hello\r\n world from client2\r\n"
idIsDifferent := sentLiteralID != newLiteralID
externIDMatches := sentLiteralExternID == newLiteralExternID
return literalMatches && idIsDifferent && externIDMatches
}, 2*time.Second, time.Second)
})
}, server.WithMessageDedup())
}

View File

@ -399,6 +399,10 @@ func createNumMessages(ctx context.Context, t *testing.T, c *proton.Client, addr
} }
func createMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID, labelID string, messages ...[]byte) []string { func createMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID, labelID string, messages ...[]byte) []string {
return createMessagesWithFlags(ctx, t, c, addrID, labelID, 0, messages...)
}
func createMessagesWithFlags(ctx context.Context, t *testing.T, c *proton.Client, addrID, labelID string, flags proton.MessageFlag, messages ...[]byte) []string {
user, err := c.GetUser(ctx) user, err := c.GetUser(ctx)
require.NoError(t, err) require.NoError(t, err)
@ -417,6 +421,13 @@ func createMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID,
_, ok := addrKRs[addrID] _, ok := addrKRs[addrID]
require.True(t, ok) require.True(t, ok)
var msgFlags proton.MessageFlag
if flags == 0 {
msgFlags = proton.MessageFlagReceived
} else {
msgFlags = flags
}
str, err := c.ImportMessages( str, err := c.ImportMessages(
ctx, ctx,
addrKRs[addrID], addrKRs[addrID],
@ -427,7 +438,7 @@ func createMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID,
Metadata: proton.ImportMetadata{ Metadata: proton.ImportMetadata{
AddressID: addrID, AddressID: addrID,
LabelIDs: []string{labelID}, LabelIDs: []string{labelID},
Flags: proton.MessageFlagReceived, Flags: msgFlags,
}, },
Message: message, Message: message,
} }

View File

@ -21,6 +21,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"strconv"
"time" "time"
"github.com/ProtonMail/proton-bridge/v3/internal/safe" "github.com/ProtonMail/proton-bridge/v3/internal/safe"
@ -74,8 +75,9 @@ func (status *ConfigurationStatus) Save() error {
if err != nil { if err != nil {
return err return err
} }
enc := json.NewEncoder(f)
err = json.NewEncoder(f).Encode(status.Data) enc.SetIndent("", " ")
err = enc.Encode(status.Data)
if err := f.Close(); err != nil { if err := f.Close(); err != nil {
logrus.WithError(err).Error("Error while closing configstatus file.") logrus.WithError(err).Error("Error while closing configstatus file.")
} }
@ -197,3 +199,23 @@ func (data *ConfigurationStatusData) hasLinkClicked(pos uint) bool {
val := data.DataV1.ClickedLink & (1 << pos) val := data.DataV1.ClickedLink & (1 << pos)
return val > 0 return val > 0
} }
func (data *ConfigurationStatusData) clickedLinkToString() string {
var str = ""
var first = true
for i := 0; i < 64; i++ {
if data.hasLinkClicked(uint(i)) {
if !first {
str += ","
} else {
first = false
str += "["
}
str += strconv.Itoa(i)
}
}
if str != "" {
str += "]"
}
return str
}

View File

@ -17,16 +17,19 @@
package configstatus package configstatus
import "time" import (
"strconv"
"time"
)
type ConfigAbortValues struct { type ConfigAbortValues struct {
Duration int `json:"duration"` Duration int `json:"duration"`
} }
type ConfigAbortDimensions struct { type ConfigAbortDimensions struct {
ReportClick interface{} `json:"report_click"` ReportClick string `json:"report_click"`
ReportSent interface{} `json:"report_sent"` ReportSent string `json:"report_sent"`
ClickedLink uint64 `json:"clicked_link"` ClickedLink string `json:"clicked_link"`
} }
type ConfigAbortData struct { type ConfigAbortData struct {
@ -46,9 +49,9 @@ func (*ConfigAbortBuilder) New(data *ConfigurationStatusData) ConfigAbortData {
Duration: int(time.Since(data.DataV1.PendingSince).Minutes()), Duration: int(time.Since(data.DataV1.PendingSince).Minutes()),
}, },
Dimensions: ConfigSuccessDimensions{ Dimensions: ConfigSuccessDimensions{
ReportClick: data.DataV1.ReportClick, ReportClick: strconv.FormatBool(data.DataV1.ReportClick),
ReportSent: data.DataV1.ReportSent, ReportSent: strconv.FormatBool(data.DataV1.ReportSent),
ClickedLink: data.DataV1.ClickedLink, ClickedLink: data.clickedLinkToString(),
}, },
} }
} }

View File

@ -38,9 +38,9 @@ func TestConfigurationAbort_default(t *testing.T) {
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup) require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_abort", req.Event) require.Equal(t, "bridge_config_abort", req.Event)
require.Equal(t, 0, req.Values.Duration) require.Equal(t, 0, req.Values.Duration)
require.Equal(t, false, req.Dimensions.ReportClick) require.Equal(t, "false", req.Dimensions.ReportClick)
require.Equal(t, false, req.Dimensions.ReportSent) require.Equal(t, "false", req.Dimensions.ReportSent)
require.Equal(t, uint64(0), req.Dimensions.ClickedLink) require.Equal(t, "", req.Dimensions.ClickedLink)
} }
func TestConfigurationAbort_fed(t *testing.T) { func TestConfigurationAbort_fed(t *testing.T) {
@ -69,7 +69,7 @@ func TestConfigurationAbort_fed(t *testing.T) {
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup) require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_abort", req.Event) require.Equal(t, "bridge_config_abort", req.Event)
require.Equal(t, 10, req.Values.Duration) require.Equal(t, 10, req.Values.Duration)
require.Equal(t, true, req.Dimensions.ReportClick) require.Equal(t, "true", req.Dimensions.ReportClick)
require.Equal(t, false, req.Dimensions.ReportSent) require.Equal(t, "false", req.Dimensions.ReportSent)
require.Equal(t, uint64(42), req.Dimensions.ClickedLink) require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
} }

View File

@ -46,7 +46,7 @@ func (*ConfigProgressBuilder) New(data *ConfigurationStatusData) ConfigProgressD
func numberOfDay(now, prev time.Time) int { func numberOfDay(now, prev time.Time) int {
if now.IsZero() || prev.IsZero() { if now.IsZero() || prev.IsZero() {
return 0 return 1
} }
if now.Year() > prev.Year() { if now.Year() > prev.Year() {
if now.YearDay() > prev.YearDay() { if now.YearDay() > prev.YearDay() {

View File

@ -38,7 +38,7 @@ func TestConfigurationProgress_default(t *testing.T) {
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup) require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_progress", req.Event) require.Equal(t, "bridge_config_progress", req.Event)
require.Equal(t, 0, req.Values.NbDay) require.Equal(t, 0, req.Values.NbDay)
require.Equal(t, 0, req.Values.NbDaySinceLast) require.Equal(t, 1, req.Values.NbDaySinceLast)
} }
func TestConfigurationProgress_fed(t *testing.T) { func TestConfigurationProgress_fed(t *testing.T) {

View File

@ -18,6 +18,7 @@
package configstatus package configstatus
import ( import (
"strconv"
"time" "time"
) )
@ -26,11 +27,11 @@ type ConfigRecoveryValues struct {
} }
type ConfigRecoveryDimensions struct { type ConfigRecoveryDimensions struct {
Autoconf string `json:"autoconf"` Autoconf string `json:"autoconf"`
ReportClick interface{} `json:"report_click"` ReportClick string `json:"report_click"`
ReportSent interface{} `json:"report_sent"` ReportSent string `json:"report_sent"`
ClickedLink uint64 `json:"clicked_link"` ClickedLink string `json:"clicked_link"`
FailureDetails string `json:"failure_details"` FailureDetails string `json:"failure_details"`
} }
type ConfigRecoveryData struct { type ConfigRecoveryData struct {
@ -51,9 +52,9 @@ func (*ConfigRecoveryBuilder) New(data *ConfigurationStatusData) ConfigRecoveryD
}, },
Dimensions: ConfigRecoveryDimensions{ Dimensions: ConfigRecoveryDimensions{
Autoconf: data.DataV1.Autoconf, Autoconf: data.DataV1.Autoconf,
ReportClick: data.DataV1.ReportClick, ReportClick: strconv.FormatBool(data.DataV1.ReportClick),
ReportSent: data.DataV1.ReportSent, ReportSent: strconv.FormatBool(data.DataV1.ReportSent),
ClickedLink: data.DataV1.ClickedLink, ClickedLink: data.clickedLinkToString(),
FailureDetails: data.DataV1.FailureDetails, FailureDetails: data.DataV1.FailureDetails,
}, },
} }

View File

@ -39,9 +39,9 @@ func TestConfigurationRecovery_default(t *testing.T) {
require.Equal(t, "bridge_config_recovery", req.Event) require.Equal(t, "bridge_config_recovery", req.Event)
require.Equal(t, 0, req.Values.Duration) require.Equal(t, 0, req.Values.Duration)
require.Equal(t, "", req.Dimensions.Autoconf) require.Equal(t, "", req.Dimensions.Autoconf)
require.Equal(t, false, req.Dimensions.ReportClick) require.Equal(t, "false", req.Dimensions.ReportClick)
require.Equal(t, false, req.Dimensions.ReportSent) require.Equal(t, "false", req.Dimensions.ReportSent)
require.Equal(t, uint64(0), req.Dimensions.ClickedLink) require.Equal(t, "", req.Dimensions.ClickedLink)
require.Equal(t, "", req.Dimensions.FailureDetails) require.Equal(t, "", req.Dimensions.FailureDetails)
} }
@ -72,8 +72,8 @@ func TestConfigurationRecovery_fed(t *testing.T) {
require.Equal(t, "bridge_config_recovery", req.Event) require.Equal(t, "bridge_config_recovery", req.Event)
require.Equal(t, 10, req.Values.Duration) require.Equal(t, 10, req.Values.Duration)
require.Equal(t, "Mr TBird", req.Dimensions.Autoconf) require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
require.Equal(t, true, req.Dimensions.ReportClick) require.Equal(t, "true", req.Dimensions.ReportClick)
require.Equal(t, false, req.Dimensions.ReportSent) require.Equal(t, "false", req.Dimensions.ReportSent)
require.Equal(t, uint64(42), req.Dimensions.ClickedLink) require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
require.Equal(t, "Not an error", req.Dimensions.FailureDetails) require.Equal(t, "Not an error", req.Dimensions.FailureDetails)
} }

View File

@ -18,6 +18,7 @@
package configstatus package configstatus
import ( import (
"strconv"
"time" "time"
) )
@ -26,10 +27,10 @@ type ConfigSuccessValues struct {
} }
type ConfigSuccessDimensions struct { type ConfigSuccessDimensions struct {
Autoconf string `json:"autoconf"` Autoconf string `json:"autoconf"`
ReportClick interface{} `json:"report_click"` ReportClick string `json:"report_click"`
ReportSent interface{} `json:"report_sent"` ReportSent string `json:"report_sent"`
ClickedLink uint64 `json:"clicked_link"` ClickedLink string `json:"clicked_link"`
} }
type ConfigSuccessData struct { type ConfigSuccessData struct {
@ -50,9 +51,9 @@ func (*ConfigSuccessBuilder) New(data *ConfigurationStatusData) ConfigSuccessDat
}, },
Dimensions: ConfigSuccessDimensions{ Dimensions: ConfigSuccessDimensions{
Autoconf: data.DataV1.Autoconf, Autoconf: data.DataV1.Autoconf,
ReportClick: data.DataV1.ReportClick, ReportClick: strconv.FormatBool(data.DataV1.ReportClick),
ReportSent: data.DataV1.ReportSent, ReportSent: strconv.FormatBool(data.DataV1.ReportSent),
ClickedLink: data.DataV1.ClickedLink, ClickedLink: data.clickedLinkToString(),
}, },
} }
} }

View File

@ -39,9 +39,9 @@ func TestConfigurationSuccess_default(t *testing.T) {
require.Equal(t, "bridge_config_success", req.Event) require.Equal(t, "bridge_config_success", req.Event)
require.Equal(t, 0, req.Values.Duration) require.Equal(t, 0, req.Values.Duration)
require.Equal(t, "", req.Dimensions.Autoconf) require.Equal(t, "", req.Dimensions.Autoconf)
require.Equal(t, false, req.Dimensions.ReportClick) require.Equal(t, "false", req.Dimensions.ReportClick)
require.Equal(t, false, req.Dimensions.ReportSent) require.Equal(t, "false", req.Dimensions.ReportSent)
require.Equal(t, uint64(0), req.Dimensions.ClickedLink) require.Equal(t, "", req.Dimensions.ClickedLink)
} }
func TestConfigurationSuccess_fed(t *testing.T) { func TestConfigurationSuccess_fed(t *testing.T) {
@ -71,7 +71,7 @@ func TestConfigurationSuccess_fed(t *testing.T) {
require.Equal(t, "bridge_config_success", req.Event) require.Equal(t, "bridge_config_success", req.Event)
require.Equal(t, 10, req.Values.Duration) require.Equal(t, 10, req.Values.Duration)
require.Equal(t, "Mr TBird", req.Dimensions.Autoconf) require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
require.Equal(t, true, req.Dimensions.ReportClick) require.Equal(t, "true", req.Dimensions.ReportClick)
require.Equal(t, false, req.Dimensions.ReportSent) require.Equal(t, "false", req.Dimensions.ReportSent)
require.Equal(t, uint64(42), req.Dimensions.ClickedLink) require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink)
} }

View File

@ -124,10 +124,12 @@ func (user *User) SendConfigStatusProgress() {
if !user.configStatus.IsPending() { if !user.configStatus.IsPending() {
return return
} }
var builder configstatus.ConfigProgressBuilder var builder configstatus.ConfigProgressBuilder
progress := builder.New(user.configStatus.Data) progress := builder.New(user.configStatus.Data)
if progress.Values.NbDaySinceLast == 0 || progress.Values.NbDay == 0 { if progress.Values.NbDay == 0 {
return
}
if progress.Values.NbDaySinceLast == 0 {
return return
} }

View File

@ -514,9 +514,9 @@ 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, but we should // Draft update means to completely remove old message and upload the new data again, but we should
// only do this if the event is of type EventUpdate otherwise label switch operations will not work. // only do this if the event is of type EventUpdate otherwise label switch operations will not work.
if event.Message.IsDraft() && event.Action == proton.EventUpdate { if (event.Message.IsDraft() || (event.Message.Flags&proton.MessageFlagSent != 0)) && event.Action == proton.EventUpdate {
updates, err := user.handleUpdateDraftEvent( updates, err := user.handleUpdateDraftOrSentMessage(
logging.WithLogrusField(ctx, "action", "update draft"), logging.WithLogrusField(ctx, "action", "update draft or sent message"),
event, event,
) )
if err != nil { if err != nil {
@ -701,18 +701,19 @@ func (user *User) handleDeleteMessageEvent(_ context.Context, event proton.Messa
}, user.updateChLock) }, user.updateChLock)
} }
func (user *User) handleUpdateDraftEvent(ctx context.Context, event proton.MessageEvent) ([]imap.Update, error) { func (user *User) handleUpdateDraftOrSentMessage(ctx context.Context, event proton.MessageEvent) ([]imap.Update, error) {
return safe.RLockRetErr(func() ([]imap.Update, error) { return safe.RLockRetErr(func() ([]imap.Update, error) {
user.log.WithFields(logrus.Fields{ user.log.WithFields(logrus.Fields{
"messageID": event.ID, "messageID": event.ID,
"subject": logging.Sensitive(event.Message.Subject), "subject": logging.Sensitive(event.Message.Subject),
}).Info("Handling draft updated event") "isDraft": event.Message.IsDraft(),
}).Info("Handling draft or sent updated event")
full, err := user.client.GetFullMessage(ctx, event.Message.ID, newProtonAPIScheduler(user.panicHandler), proton.NewDefaultAttachmentAllocator()) full, err := user.client.GetFullMessage(ctx, event.Message.ID, newProtonAPIScheduler(user.panicHandler), proton.NewDefaultAttachmentAllocator())
if err != nil { if err != nil {
// If the message is not found, it means that it has been deleted before we could fetch it. // If the message is not found, it means that it has been deleted before we could fetch it.
if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Status == http.StatusUnprocessableEntity { if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Status == http.StatusUnprocessableEntity {
user.log.WithField("messageID", event.Message.ID).Warn("Cannot update draft: full message is missing on API") user.log.WithField("messageID", event.Message.ID).Warn("Cannot update message: full message is missing on API")
return nil, nil return nil, nil
} }