sanitizes replyTo emailAddresses (#5221)
sanitizes replyTo emailAddresses based on: - valid email address format - valid DN format #### Does this PR need a docs update or release note? - [ ] ✅ Yes, it's included - [x] 🕐 Yes, but in a later PR - [ ] ⛔ No #### Type of change <!--- Please check the type of change your PR introduces: ---> - [ ] 🌻 Feature - [x] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) INC-43 #### Test Plan <!-- How will this be tested prior to merging.--> - [x] 💪 Manual - [x] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
28aba60cc5
commit
4b56754546
@ -3,6 +3,7 @@ package exchange
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
@ -147,6 +148,8 @@ func restoreMail(
|
|||||||
|
|
||||||
msg = setMessageSVEPs(toMessage(msg))
|
msg = setMessageSVEPs(toMessage(msg))
|
||||||
|
|
||||||
|
setReplyTos(msg)
|
||||||
|
|
||||||
attachments := msg.GetAttachments()
|
attachments := msg.GetAttachments()
|
||||||
// Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized
|
// Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized
|
||||||
msg.SetAttachments([]models.Attachmentable{})
|
msg.SetAttachments([]models.Attachmentable{})
|
||||||
@ -229,6 +232,38 @@ func setMessageSVEPs(msg models.Messageable) models.Messageable {
|
|||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setReplyTos(msg models.Messageable) {
|
||||||
|
var (
|
||||||
|
replyTos = msg.GetReplyTo()
|
||||||
|
emailAddress models.EmailAddressable
|
||||||
|
name, address string
|
||||||
|
sanitizedReplyTos = make([]models.Recipientable, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(replyTos) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, replyTo := range replyTos {
|
||||||
|
emailAddress = replyTo.GetEmailAddress()
|
||||||
|
address = ptr.Val(emailAddress.GetAddress())
|
||||||
|
name = ptr.Val(emailAddress.GetName())
|
||||||
|
|
||||||
|
if isValidEmail(address) || isValidDN(address) {
|
||||||
|
newEmailAddress := models.NewEmailAddress()
|
||||||
|
newEmailAddress.SetAddress(ptr.To(address))
|
||||||
|
newEmailAddress.SetName(ptr.To(name))
|
||||||
|
|
||||||
|
sanitizedReplyTo := models.NewRecipient()
|
||||||
|
sanitizedReplyTo.SetEmailAddress(newEmailAddress)
|
||||||
|
|
||||||
|
sanitizedReplyTos = append(sanitizedReplyTos, sanitizedReplyTo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.SetReplyTo(sanitizedReplyTos)
|
||||||
|
}
|
||||||
|
|
||||||
func (h mailRestoreHandler) GetItemsInContainerByCollisionKey(
|
func (h mailRestoreHandler) GetItemsInContainerByCollisionKey(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID, containerID string,
|
userID, containerID string,
|
||||||
@ -240,3 +275,24 @@ func (h mailRestoreHandler) GetItemsInContainerByCollisionKey(
|
|||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [TODO]relocate to a common place
|
||||||
|
func isValidEmail(email string) bool {
|
||||||
|
emailRegex := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
|
||||||
|
r := regexp.MustCompile(emailRegex)
|
||||||
|
|
||||||
|
return r.MatchString(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidDN check if given string's format matches that of a MSFT Distinguished Name
|
||||||
|
// This regular expression matches strings that start with /o=,
|
||||||
|
// followed by any characters except /,
|
||||||
|
// then /ou=, followed by any characters except /,
|
||||||
|
// then /cn=, followed by any characters except /,
|
||||||
|
// then /cn= followed by a 32-character hexadecimal string followed by - and any additional characters.
|
||||||
|
func isValidDN(dn string) bool {
|
||||||
|
dnRegex := `^/o=[^/]+/ou=[^/]+/cn=[^/]+/cn=[a-fA-F0-9]{32}-[a-zA-Z0-9-]+$`
|
||||||
|
r := regexp.MustCompile(dnRegex)
|
||||||
|
|
||||||
|
return r.MatchString(dn)
|
||||||
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
|
"github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/internal/tester/its"
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
@ -24,6 +25,127 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
const TestDN = "/o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=4eca0d46a2324036b0b326dc58cfc802-user"
|
||||||
|
|
||||||
|
type RestoreMailUnitSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRestoreMailUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &RestoreMailUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RestoreMailUnitSuite) TestIsValidEmail() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
email string
|
||||||
|
check assert.BoolAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid email",
|
||||||
|
email: "foo@bar.com",
|
||||||
|
check: assert.True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid email, missing domain",
|
||||||
|
email: "foo.com",
|
||||||
|
check: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid email, random uuid",
|
||||||
|
email: "12345678-abcd-90ef-88f8-2d95ef12fb66",
|
||||||
|
check: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty email",
|
||||||
|
email: "",
|
||||||
|
check: assert.False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
result := isValidEmail(test.email)
|
||||||
|
test.check(t, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RestoreMailUnitSuite) TestIsValidDN() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
dn string
|
||||||
|
check assert.BoolAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid DN",
|
||||||
|
dn: TestDN,
|
||||||
|
check: assert.True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid DN",
|
||||||
|
dn: "random string",
|
||||||
|
check: assert.False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
result := isValidDN(test.dn)
|
||||||
|
test.check(t, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RestoreMailUnitSuite) TestSetReplyTos() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
replyTos := make([]models.Recipientable, 0)
|
||||||
|
|
||||||
|
emailAddresses := map[string]string{
|
||||||
|
"foo.bar": "foo@bar.com",
|
||||||
|
"foo.com": "foo.com",
|
||||||
|
"empty": "",
|
||||||
|
"dn": TestDN,
|
||||||
|
}
|
||||||
|
|
||||||
|
validEmailAddresses := map[string]string{
|
||||||
|
"foo.bar": "foo@bar.com",
|
||||||
|
"dn": TestDN,
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range emailAddresses {
|
||||||
|
emailAddress := models.NewEmailAddress()
|
||||||
|
emailAddress.SetAddress(ptr.To(v))
|
||||||
|
emailAddress.SetName(ptr.To(k))
|
||||||
|
|
||||||
|
replyTo := models.NewRecipient()
|
||||||
|
replyTo.SetEmailAddress(emailAddress)
|
||||||
|
|
||||||
|
replyTos = append(replyTos, replyTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
mailMessage := models.NewMessage()
|
||||||
|
mailMessage.SetReplyTo(replyTos)
|
||||||
|
|
||||||
|
setReplyTos(mailMessage)
|
||||||
|
|
||||||
|
sanitizedReplyTos := mailMessage.GetReplyTo()
|
||||||
|
require.Len(t, sanitizedReplyTos, len(validEmailAddresses))
|
||||||
|
|
||||||
|
for _, sanitizedReplyTo := range sanitizedReplyTos {
|
||||||
|
emailAddress := sanitizedReplyTo.GetEmailAddress()
|
||||||
|
|
||||||
|
assert.Contains(t, validEmailAddresses, ptr.Val(emailAddress.GetName()))
|
||||||
|
assert.Equal(t, validEmailAddresses[ptr.Val(emailAddress.GetName())], ptr.Val(emailAddress.GetAddress()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var _ mailRestorer = &mailRestoreMock{}
|
var _ mailRestorer = &mailRestoreMock{}
|
||||||
|
|
||||||
type mailRestoreMock struct {
|
type mailRestoreMock struct {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user