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 (
|
||||
"context"
|
||||
"errors"
|
||||
"regexp"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||
@ -147,6 +148,8 @@ func restoreMail(
|
||||
|
||||
msg = setMessageSVEPs(toMessage(msg))
|
||||
|
||||
setReplyTos(msg)
|
||||
|
||||
attachments := msg.GetAttachments()
|
||||
// Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized
|
||||
msg.SetAttachments([]models.Attachmentable{})
|
||||
@ -229,6 +232,38 @@ func setMessageSVEPs(msg models.Messageable) models.Messageable {
|
||||
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(
|
||||
ctx context.Context,
|
||||
userID, containerID string,
|
||||
@ -240,3 +275,24 @@ func (h mailRestoreHandler) GetItemsInContainerByCollisionKey(
|
||||
|
||||
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/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/tester"
|
||||
"github.com/alcionai/corso/src/internal/tester/its"
|
||||
@ -24,6 +25,127 @@ import (
|
||||
"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{}
|
||||
|
||||
type mailRestoreMock struct {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user