diff --git a/src/internal/connector/exchange/exchange_service_test.go b/src/internal/connector/exchange/exchange_service_test.go index c45c9ac7b..033a4be2e 100644 --- a/src/internal/connector/exchange/exchange_service_test.go +++ b/src/internal/connector/exchange/exchange_service_test.go @@ -248,6 +248,49 @@ func (suite *ExchangeServiceSuite) TestGraphQueryFunctions() { } } +// TestParseCalendarIDFromEvent verifies that parse function +// works on the current accepted reference format of +// additional data["calendar@odata.associationLink"] +func (suite *ExchangeServiceSuite) TestParseCalendarIDFromEvent() { + tests := []struct { + name string + input string + checkError assert.ErrorAssertionFunc + }{ + { + name: "Empty string", + input: "", + checkError: assert.Error, + }, + { + name: "Invalid string", + input: "https://github.com/whyNot/calendarNot Used", + checkError: assert.Error, + }, + { + name: "Missing calendarID not found", + input: "https://graph.microsoft.com/v1.0/users" + + "('invalid@onmicrosoft.com')/calendars(" + + "'')/$ref", + checkError: assert.Error, + }, + { + name: "Valid string", + input: "https://graph.microsoft.com/v1.0/users" + + "('valid@onmicrosoft.com')/calendars(" + + "'AAMkAGZmNjNlYjI3LWJlZWYtNGI4Mi04YjMyLTIxYThkNGQ4NmY1MwBGAAAAAA" + + "DCNgjhM9QmQYWNcI7hCpPrBwDSEBNbUIB9RL6ePDeF3FIYAAAAAAEGAADSEBNbUIB9RL6ePDeF3FIYAAAZkDq1AAA=')/$ref", + checkError: assert.NoError, + }, + } + for _, test := range tests { + suite.T().Run(test.name, func(t *testing.T) { + _, err := parseCalendarIDFromEvent(test.input) + test.checkError(t, err) + }) + } +} + // TestIterativeFunctions verifies that GraphQuery to Iterate // functions are valid for current versioning of msgraph-go-sdk func (suite *ExchangeServiceSuite) TestIterativeFunctions() { diff --git a/src/internal/connector/exchange/service_functions.go b/src/internal/connector/exchange/service_functions.go index bfa83aa00..c18322673 100644 --- a/src/internal/connector/exchange/service_functions.go +++ b/src/internal/connector/exchange/service_functions.go @@ -177,6 +177,28 @@ func GetMailFolderID(service graph.Service, folderName, user string) (*string, e return folderID, errs } +// parseCalendarIDFromEvent returns the M365 ID for a calendar +// @param reference: string from additionalData map of an event +// References should follow the form `https://... calendars('ID')/$ref` +// If the reference does not follow form an error is returned +func parseCalendarIDFromEvent(reference string) (string, error) { + stringArray := strings.Split(reference, "calendars('") + if len(stringArray) < 2 { + return "", errors.New("calendarID not found") + } + temp := stringArray[1] + stringArray = strings.Split(temp, "')/$ref") + if len(stringArray) < 2 { + return "", errors.New("calendarID not found") + } + calendarID := stringArray[0] + if len(calendarID) == 0 { + return "", errors.New("calendarID empty") + } + + return calendarID, nil +} + // SetupExchangeCollectionVars is a helper function returns a sets // Exchange.Type specific functions based on scope func SetupExchangeCollectionVars(scope selectors.ExchangeScope) ( diff --git a/src/internal/connector/exchange/service_query.go b/src/internal/connector/exchange/service_query.go index 173049692..abccda4c2 100644 --- a/src/internal/connector/exchange/service_query.go +++ b/src/internal/connector/exchange/service_query.go @@ -147,9 +147,10 @@ func GetAllUsersForTenant(gs graph.Service, user string) (absser.Parsable, error } // GetAllEvents for User. Default returns EventResponseCollection for future events. -// of the time that the call was made. There a +// of the time that the call was made. 'calendar' option must be present to gain +// access to additional data map in future calls. func GetAllEventsForUser(gs graph.Service, user string) (absser.Parsable, error) { - options, err := optionsForEvents([]string{"id"}) + options, err := optionsForEvents([]string{"id", "calendar"}) if err != nil { return nil, err } @@ -236,6 +237,10 @@ func IterateSelectAllMessagesForCollections( } } +// IterateSelectAllEventsForCollections +// utility function for iterating through events +// and storing events in collections based on +// the calendarID which originates from M365. func IterateSelectAllEventsForCollections( tenant string, scope selectors.ExchangeScope, @@ -245,26 +250,8 @@ func IterateSelectAllEventsForCollections( collections map[string]*Collection, statusCh chan<- *support.ConnectorOperationStatus, ) func(any) bool { - var isDirectorySet bool return func(eventItem any) bool { - eventFolder := "Events" user := scope.Get(selectors.ExchangeUser)[0] - if !isDirectorySet { - service, err := createService(credentials, failFast) - if err != nil { - errs = support.WrapAndAppend(user, err, errs) - return true - } - edc := NewCollection( - user, - []string{tenant, user, eventsCategory, eventFolder}, - events, - service, - statusCh, - ) - collections[eventFolder] = &edc - isDirectorySet = true - } event, ok := eventItem.(models.Eventable) if !ok { @@ -276,7 +263,55 @@ func IterateSelectAllEventsForCollections( return true } - collections[eventFolder].AddJob(*event.GetId()) + adtl := event.GetAdditionalData() + value, ok := adtl["calendar@odata.associationLink"] + if !ok { + errs = support.WrapAndAppend( + user, + fmt.Errorf("%s: does not support calendar look up", *event.GetId()), + errs, + ) + return true + } + link, ok := value.(*string) + if !ok || link == nil { + errs = support.WrapAndAppend( + user, + fmt.Errorf("%s: unable to obtain calendar event data", *event.GetId()), + errs, + ) + return true + } + // calendars and events are not easily correlated + // helper function retrieves calendarID from url + directory, err := parseCalendarIDFromEvent(*link) + if err != nil { + errs = support.WrapAndAppend( + user, + errors.Wrap(err, *event.GetId()), + errs, + ) + return true + } + + if _, ok := collections[directory]; !ok { + + service, err := createService(credentials, failFast) + if err != nil { + errs = support.WrapAndAppend(user, err, errs) + return true + } + edc := NewCollection( + user, + []string{tenant, user, eventsCategory, directory}, + events, + service, + statusCh, + ) + collections[directory] = &edc + } + + collections[directory].AddJob(*event.GetId()) return true } }