From b3a1de89bb26302940f4d324c39fb1c956abdcfe Mon Sep 17 00:00:00 2001 From: Danny Date: Fri, 10 Feb 2023 09:23:18 -0500 Subject: [PATCH] GC: Item attachment contact support (#2465) ## Description The logic for sanitizing Contactable data for restoration of `ItemAttachable.Contact` types. Contact `Item.Attachment`s required the removal of: - `odata.Context` - `ETag` - `ParentFolder` Otherwise, the following error occurs on POST. ```bash UnableToDeserializePostBody were unable to deserialize ``` ## Does this PR need a docs update or release note? - [x] :no_entry: No ## Type of change - [x] :sunflower: Feature - [x] :bug: Bugfix ## Issue(s) * closes #2426 ## Test Plan - [x] :zap: Unit test --- src/internal/common/ptr/pointer.go | 14 +++++++++ src/internal/connector/exchange/attachment.go | 15 ++++------ .../connector/exchange/restore_test.go | 15 ++++++++++ .../mockconnector/mock_data_message.go | 30 ++++++++++++++++++- .../connector/support/m365Transform.go | 23 ++++++++++++-- 5 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 src/internal/common/ptr/pointer.go diff --git a/src/internal/common/ptr/pointer.go b/src/internal/common/ptr/pointer.go new file mode 100644 index 000000000..68d15b109 --- /dev/null +++ b/src/internal/common/ptr/pointer.go @@ -0,0 +1,14 @@ +package ptr + +// Val helper method for unwrapping strings +// Microsoft Graph saves many variables as string pointers. +// Function will safely check if the point is nil prior to +// dereferencing the pointer. If the pointer is nil, +// an empty string is returned. +func Val(ptr *string) string { + if ptr == nil { + return "" + } + + return *ptr +} diff --git a/src/internal/connector/exchange/attachment.go b/src/internal/connector/exchange/attachment.go index 075ab09a6..ed8828930 100644 --- a/src/internal/connector/exchange/attachment.go +++ b/src/internal/connector/exchange/attachment.go @@ -8,6 +8,7 @@ import ( "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/pkg/errors" + "github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/connector/uploadsession" "github.com/alcionai/corso/src/pkg/logger" @@ -63,19 +64,16 @@ func uploadAttachment( attachment, err = support.ToItemAttachment(attachment) if err != nil { - name := "" - if prev.GetName() != nil { - name = *prev.GetName() - } + name := ptr.Val(prev.GetName()) + msg := "item attachment restore not supported for this type. skipping upload." // TODO: (rkeepers) Update to support PII protection - msg := "item attachment restore not supported for this type. skipping upload." logger.Ctx(ctx).Infow(msg, "err", err, "attachment_name", name, "attachment_type", attachmentType, "internal_item_type", getItemAttachmentItemType(prev), - "attachment_id", *prev.GetId(), + "attachment_id", ptr.Val(prev.GetId()), ) return nil @@ -129,9 +127,6 @@ func getItemAttachmentItemType(query models.Attachmentable) string { } item := attachment.GetItem() - if item.GetOdataType() == nil { - return empty - } - return *item.GetOdataType() + return ptr.Val(item.GetOdataType()) } diff --git a/src/internal/connector/exchange/restore_test.go b/src/internal/connector/exchange/restore_test.go index e6db75129..ad0c6b192 100644 --- a/src/internal/connector/exchange/restore_test.go +++ b/src/internal/connector/exchange/restore_test.go @@ -230,6 +230,21 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { return *folder.GetId() }, }, + { + name: "Test Mail: Item Attachment_Contact", + bytes: mockconnector.GetMockMessageWithNestedItemAttachmentContact(t, + mockconnector.GetMockContactBytes("Victor"), + "Contact Item Attachment", + ), + category: path.EmailCategory, + destination: func(t *testing.T, ctx context.Context) string { + folderName := "ItemMailAttachment_Contact " + common.FormatSimpleDateTime(now) + folder, err := suite.ac.Mail().CreateMailFolder(ctx, userID, folderName) + require.NoError(t, err) + + return *folder.GetId() + }, + }, { // Restore will upload the Message without uploading the attachment name: "Test Mail: Item Attachment_NestedEvent", bytes: mockconnector.GetMockMessageWithNestedItemAttachmentEvent("Nested Item Attachment"), diff --git a/src/internal/connector/mockconnector/mock_data_message.go b/src/internal/connector/mockconnector/mock_data_message.go index 50ff3345c..9f697b80a 100644 --- a/src/internal/connector/mockconnector/mock_data_message.go +++ b/src/internal/connector/mockconnector/mock_data_message.go @@ -5,6 +5,7 @@ import ( "fmt" "testing" + absser "github.com/microsoft/kiota-abstractions-go/serialization" js "github.com/microsoft/kiota-serialization-json-go" "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/pkg/errors" @@ -706,8 +707,35 @@ func GetMockMessageWithNestedItemAttachmentMail(t *testing.T, nested []byte, sub iaNode.SetItem(nestedMessage) message.SetAttachments([]models.Attachmentable{iaNode}) + return serialize(t, message) +} + +func GetMockMessageWithNestedItemAttachmentContact(t *testing.T, nested []byte, subject string) []byte { + base := GetMockMessageBytes(subject) + message, err := hydrateMessage(base) + require.NoError(t, err) + + parseNode, err := js.NewJsonParseNodeFactory().GetRootParseNode("application/json", nested) + require.NoError(t, err) + + anObject, err := parseNode.GetObjectValue(models.CreateContactFromDiscriminatorValue) + require.NoError(t, err) + + contact := anObject.(models.Contactable) + internalName := "Nested Contact" + iaNode := models.NewItemAttachment() + attachmentSize := int32(len(nested)) + iaNode.SetSize(&attachmentSize) + iaNode.SetName(&internalName) + iaNode.SetItem(contact) + message.SetAttachments([]models.Attachmentable{iaNode}) + + return serialize(t, message) +} + +func serialize(t *testing.T, item absser.Parsable) []byte { wtr := js.NewJsonSerializationWriter() - err = wtr.WriteObjectValue("", message) + err := wtr.WriteObjectValue("", item) require.NoError(t, err) byteArray, err := wtr.GetSerializedContent() diff --git a/src/internal/connector/support/m365Transform.go b/src/internal/connector/support/m365Transform.go index 4f8227a29..bcbdac898 100644 --- a/src/internal/connector/support/m365Transform.go +++ b/src/internal/connector/support/m365Transform.go @@ -306,9 +306,10 @@ func cloneColumnDefinitionable(orig models.ColumnDefinitionable) models.ColumnDe // //nolint:lll const ( - itemAttachment = "#microsoft.graph.itemAttachment" - eventItemType = "#microsoft.graph.event" - mailItemType = "#microsoft.graph.message" + itemAttachment = "#microsoft.graph.itemAttachment" + eventItemType = "#microsoft.graph.event" + mailItemType = "#microsoft.graph.message" + contactItemType = "#microsoft.graph.contact" ) // ToItemAttachment transforms internal item, OutlookItemables, into @@ -323,6 +324,13 @@ func ToItemAttachment(orig models.Attachmentable) (models.Attachmentable, error) itemType := item.GetOdataType() switch *itemType { + case contactItemType: + contact := item.(models.Contactable) + revised := sanitizeContact(contact) + + transform.SetItem(revised) + + return transform, nil case eventItemType: event := item.(models.Eventable) @@ -372,6 +380,15 @@ func ToItemAttachment(orig models.Attachmentable) (models.Attachmentable, error) // return attachments, nil // } +// sanitizeContact removes fields which prevent a Contact from +// being uploaded as an attachment. +func sanitizeContact(orig models.Contactable) models.Contactable { + orig.SetParentFolderId(nil) + orig.SetAdditionalData(nil) + + return orig +} + // sanitizeEvent transfers data into event object and // removes unique IDs from the M365 object func sanitizeEvent(orig models.Eventable) (models.Eventable, error) {