diff --git a/src/pkg/services/m365/custom/drive_item.go b/src/pkg/services/m365/custom/drive_item.go index ed9e18eed..cea91f5f3 100644 --- a/src/pkg/services/m365/custom/drive_item.go +++ b/src/pkg/services/m365/custom/drive_item.go @@ -23,13 +23,15 @@ type LiteDriveItemable interface { GetFolder() interface{} GetPackageEscaped() interface{} GetShared() interface{} - GetMalware() interface{} + GetMalware() malwareable GetDeleted() interface{} GetRoot() interface{} GetFile() fileItemable - GetParentReference() parentReferenceable - SetParentReference(parentReferenceable) - GetCreatedBy() itemIdentitySetable + GetParentReference() itemReferenceable + SetParentReference(itemReferenceable) + GetCreatedBy() identitySetable + GetCreatedByUser() userable + GetLastModifiedByUser() userable GetCreatedDateTime() *time.Time GetLastModifiedDateTime() *time.Time GetAdditionalData() map[string]interface{} @@ -44,12 +46,14 @@ type driveItem struct { folder interface{} pkg interface{} shared interface{} - malware interface{} deleted interface{} root interface{} + malware malwareable file fileItemable - parentRef parentReferenceable - createdBy itemIdentitySetable + parentRef itemReferenceable + createdBy identitySetable + createdByUser userable + lastModifiedByUser userable createdDateTime time.Time lastModifiedDateTime time.Time additionalData map[string]interface{} @@ -79,7 +83,7 @@ func (c *driveItem) GetShared() interface{} { return c.shared } -func (c *driveItem) GetMalware() interface{} { +func (c *driveItem) GetMalware() malwareable { return c.malware } @@ -95,19 +99,27 @@ func (c *driveItem) GetFile() fileItemable { return c.file } -func (c *driveItem) GetParentReference() parentReferenceable { +func (c *driveItem) GetParentReference() itemReferenceable { return c.parentRef } // TODO(pandeyabs): Should we only support GETs? -func (c *driveItem) SetParentReference(parent parentReferenceable) { +func (c *driveItem) SetParentReference(parent itemReferenceable) { c.parentRef = parent } -func (c *driveItem) GetCreatedBy() itemIdentitySetable { +func (c *driveItem) GetCreatedBy() identitySetable { return c.createdBy } +func (c *driveItem) GetCreatedByUser() userable { + return c.createdByUser +} + +func (c *driveItem) GetLastModifiedByUser() userable { + return c.lastModifiedByUser +} + func (c *driveItem) GetCreatedDateTime() *time.Time { return &c.createdDateTime } @@ -121,25 +133,41 @@ func (c *driveItem) GetAdditionalData() map[string]interface{} { } type ( + malwareable interface { + GetDescription() *string + } fileItemable interface { GetMimeType() *string } - parentReferenceable interface { + itemReferenceable interface { GetPath() *string GetId() *string GetName() *string GetDriveId() *string } - itemIdentitySetable interface { - GetUser() itemUserable + identitySetable interface { + GetUser() identityable } - itemUserable interface { + identityable interface { GetAdditionalData() map[string]interface{} } + userable interface { + GetId() *string + } ) // Concrete implementations +var _ malwareable = &malware{} + +type malware struct { + description string +} + +func (m *malware) GetDescription() *string { + return &m.description +} + var _ fileItemable = &fileItem{} type fileItem struct { @@ -150,7 +178,7 @@ func (f *fileItem) GetMimeType() *string { return &f.mimeType } -var _ parentReferenceable = &parentRef{} +var _ itemReferenceable = &parentRef{} type parentRef struct { path string @@ -175,26 +203,36 @@ func (pr *parentRef) GetDriveId() *string { return &pr.driveID } -var _ itemIdentitySetable = &itemIdentitySet{} +var _ identitySetable = &identitySet{} -type itemIdentitySet struct { - user itemUserable +type identitySet struct { + identity identityable } -func (iis *itemIdentitySet) GetUser() itemUserable { - return iis.user +func (iis *identitySet) GetUser() identityable { + return iis.identity } -var _ itemUserable = &itemUser{} +var _ identityable = &identity{} -type itemUser struct { +type identity struct { additionalData map[string]interface{} } -func (iu *itemUser) GetAdditionalData() map[string]interface{} { +func (iu *identity) GetAdditionalData() map[string]interface{} { return iu.additionalData } +var _ userable = &user{} + +type user struct { + id string +} + +func (u *user) GetId() *string { + return &u.id +} + // TODO(pandeyabs): This is duplicated from collection/drive package. // Move to common pkg var downloadURLKeys = []string{ @@ -207,7 +245,7 @@ func ToLiteDriveItemable(item models.DriveItemable) LiteDriveItemable { return nil } - cdi := &driveItem{ + di := &driveItem{ id: strings.Clone(ptr.Val(item.GetId())), name: strings.Clone(ptr.Val(item.GetName())), size: ptr.Val(item.GetSize()), @@ -216,17 +254,17 @@ func ToLiteDriveItemable(item models.DriveItemable) LiteDriveItemable { } if item.GetFolder() != nil { - cdi.folder = &struct{}{} + di.folder = &struct{}{} } else if item.GetFile() != nil { - cdi.file = &fileItem{ + di.file = &fileItem{ mimeType: strings.Clone(ptr.Val(item.GetFile().GetMimeType())), } } else if item.GetPackageEscaped() != nil { - cdi.pkg = &struct{}{} + di.pkg = &struct{}{} } if item.GetParentReference() != nil { - cdi.parentRef = &parentRef{ + di.parentRef = &parentRef{ id: strings.Clone(ptr.Val(item.GetParentReference().GetId())), path: strings.Clone(ptr.Val(item.GetParentReference().GetPath())), name: strings.Clone(ptr.Val(item.GetParentReference().GetName())), @@ -235,59 +273,67 @@ func ToLiteDriveItemable(item models.DriveItemable) LiteDriveItemable { } if item.GetShared() != nil { - cdi.shared = &struct{}{} + di.shared = &struct{}{} } if item.GetMalware() != nil { - cdi.malware = &struct{}{} + di.malware = &malware{ + description: strings.Clone(ptr.Val(item.GetMalware().GetDescription())), + } } if item.GetDeleted() != nil { - cdi.deleted = &struct{}{} + di.deleted = &struct{}{} } if item.GetRoot() != nil { - cdi.root = &struct{}{} + di.root = &struct{}{} } if item.GetCreatedBy() != nil && item.GetCreatedBy().GetUser() != nil { additionalData := item.GetCreatedBy().GetUser().GetAdditionalData() ad := make(map[string]interface{}) - var s string - if v, err := str.AnyValueToString("email", additionalData); err == nil { - s = strings.Clone(v) - ad["email"] = &s - } else if v, err := str.AnyValueToString("userPrincipalName", additionalData); err == nil { - s = strings.Clone(v) - ad["displayName"] = &s + email := strings.Clone(v) + ad["email"] = &email } - cdi.createdBy = &itemIdentitySet{ - user: &itemUser{ + if v, err := str.AnyValueToString("displayName", additionalData); err == nil { + displayName := strings.Clone(v) + ad["displayName"] = &displayName + } + + di.createdBy = &identitySet{ + identity: &identity{ additionalData: ad, }, } } + if item.GetCreatedByUser() != nil { + di.createdByUser = &user{ + id: strings.Clone(ptr.Val(item.GetCreatedByUser().GetId())), + } + } + + if item.GetLastModifiedByUser() != nil { + di.lastModifiedByUser = &user{ + id: strings.Clone(ptr.Val(item.GetLastModifiedByUser().GetId())), + } + } + // We only use the download URL from additional data - var downloadURL, mapKey string + ad := make(map[string]interface{}) for _, key := range downloadURLKeys { if v, err := str.AnyValueToString(key, item.GetAdditionalData()); err == nil { - downloadURL = strings.Clone(v) - mapKey = key - - break + downloadURL := strings.Clone(v) + ad[key] = &downloadURL } } - if len(downloadURL) != 0 { - cdi.additionalData = map[string]interface{}{ - mapKey: &downloadURL, - } - } + di.additionalData = ad - return cdi + return di } diff --git a/src/pkg/services/m365/custom/drive_item_test.go b/src/pkg/services/m365/custom/drive_item_test.go index 51d4c7448..fe131e86b 100644 --- a/src/pkg/services/m365/custom/drive_item_test.go +++ b/src/pkg/services/m365/custom/drive_item_test.go @@ -77,6 +77,8 @@ func (suite *driveUnitSuite) TestToLiteDriveItemable() { require.Nil(t, got.GetDeleted()) require.Nil(t, got.GetRoot()) require.Nil(t, got.GetCreatedBy()) + require.Nil(t, got.GetCreatedByUser()) + require.Nil(t, got.GetLastModifiedByUser()) require.Nil(t, got.GetParentReference()) assert.Equal(t, len(got.GetAdditionalData()), 0) }, @@ -85,9 +87,9 @@ func (suite *driveUnitSuite) TestToLiteDriveItemable() { name: "ID, name, size, created, modified", itemFunc: func() models.DriveItemable { name := "itemName" - size := int64(123) - created := time.Now().Add(-time.Second).Truncate(time.Nanosecond) - modified := time.Now().Truncate(time.Nanosecond) + size := int64(6) + created := time.Now().Add(-time.Second) + modified := time.Now() di := models.NewDriveItem() @@ -167,9 +169,14 @@ func (suite *driveUnitSuite) TestToLiteDriveItemable() { di.SetFile(models.NewFile()) di.GetFile().SetMimeType(&mime) - // additional data + // Intentionally set different URLs for the two keys to test + // for correctness. It's unlikely that a) both will be set, + // b) URLs will be different, but it's not the responsibility + // of function being tested, which is simply copying over key, val + // pairs useful to callers. di.SetAdditionalData(map[string]interface{}{ "@microsoft.graph.downloadUrl": "downloadURL", + "@content.downloadUrl": "contentURL", }) return di @@ -201,8 +208,23 @@ func (suite *driveUnitSuite) TestToLiteDriveItemable() { assert.Equal( t, - urlExpected, - urlGot) + urlGot, + urlExpected) + + contentExpected, err := str.AnyValueToString( + "@content.downloadUrl", + expected.GetAdditionalData()) + require.NoError(t, err) + + contentGot, err := str.AnyValueToString( + "@content.downloadUrl", + got.GetAdditionalData()) + require.NoError(t, err) + + assert.Equal( + t, + contentGot, + contentExpected) }, }, @@ -213,7 +235,6 @@ func (suite *driveUnitSuite) TestToLiteDriveItemable() { di.SetId(&id) di.SetShared(models.NewShared()) - di.SetFile(models.NewFile()) return di }, @@ -223,7 +244,6 @@ func (suite *driveUnitSuite) TestToLiteDriveItemable() { got LiteDriveItemable, ) { require.NotNil(t, got.GetShared()) - require.NotNil(t, got.GetFile()) assert.Equal(t, ptr.Val(got.GetId()), ptr.Val(expected.GetId())) }, }, @@ -232,9 +252,12 @@ func (suite *driveUnitSuite) TestToLiteDriveItemable() { itemFunc: func() models.DriveItemable { di := models.NewDriveItem() + mw := models.NewMalware() + desc := "malware description" + mw.SetDescription(&desc) + di.SetId(&id) - di.SetMalware(models.NewMalware()) - di.SetFile(models.NewFile()) + di.SetMalware(mw) return di }, @@ -244,7 +267,11 @@ func (suite *driveUnitSuite) TestToLiteDriveItemable() { got LiteDriveItemable, ) { require.NotNil(t, got.GetMalware()) - require.NotNil(t, got.GetFile()) + assert.Equal( + t, + ptr.Val(expected.GetMalware().GetDescription()), + ptr.Val(got.GetMalware().GetDescription())) + assert.Equal(t, ptr.Val(got.GetId()), ptr.Val(expected.GetId())) }, }, @@ -255,7 +282,6 @@ func (suite *driveUnitSuite) TestToLiteDriveItemable() { di.SetId(&id) di.SetDeleted(models.NewDeleted()) - di.SetFile(models.NewFile()) return di }, @@ -265,7 +291,6 @@ func (suite *driveUnitSuite) TestToLiteDriveItemable() { got LiteDriveItemable, ) { require.NotNil(t, got.GetDeleted()) - require.NotNil(t, got.GetFile()) assert.Equal(t, ptr.Val(got.GetId()), ptr.Val(expected.GetId())) }, }, @@ -343,7 +368,8 @@ func (suite *driveUnitSuite) TestToLiteDriveItemable() { createdBy.SetUser(models.NewUser()) createdBy.GetUser().SetAdditionalData(map[string]interface{}{ - "email": "email@me", + "email": "email@user", + "displayName": "username", }) di := models.NewDriveItem() @@ -370,7 +396,55 @@ func (suite *driveUnitSuite) TestToLiteDriveItemable() { got.GetCreatedBy().GetUser().GetAdditionalData()) require.NoError(t, err) - assert.Equal(t, emailExpected, emailGot) + assert.Equal(t, emailGot, emailExpected) + + displayNameExpected, err := str.AnyValueToString( + "displayName", + expected.GetCreatedBy().GetUser().GetAdditionalData()) + require.NoError(t, err) + + displayNameGot, err := str.AnyValueToString( + "displayName", + got.GetCreatedBy().GetUser().GetAdditionalData()) + require.NoError(t, err) + + assert.Equal(t, displayNameGot, displayNameExpected) + }, + }, + { + name: "Created & last modified by users", + itemFunc: func() models.DriveItemable { + createdByUser := models.NewUser() + uid := "creatorUserID" + createdByUser.SetId(&uid) + + lastModifiedByUser := models.NewUser() + luid := "lastModifierUserID" + lastModifiedByUser.SetId(&luid) + + di := models.NewDriveItem() + + di.SetId(&id) + di.SetCreatedByUser(createdByUser) + di.SetLastModifiedByUser(lastModifiedByUser) + + return di + }, + validateFunc: func( + t *testing.T, + expected models.DriveItemable, + got LiteDriveItemable, + ) { + require.NotNil(t, got.GetCreatedByUser()) + require.NotNil(t, got.GetLastModifiedByUser()) + assert.Equal( + t, + ptr.Val(got.GetCreatedByUser().GetId()), + ptr.Val(expected.GetCreatedByUser().GetId())) + assert.Equal( + t, + ptr.Val(got.GetLastModifiedByUser().GetId()), + ptr.Val(expected.GetLastModifiedByUser().GetId())) }, }, }