From e19ee200489d955ab8f3e64c431607525544430d Mon Sep 17 00:00:00 2001 From: Kush Rana Date: Tue, 26 Dec 2023 11:46:08 +0530 Subject: [PATCH] add and update unit tests --- x-pack/filebeat/input/salesforce/config.go | 1 - .../input/salesforce/config_auth_test.go | 2 +- x-pack/filebeat/input/salesforce/input.go | 4 +- .../filebeat/input/salesforce/input_test.go | 549 ++++++++++++++++-- x-pack/filebeat/input/salesforce/state.go | 4 +- 5 files changed, 491 insertions(+), 69 deletions(-) diff --git a/x-pack/filebeat/input/salesforce/config.go b/x-pack/filebeat/input/salesforce/config.go index 457ad5f6c63e..a3801f4897da 100644 --- a/x-pack/filebeat/input/salesforce/config.go +++ b/x-pack/filebeat/input/salesforce/config.go @@ -15,7 +15,6 @@ type config struct { EventMonitoringMethod *EventMonitoringMethod `config:"event_monitoring_method"` URL string `config:"url" validate:"required"` Version int `config:"version" validate:"required"` - InitialInterval time.Duration `config:"initial_interval"` } type EventMonitoringMethod struct { diff --git a/x-pack/filebeat/input/salesforce/config_auth_test.go b/x-pack/filebeat/input/salesforce/config_auth_test.go index 9e2ad10334fb..0b378f7b47f0 100644 --- a/x-pack/filebeat/input/salesforce/config_auth_test.go +++ b/x-pack/filebeat/input/salesforce/config_auth_test.go @@ -21,7 +21,7 @@ func TestOAuth2Config(t *testing.T) { "tokenURL missing": {config: UserPasswordFlow{Enabled: pointer(true), TokenURL: ""}, wantErr: errors.New("token_url must be provided")}, "clientID missing": {config: UserPasswordFlow{Enabled: pointer(true), TokenURL: "https://salesforce.com", ClientID: ""}, wantErr: errors.New("client.id must be provided")}, "clientSecret missing": {config: UserPasswordFlow{Enabled: pointer(true), TokenURL: "https://salesforce.com", ClientID: "xyz", ClientSecret: ""}, wantErr: errors.New("client.secret must be provided")}, - "user missing": {config: UserPasswordFlow{Enabled: pointer(true), TokenURL: "https://salesforce.com", ClientID: "xyz", ClientSecret: "abc", Username: ""}, wantErr: errors.New("username must be provided")}, + "username missing": {config: UserPasswordFlow{Enabled: pointer(true), TokenURL: "https://salesforce.com", ClientID: "xyz", ClientSecret: "abc", Username: ""}, wantErr: errors.New("username must be provided")}, "password missing": {config: UserPasswordFlow{Enabled: pointer(true), TokenURL: "https://salesforce.com", ClientID: "xyz", ClientSecret: "abc", Username: "user", Password: ""}, wantErr: errors.New("password must be provided")}, "all present": {config: UserPasswordFlow{Enabled: pointer(true), TokenURL: "https://salesforce.com", ClientID: "xyz", ClientSecret: "abc", Username: "user", Password: "pass"}, wantErr: nil}, } diff --git a/x-pack/filebeat/input/salesforce/input.go b/x-pack/filebeat/input/salesforce/input.go index 8a8df2b61ff0..e608cf3f4eda 100644 --- a/x-pack/filebeat/input/salesforce/input.go +++ b/x-pack/filebeat/input/salesforce/input.go @@ -198,7 +198,7 @@ func (s *salesforceInput) SetupSFClientConnection() (*soql.Resource, error) { // FormQueryWithCursor takes a queryConfig and a cursor and returns a querier. func (s *salesforceInput) FormQueryWithCursor(queryConfig *QueryConfig, cursor mapstr.M) (*querier, error) { - qr, err := parseCursor(&s.config.InitialInterval, queryConfig, cursor, s.log) + qr, err := parseCursor(queryConfig, cursor, s.log) if err != nil { return nil, err } @@ -306,7 +306,7 @@ func (s *salesforceInput) RunEventLogFile() error { totalEvents, firstEvent := 0, true for res.Done() { for _, rec := range res.Records() { - req, err := http.NewRequestWithContext(s.ctx, "GET", s.config.URL+rec.Record().Fields()["LogFile"].(string), nil) + req, err := http.NewRequestWithContext(s.ctx, http.MethodGet, s.config.URL+rec.Record().Fields()["LogFile"].(string), nil) if err != nil { return err } diff --git a/x-pack/filebeat/input/salesforce/input_test.go b/x-pack/filebeat/input/salesforce/input_test.go index 118dd2b505ec..334186595f10 100644 --- a/x-pack/filebeat/input/salesforce/input_test.go +++ b/x-pack/filebeat/input/salesforce/input_test.go @@ -6,8 +6,8 @@ package salesforce import ( "context" + "errors" "fmt" - "io" "net/http" "net/http/httptest" "reflect" @@ -15,38 +15,49 @@ import ( "testing" "time" - "github.com/elastic/beats/v7/libbeat/common/transform/typeconv" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - inputcursor "github.com/elastic/beats/v7/filebeat/input/v2/input-cursor" "github.com/elastic/beats/v7/libbeat/beat" + "github.com/elastic/beats/v7/libbeat/common/transform/typeconv" conf "github.com/elastic/elastic-agent-libs/config" "github.com/elastic/elastic-agent-libs/logp" "github.com/elastic/elastic-agent-libs/mapstr" + "github.com/g8rswimmer/go-sfdc" + "github.com/g8rswimmer/go-sfdc/soql" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" ) const ( - defaultLoginEventObjectQuery = "SELECT FIELDS(STANDARD) FROM LoginEvent" - valueLoginEventObjectQuery = "SELECT FIELDS(STANDARD) FROM LoginEvent WHERE EventDate > [[ .cursor.object.first_event_time ]]" + PaginationFlow = "PaginationFlow" + NoPaginationFlow = "NoPaginationFlow" + IntervalFlow = "IntervalFlow" + BadReponseFlow = "BadReponseFlow" + + defaultLoginObjectQuery = "SELECT FIELDS(STANDARD) FROM LoginEvent" + valueLoginObjectQuery = "SELECT FIELDS(STANDARD) FROM LoginEvent WHERE EventDate > [[ .cursor.object.first_event_time ]]" + defaultLoginObjectQueryWithCursor = "SELECT FIELDS(STANDARD) FROM LoginEvent WHERE EventDate > 2023-12-06T05:44:24.973+0000" defaultLoginEventLogFileQuery = "SELECT Id,CreatedDate,LogDate,LogFile FROM EventLogFile WHERE EventType = 'Login' ORDER BY CreatedDate ASC NULLS FIRST" valueLoginEventLogFileQuery = "SELECT Id,CreatedDate,LogDate,LogFile FROM EventLogFile WHERE EventType = 'Login' AND CreatedDate > [[ .cursor.event_log_file.last_event_time ]] ORDER BY CreatedDate ASC NULLS FIRST" + invalidDefaultLoginEventObjectQuery = "SELECT FIELDS(STANDARD) FROM LoginEvnt" + invalidDefaultLoginEventLogFileQuery = "SELECT Id,CreatedDate,LogDate,LogFile FROM EventLogFile WHERE EventType = 'Login' ORDER BY ASC NULLS FIRST" + + invalidValueLoginObjectQuery = "SELECT FIELDS(STANDARD) FROM LoginEvent WHERE EventDate > [[ .cursor.object.first_event ]]" + invalidValueLoginEventLogFileQuery = "SELECT Id,CreatedDate,LogDate,LogFile FROM EventLogFile WHERE EventType = 'Login' AND CreatedDate > [[ .cursor.event_log_file.last_event ]] ORDER BY CreatedDate ASC NULLS FIRST" + oneEventLogfileFirstResponseJSON = `{ "totalSize": 1, "done": true, "records": [ { "attributes": { "type": "EventLogFile", "url": "/services/data/v58.0/sobjects/EventLogFile/0AT5j00002LqQTxGAN" }, "Id": "0AT5j00002LqQTxGAN", "CreatedDate": "2023-12-19T21:04:35.000+0000", "LogDate": "2023-12-18T00:00:00.000+0000", "LogFile": "/services/data/v58.0/sobjects/EventLogFile/0AT5j00002LqQTxGAN/LogFile" } ] }` oneEventLogfileSecondResponseCSV = `"EVENT_TYPE","TIMESTAMP","REQUEST_ID","ORGANIZATION_ID","USER_ID","RUN_TIME","CPU_TIME","URI","SESSION_KEY","LOGIN_KEY","USER_TYPE","REQUEST_STATUS","DB_TOTAL_TIME","LOGIN_TYPE","BROWSER_TYPE","API_TYPE","API_VERSION","USER_NAME","TLS_PROTOCOL","CIPHER_SUITE","AUTHENTICATION_METHOD_REFERENCE","LOGIN_SUB_TYPE","TIMESTAMP_DERIVED","USER_ID_DERIVED","CLIENT_IP","URI_ID_DERIVED","LOGIN_STATUS","SOURCE_IP" "Login","20231218054831.655","4u6LyuMrDvb_G-l1cJIQk-","00D5j00000DgAYG","0055j00000AT6I1","1219","127","/services/oauth2/token","","bY5Wfv8t/Ith7WVE","Standard","","1051271151","i","Go-http-client/1.1","","9998.0","salesforceinstance@devtest.in","TLSv1.2","ECDHE-RSA-AES256-GCM-SHA384","","","2023-12-18T05:48:31.655Z","0055j00000AT6I1AAL","Salesforce.com IP","","LOGIN_NO_ERROR","103.108.207.58" -"Login","20231218054832.003","4u6LyuHSDv8LLVl1cJOqGV","00D5j00000DgAYG","0055j00000AT6I1","1277","104","/services/oauth2/token","","u60el7VqW8CSSKcW","Standard","","674857427","i","Go-http-client/1.1","","9998.0","salesforceinstance@devtest.in","TLSv1.2","ECDHE-RSA-AES256-GCM-SHA384","","","2023-12-18T05:48:32.003Z","0055j00000AT6I1AAL","103.108.207.58","","LOGIN_NO_ERROR","103.108.207.58" -"Login","20231218060656.232","4u6MzGRQmHeuAVl1cJNqIV","00D5j00000DgAYG","0055j00000AT6I1","90","37","/services/oauth2/token","","RhVHOa6prs/tRDZr","Standard","","52463859","i","Go-http-client/1.1","","9998.0","salesforceinstance@devtest.in","TLSv1.2","ECDHE-RSA-AES256-GCM-SHA384","","","2023-12-18T06:06:56.232Z","0055j00000AT6I1AAL","103.108.207.58","","LOGIN_NO_ERROR","103.108.207.58" -"Login","20231218060656.247","4u6MzGUl-6XJDVl1cJIHX-","00D5j00000DgAYG","0055j00000AT6I1","93","34","/services/oauth2/token","","gwwwflcWcytNc28j","Standard","","29208661","i","Go-http-client/1.1","","9998.0","salesforceinstance@devtest.in","TLSv1.2","ECDHE-RSA-AES256-GCM-SHA384","","","2023-12-18T06:06:56.247Z","0055j00000AT6I1AAL","Salesforce.com IP","","LOGIN_NO_ERROR","103.108.207.58" -"Login","20231218060707.127","4u6Mzu2WdsiJ4-l1cJ10hV","00D5j00000DgAYG","0055j00000AT6I1","85","35","/services/oauth2/token","","/6xZonK38rL95woZ","Standard","","47752553","i","Go-http-client/1.1","","9998.0","salesforceinstance@devtest.in","TLSv1.2","ECDHE-RSA-AES256-GCM-SHA384","","","2023-12-18T06:07:07.127Z","0055j00000AT6I1AAL","Salesforce.com IP","","LOGIN_NO_ERROR","103.108.207.58" -"Login","20231218060707.336","4u6MzuqusFJ0wFl1cJOrUV","00D5j00000DgAYG","0055j00000AT6I1","79","37","/services/oauth2/token","","U6CLGZunsCexuVaT","Standard","","42803201","i","Go-http-client/1.1","","9998.0","salesforceinstance@devtest.in","TLSv1.2","ECDHE-RSA-AES256-GCM-SHA384","","","2023-12-18T06:07:07.336Z","0055j00000AT6I1AAL","Salesforce.com IP","","LOGIN_NO_ERROR","103.108.207.58" -"Login","20231218061215.381","4u6NGqOvVq3PAVl1cIPeN-","00D5j00000DgAYG","0055j00000AT6I1","81","35","/services/oauth2/token","","r9Z9CeDgcqvuiAE2","Standard","","41447870","i","Go-http-client/1.1","","9998.0","salesforceinstance@devtest.in","TLSv1.2","ECDHE-RSA-AES256-GCM-SHA384","","","2023-12-18T06:12:15.381Z","0055j00000AT6I1AAL","Salesforce.com IP","","LOGIN_NO_ERROR","103.108.207.58" -"Login","20231218061215.388","4u6NGqPumIr5IFl1cJI3i-","00D5j00000DgAYG","0055j00000AT6I1","85","38","/services/oauth2/token","","nj1BE27iK9SO3PLI","Standard","","48346017","i","Go-http-client/1.1","","9998.0","salesforceinstance@devtest.in","TLSv1.2","ECDHE-RSA-AES256-GCM-SHA384","","","2023-12-18T06:12:15.388Z","0055j00000AT6I1AAL","Salesforce.com IP","","LOGIN_NO_ERROR","103.108.207.58" -"Login","20231218090817.044","4u6WsbPmV2hRy-l1cJ1-SV","00D5j00000DgAYG","0055j00000AT6I1","148","41","/services/oauth2/token","","6ubxOot7STy+7IrM","Standard","","73688965","i","Go-http-client/1.1","","9998.0","salesforceinstance@devtest.in","TLSv1.2","ECDHE-RSA-AES256-GCM-SHA384","","","2023-12-18T09:08:17.044Z","0055j00000AT6I1AAL","Salesforce.com IP","","LOGIN_NO_ERROR","134.238.252.19" -"Login","20231218090817.878","4u6WsekpMMbFsFl1cJIGT-","00D5j00000DgAYG","0055j00000AT6I1","84","37","/services/oauth2/token","","ODeZG/Py69Bxtb4o","Standard","","46097736","i","Go-http-client/1.1","","9998.0","salesforceinstance@devtest.in","TLSv1.2","ECDHE-RSA-AES256-GCM-SHA384","","","2023-12-18T09:08:17.878Z","0055j00000AT6I1AAL","134.238.252.19","","LOGIN_NO_ERROR","134.238.252.19" ` + + expectedELFEvent = `{"API_TYPE":"","API_VERSION":"9998.0","AUTHENTICATION_METHOD_REFERENCE":"","BROWSER_TYPE":"Go-http-client/1.1","CIPHER_SUITE":"ECDHE-RSA-AES256-GCM-SHA384","CLIENT_IP":"Salesforce.com IP","CPU_TIME":"127","DB_TOTAL_TIME":"1051271151","EVENT_TYPE":"Login","LOGIN_KEY":"bY5Wfv8t/Ith7WVE","LOGIN_STATUS":"LOGIN_NO_ERROR","LOGIN_SUB_TYPE":"","LOGIN_TYPE":"i","ORGANIZATION_ID":"00D5j00000DgAYG","REQUEST_ID":"4u6LyuMrDvb_G-l1cJIQk-","REQUEST_STATUS":"","RUN_TIME":"1219","SESSION_KEY":"","SOURCE_IP":"103.108.207.58","TIMESTAMP":"20231218054831.655","TIMESTAMP_DERIVED":"2023-12-18T05:48:31.655Z","TLS_PROTOCOL":"TLSv1.2","URI":"/services/oauth2/token","URI_ID_DERIVED":"","USER_ID":"0055j00000AT6I1","USER_ID_DERIVED":"0055j00000AT6I1AAL","USER_NAME":"salesforceinstance@devtest.in","USER_TYPE":"Standard"}` + + oneObjectEvents = `{ "totalSize": 1, "done": true, "records": [ { "attributes": { "type": "LoginEvent", "url": "/services/data/v58.0/sobjects/LoginEvent/000000000000000AAA" }, "AdditionalInfo": "{}", "ApiType": "N/A", "ApiVersion": "N/A", "Application": "salesforce_test", "Browser": "Unknown", "CipherSuite": "ECDHE-RSA-AES256-GCM-SHA384", "City": "Mumbai", "ClientVersion": "N/A", "Country": "India", "CountryIso": "IN", "CreatedDate": "2023-12-06T05:44:34.942+0000", "EvaluationTime": 0, "EventDate": "2023-12-06T05:44:24.973+0000", "EventIdentifier": "00044326-ed4a-421a-a0a8-e62ea626f3af", "HttpMethod": "POST", "Id": "000000000000000AAA", "LoginGeoId": "04F5j00003NvV1cEAF", "LoginHistoryId": "0Ya5j00003k2scQCAQ", "LoginKey": "pgOVoLbV96U9o08W", "LoginLatitude": 19.0748, "LoginLongitude": 72.8856, "LoginType": "Remote Access 2.0", "LoginUrl": "login.salesforce.com", "Platform": "Unknown", "PostalCode": "400070", "SessionLevel": "STANDARD", "SourceIp": "134.238.252.19", "Status": "Success", "Subdivision": "Maharashtra", "TlsProtocol": "TLS 1.2", "UserId": "0055j00000AT6I1AAL", "UserType": "Standard", "Username": "salesforceinstance@devtest.in" } ] }` + oneObjectEventsPageOne = `{ "totalSize": 1, "done": true, "nextRecordsUrl": "/nextRecords/LoginEvents/ABCABCDABCDE", "records": [ { "attributes": { "type": "LoginEvent", "url": "/services/data/v58.0/sobjects/LoginEvent/000000000000000AAA" }, "AdditionalInfo": "{}", "ApiType": "N/A", "ApiVersion": "N/A", "Application": "salesforce_test", "Browser": "Unknown", "CipherSuite": "ECDHE-RSA-AES256-GCM-SHA384", "City": "Mumbai", "ClientVersion": "N/A", "Country": "India", "CountryIso": "IN", "CreatedDate": "2023-12-06T05:44:34.942+0000", "EvaluationTime": 0, "EventDate": "2023-12-06T05:44:24.973+0000", "EventIdentifier": "00044326-ed4a-421a-a0a8-e62ea626f3af", "HttpMethod": "POST", "Id": "000000000000000AAA", "LoginGeoId": "04F5j00003NvV1cEAF", "LoginHistoryId": "0Ya5j00003k2scQCAQ", "LoginKey": "pgOVoLbV96U9o08W", "LoginLatitude": 19.0748, "LoginLongitude": 72.8856, "LoginType": "Remote Access 2.0", "LoginUrl": "login.salesforce.com", "Platform": "Unknown", "PostalCode": "400070", "SessionLevel": "STANDARD", "SourceIp": "134.238.252.19", "Status": "Success", "Subdivision": "Maharashtra", "TlsProtocol": "TLS 1.2", "UserId": "0055j00000AT6I1AAL", "UserType": "Standard", "Username": "salesforceinstance@devtest.in" } ] }` + oneObjectEventsPageTwo = `{ "totalSize": 1, "done": true, "records": [ { "attributes": { "type": "LoginEvent", "url": "/services/data/v58.0/sobjects/LoginEvent/000000000000000AAA" }, "AdditionalInfo": "{}", "ApiType": "N/A", "ApiVersion": "N/A", "Application": "salesforce_test", "Browser": "Unknown", "CipherSuite": "ECDHE-RSA-AES256-GCM-SHA384", "City": "Mumbai", "ClientVersion": "N/A", "Country": "India", "CountryIso": "IN", "CreatedDate": "2023-12-06T05:44:34.942+0000", "EvaluationTime": 0, "EventDate": "2023-12-06T05:44:24.973+0000", "EventIdentifier": "00044326-ed4a-421a-a0a8-e62ea626f3af", "HttpMethod": "POST", "Id": "000000000000000AAA", "LoginGeoId": "04F5j00003NvV1cEAF", "LoginHistoryId": "0Ya5j00003k2scQCAQ", "LoginKey": "pgOVoLbV96U9o08W", "LoginLatitude": 19.0748, "LoginLongitude": 72.8856, "LoginType": "Remote Access 2.0", "LoginUrl": "login.salesforce.com", "Platform": "Unknown", "PostalCode": "400070", "SessionLevel": "STANDARD", "SourceIp": "134.238.252.19", "Status": "Success", "Subdivision": "Maharashtra", "TlsProtocol": "TLS 1.2", "UserId": "0055j00000AT6I1AAL", "UserType": "Standard", "Username": "salesforceinstance@devtest.in" } ] }` + + expectedObjectEvent = `{"AdditionalInfo":"{}","ApiType":"N/A","ApiVersion":"N/A","Application":"salesforce_test","Browser":"Unknown","CipherSuite":"ECDHE-RSA-AES256-GCM-SHA384","City":"Mumbai","ClientVersion":"N/A","Country":"India","CountryIso":"IN","CreatedDate":"2023-12-06T05:44:34.942+0000","EvaluationTime":0,"EventDate":"2023-12-06T05:44:24.973+0000","EventIdentifier":"00044326-ed4a-421a-a0a8-e62ea626f3af","HttpMethod":"POST","Id":"000000000000000AAA","LoginGeoId":"04F5j00003NvV1cEAF","LoginHistoryId":"0Ya5j00003k2scQCAQ","LoginKey":"pgOVoLbV96U9o08W","LoginLatitude":19.0748,"LoginLongitude":72.8856,"LoginType":"Remote Access 2.0","LoginUrl":"login.salesforce.com","Platform":"Unknown","PostalCode":"400070","SessionLevel":"STANDARD","SourceIp":"134.238.252.19","Status":"Success","Subdivision":"Maharashtra","TlsProtocol":"TLS 1.2","UserId":"0055j00000AT6I1AAL","UserType":"Standard","Username":"salesforceinstance@devtest.in"}` ) func TestFormQueryWithCursor(t *testing.T) { @@ -84,6 +95,14 @@ func TestFormQueryWithCursor(t *testing.T) { wantQuery: "SELECT Id,CreatedDate,LogDate,LogFile FROM EventLogFile WHERE EventType = 'Login' AND CreatedDate > 2023-05-18T12:00:00Z ORDER BY CreatedDate ASC NULLS FIRST", cursor: mapstr.M{"event_log_file": mapstr.M{"logdate": timeNow().Format(formatRFC3339Like)}}, }, + "invalid soql templates wrong cursor name .cursor.event_log_file.logdate1": { // expect value SOQL query with .cursor.event_log_file.logdate set + initialInterval: 60 * 24 * time.Hour, // 60 * 24h = 1440h = 60 days = 2 months + defaultSOQLTemplate: `SELECT Id,CreatedDate,LogDate,LogFile FROM EventLogFile WHERE EventType = 'Login' AND Logdate > [[ (formatTime (now.Add (parseDuration "-1440h")) "RFC3339") ]] ORDER BY CreatedDate ASC NULLS FIRST`, + valueSOQLTemplate: "SELECT Id,CreatedDate,LogDate,LogFile FROM EventLogFile WHERE EventType = 'Login' AND CreatedDate > [[ .cursor.event_log_file.logdate1 ]] ORDER BY CreatedDate ASC NULLS FIRST", + wantQuery: "SELECT Id,CreatedDate,LogDate,LogFile FROM EventLogFile WHERE EventType = 'Login' AND CreatedDate > 2023-05-18T12:00:00Z ORDER BY CreatedDate ASC NULLS FIRST", + cursor: mapstr.M{"event_log_file": mapstr.M{"logdate": timeNow().Format(formatRFC3339Like)}}, + wantErr: errors.New(`template: :1:110: executing "" at <.cursor.event_log_file.logdate1>: map has no entry for key "logdate1"`), + }, } for name, tc := range tests { @@ -102,43 +121,58 @@ func TestFormQueryWithCursor(t *testing.T) { } sfInput := &salesforceInput{ - config: config{InitialInterval: tc.initialInterval}, + config: config{}, log: logp.NewLogger("salesforce_test"), } querier, err := sfInput.FormQueryWithCursor(queryConfig, tc.cursor) - assert.NoError(t, err) + if fmt.Sprint(tc.wantErr) != fmt.Sprint(err) { + t.Errorf("got error %v, want error %v", err, tc.wantErr) + } + if tc.wantErr != nil { + return + } assert.EqualValues(t, tc.wantQuery, querier.Query) }) } } -var defaultUserPasswordFlowMap = map[string]interface{}{ - "user_password_flow": map[string]interface{}{ - "enabled": true, - "client.id": "clientid", - "client.secret": "clientsecret", - "token_url": "https://instance_id.develop.my.salesforce.com/services/oauth2/token", - "username": "username", - "password": "password", - }, -} - var ( + defaultUserPasswordFlowMap = map[string]interface{}{ + "user_password_flow": map[string]interface{}{ + "enabled": true, + "client.id": "clientid", + "client.secret": "clientsecret", + "token_url": "https://instance_id.develop.my.salesforce.com/services/oauth2/token", + "username": "username", + "password": "password", + }, + } + wrongUserPasswordFlowMap = map[string]interface{}{ + "user_password_flow": map[string]interface{}{ + "enabled": true, + "client.id": "clientid-wrong", + "client.secret": "clientsecret-wrong", + "token_url": "https://instance_id.develop.my.salesforce.com/services/oauth2/token", + "username": "username-wrong", + "password": "password-wrong", + }, + } + defaultObjectMonitoringMethodConfigMap = map[string]interface{}{ - "interval": "5m", + "interval": "5s", "enabled": true, "query": map[string]interface{}{ - "default": defaultLoginEventObjectQuery, - "value": valueLoginEventObjectQuery, + "default": defaultLoginObjectQuery, + "value": valueLoginObjectQuery, }, "cursor": map[string]interface{}{ "field": "EventDate", }, } defaultEventLogFileMonitoringMethodMap = map[string]interface{}{ - "interval": "5m", + "interval": "5s", "enabled": true, "query": map[string]interface{}{ "default": defaultLoginEventLogFileQuery, @@ -148,21 +182,48 @@ var ( "field": "CreatedDate", }, } + + invalidObjectMonitoringMethodMap = map[string]interface{}{ + "interval": "5m", + "enabled": true, + "query": map[string]interface{}{ + "default": invalidDefaultLoginEventObjectQuery, + "value": valueLoginEventLogFileQuery, + }, + "cursor": map[string]interface{}{ + "field": "CreatedDate", + }, + } + invalidEventLogFileMonitoringMethodMap = map[string]interface{}{ + "interval": "5m", + "enabled": true, + "query": map[string]interface{}{ + "default": invalidDefaultLoginEventLogFileQuery, + "value": invalidValueLoginEventLogFileQuery, + }, + "cursor": map[string]interface{}{ + "field": "CreatedDate", + }, + } ) func TestInput(t *testing.T) { logp.TestingSetup() tests := []struct { - name string - setupServer func(testing.TB, http.HandlerFunc, map[string]interface{}) - baseConfig map[string]interface{} - handler http.HandlerFunc - expected []string - wantErr bool + name string + setupServer func(testing.TB, http.HandlerFunc, map[string]interface{}) + baseConfig map[string]interface{} + handler http.HandlerFunc + expected []string + wantErr bool + persistentCursor *state + AuthFail bool + timeout time.Duration }{ + // Object { - name: "event_monitoring_method_object_with_default_query_only", + name: "Positive/event_monitoring_method_object_with_default_query_only", setupServer: newTestServer(httptest.NewServer), baseConfig: map[string]interface{}{ "version": 56, @@ -171,21 +232,90 @@ func TestInput(t *testing.T) { "object": defaultObjectMonitoringMethodConfigMap, }, }, - handler: defaultHandler("GET", "", "", `{ "totalSize": 60, "done": true, "records": [ { "attributes": { "type": "LoginEvent", "url": "/services/data/v58.0/sobjects/LoginEvent/000000000000000AAA" }, "AdditionalInfo": "{}", "ApiType": "N/A", "ApiVersion": "N/A", "Application": "salesforce_test", "Browser": "Unknown", "CipherSuite": "ECDHE-RSA-AES256-GCM-SHA384", "City": "Mumbai", "ClientVersion": "N/A", "Country": "India", "CountryIso": "IN", "CreatedDate": "2023-12-06T05:44:34.942+0000", "EvaluationTime": 0, "EventDate": "2023-12-06T05:44:24.973+0000", "EventIdentifier": "00044326-ed4a-421a-a0a8-e62ea626f3af", "HttpMethod": "POST", "Id": "000000000000000AAA", "LoginGeoId": "04F5j00003NvV1cEAF", "LoginHistoryId": "0Ya5j00003k2scQCAQ", "LoginKey": "pgOVoLbV96U9o08W", "LoginLatitude": 19.0748, "LoginLongitude": 72.8856, "LoginType": "Remote Access 2.0", "LoginUrl": "login.salesforce.com", "Platform": "Unknown", "PostalCode": "400070", "SessionLevel": "STANDARD", "SourceIp": "134.238.252.19", "Status": "Success", "Subdivision": "Maharashtra", "TlsProtocol": "TLS 1.2", "UserId": "0055j00000AT6I1AAL", "UserType": "Standard", "Username": "salesforceinstance@devtest.in" } ] }`), - expected: []string{`{"AdditionalInfo":"{}","ApiType":"N/A","ApiVersion":"N/A","Application":"salesforce_test","Browser":"Unknown","CipherSuite":"ECDHE-RSA-AES256-GCM-SHA384","City":"Mumbai","ClientVersion":"N/A","Country":"India","CountryIso":"IN","CreatedDate":"2023-12-06T05:44:34.942+0000","EvaluationTime":0,"EventDate":"2023-12-06T05:44:24.973+0000","EventIdentifier":"00044326-ed4a-421a-a0a8-e62ea626f3af","HttpMethod":"POST","Id":"000000000000000AAA","LoginGeoId":"04F5j00003NvV1cEAF","LoginHistoryId":"0Ya5j00003k2scQCAQ","LoginKey":"pgOVoLbV96U9o08W","LoginLatitude":19.0748,"LoginLongitude":72.8856,"LoginType":"Remote Access 2.0","LoginUrl":"login.salesforce.com","Platform":"Unknown","PostalCode":"400070","SessionLevel":"STANDARD","SourceIp":"134.238.252.19","Status":"Success","Subdivision":"Maharashtra","TlsProtocol":"TLS 1.2","UserId":"0055j00000AT6I1AAL","UserType":"Standard","Username":"salesforceinstance@devtest.in"}`}, + handler: defaultHandler(NoPaginationFlow, false, "", oneObjectEvents), + expected: []string{expectedObjectEvent}, }, { - name: "event_monitoring_method_event_log_file_with_default_query_only", + name: "Negative/event_monitoring_method_object_with_error_in_data_collection", setupServer: newTestServer(httptest.NewServer), baseConfig: map[string]interface{}{ "version": 56, "auth.oauth2": defaultUserPasswordFlowMap, + "event_monitoring_method": map[string]interface{}{ + "object": invalidObjectMonitoringMethodMap, + }, + }, + handler: defaultHandler(NoPaginationFlow, false, "", `{"error": "invalid_query"}`), + wantErr: true, + }, + { + name: "Positive/event_monitoring_method_object_with_interval_5s", + setupServer: newTestServer(httptest.NewServer), + baseConfig: map[string]interface{}{ + "version": 56, + "auth.oauth2": defaultUserPasswordFlowMap, + "event_monitoring_method": map[string]interface{}{ + "object": defaultObjectMonitoringMethodConfigMap, + }, + }, + handler: defaultHandler(IntervalFlow, false, "", oneObjectEventsPageTwo), + expected: []string{expectedObjectEvent, expectedObjectEvent}, + timeout: 20 * time.Second, + }, + { + name: "Positive/event_monitoring_method_object_with_Pagination", + setupServer: newTestServer(httptest.NewServer), + baseConfig: map[string]interface{}{ + "version": 56, + "auth.oauth2": defaultUserPasswordFlowMap, + "event_monitoring_method": map[string]interface{}{ + "object": defaultObjectMonitoringMethodConfigMap, + }, + }, + handler: defaultHandler(PaginationFlow, false, oneObjectEventsPageOne, oneObjectEventsPageTwo), + expected: []string{expectedObjectEvent, expectedObjectEvent}, + }, + + // EventLogFile + { + name: "Positive/event_monitoring_method_elf_with_default_query_only", + setupServer: newTestServer(httptest.NewServer), + baseConfig: map[string]interface{}{ + "version": 56, + "auth.oauth2": defaultUserPasswordFlowMap, + "event_monitoring_method": map[string]interface{}{ + "event_log_file": defaultEventLogFileMonitoringMethodMap, + }, + }, + handler: defaultHandler(NoPaginationFlow, false, oneEventLogfileFirstResponseJSON, oneEventLogfileSecondResponseCSV), + expected: []string{expectedELFEvent}, + }, + { + name: "Negative/event_monitoring_method_elf_with_error_in_auth", + setupServer: newTestServer(httptest.NewServer), + baseConfig: map[string]interface{}{ + "version": 56, + "auth.oauth2": wrongUserPasswordFlowMap, "event_monitoring_method": map[string]interface{}{ "event_log_file": defaultEventLogFileMonitoringMethodMap, }, }, - handler: defaultHandler("GET", "", oneEventLogfileFirstResponseJSON, oneEventLogfileSecondResponseCSV), - expected: []string{`{"API_TYPE":"","API_VERSION":"9998.0","AUTHENTICATION_METHOD_REFERENCE":"","BROWSER_TYPE":"Go-http-client/1.1","CIPHER_SUITE":"ECDHE-RSA-AES256-GCM-SHA384","CLIENT_IP":"Salesforce.com IP","CPU_TIME":"127","DB_TOTAL_TIME":"1051271151","EVENT_TYPE":"Login","LOGIN_KEY":"bY5Wfv8t/Ith7WVE","LOGIN_STATUS":"LOGIN_NO_ERROR","LOGIN_SUB_TYPE":"","LOGIN_TYPE":"i","ORGANIZATION_ID":"00D5j00000DgAYG","REQUEST_ID":"4u6LyuMrDvb_G-l1cJIQk-","REQUEST_STATUS":"","RUN_TIME":"1219","SESSION_KEY":"","SOURCE_IP":"103.108.207.58","TIMESTAMP":"20231218054831.655","TIMESTAMP_DERIVED":"2023-12-18T05:48:31.655Z","TLS_PROTOCOL":"TLSv1.2","URI":"/services/oauth2/token","URI_ID_DERIVED":"","USER_ID":"0055j00000AT6I1","USER_ID_DERIVED":"0055j00000AT6I1AAL","USER_NAME":"salesforceinstance@devtest.in","USER_TYPE":"Standard"}`}, + handler: defaultHandler(NoPaginationFlow, false, "", `{"error": "invalid_client_id"}`), + wantErr: true, + AuthFail: true, + }, + { + name: "Negative/event_monitoring_method_elf_with_error_in_data_collection", + setupServer: newTestServer(httptest.NewServer), + baseConfig: map[string]interface{}{ + "version": 56, + "auth.oauth2": defaultUserPasswordFlowMap, + "event_monitoring_method": map[string]interface{}{ + "event_log_file": invalidEventLogFileMonitoringMethodMap, + }, + }, + handler: defaultHandler(NoPaginationFlow, false, "", `{"error": "invalid_query"}`), + wantErr: true, }, } @@ -196,8 +326,12 @@ func TestInput(t *testing.T) { cfg := defaultConfig() err := conf.MustNewConfigFrom(tc.baseConfig).Unpack(&cfg) assert.NoError(t, err) + timeout := 5 * time.Second + if tc.timeout != 0 { + timeout = tc.timeout + } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() var client publisher @@ -213,6 +347,9 @@ func TestInput(t *testing.T) { ctx, cancelClause := context.WithCancelCause(ctx) salesforceInput.cursor = &state{} + if tc.persistentCursor != nil { + salesforceInput.cursor = tc.persistentCursor + } salesforceInput.ctx = ctx salesforceInput.cancel = cancelClause salesforceInput.srcConfig = &cfg @@ -223,11 +360,19 @@ func TestInput(t *testing.T) { assert.NoError(t, err) salesforceInput.soqlr, err = salesforceInput.SetupSFClientConnection() - assert.NoError(t, err) + if err != nil && !tc.wantErr { + t.Errorf("unexpected error from running input: %v", err) + } + if tc.wantErr && tc.AuthFail { + return + } err = salesforceInput.run() - if tc.wantErr != (err != nil) { - t.Errorf("unexpected error from running input: got:%v want:%v", err, tc.wantErr) + if err != nil && !tc.wantErr { + t.Errorf("unexpected error from running input: %v", err) + } + if tc.wantErr { + return } if len(client.published) < len(tc.expected) { @@ -245,27 +390,34 @@ func TestInput(t *testing.T) { } } -func defaultHandler(expectedMethod, expectedBody, msg1, msg2 string) http.HandlerFunc { +func defaultHandler(flow string, withoutQuery bool, msg1, msg2 string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("content-type", "application/json") switch { - case r.RequestURI == "/services/oauth2/token": + case flow == PaginationFlow && r.FormValue("q") == defaultLoginObjectQuery: + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(msg1)) + case r.RequestURI == "/nextRecords/LoginEvents/ABCABCDABCDE": + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(msg2)) + case r.RequestURI == "/services/oauth2/token" && r.Method == http.MethodPost && r.FormValue("client_id") == "clientid": w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"access_token":"abcd","instance_url":"http://` + r.Host + `","token_type":"Bearer","id_token":"abcd","refresh_token":"abcd"}`)) - case r.FormValue("q") == "SELECT Id,CreatedDate,LogDate,LogFile FROM EventLogFile WHERE EventType = 'Login' ORDER BY CreatedDate ASC NULLS FIRST": + case r.FormValue("client_id") == "clientid-wrong": + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(msg2)) + case r.FormValue("q") == defaultLoginEventLogFileQuery: w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(msg1)) - case r.FormValue("q") == "SELECT FIELDS(STANDARD) FROM LoginEvent", r.RequestURI == "/services/data/v58.0/sobjects/EventLogFile/0AT5j00002LqQTxGAN/LogFile": + case r.FormValue("q") == defaultLoginObjectQuery, r.FormValue("q") == defaultLoginObjectQueryWithCursor, r.RequestURI == "/services/data/v58.0/sobjects/EventLogFile/0AT5j00002LqQTxGAN/LogFile": w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(msg2)) - case expectedBody != "": - body, _ := io.ReadAll(r.Body) - r.Body.Close() - if expectedBody != string(body) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(fmt.Sprintf(`{"error":"expected body was %q"}`, expectedBody))) - } - _, _ = w.Write([]byte(msg1)) + case r.FormValue("q") == invalidDefaultLoginEventLogFileQuery, r.FormValue("q") == invalidDefaultLoginEventObjectQuery: + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(msg2)) + case flow == BadReponseFlow && (withoutQuery && r.FormValue("q") == ""): + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"error":"internal server error"}`)) } } } @@ -353,6 +505,279 @@ func TestDecodeAsCSV(t *testing.T) { assert.Equal(t, wantEventFields, mp[0]) } +func TestSalesforceInputRunWithMethod(t *testing.T) { + var ( + defaultUserPassAuthConfig = authConfig{ + OAuth2: &OAuth2{ + UserPasswordFlow: &UserPasswordFlow{ + Enabled: pointer(true), + TokenURL: "https://instance_id.develop.my.salesforce.com/services/oauth2/token", + ClientID: "clientid", + ClientSecret: "clientsecret", + Username: "username", + Password: "password", + }, + }, + } + objectEventMonitotingConfig = EventMonitoringMethod{ + Object: EventMonitoringConfig{ + Enabled: pointer(true), + Interval: time.Second * 5, + Query: &QueryConfig{ + Default: getValueTpl(defaultLoginObjectQuery), + Value: getValueTpl(valueLoginObjectQuery), + }, + Cursor: &cursorConfig{Field: "EventDate"}, + }, + } + objectEventMonitoringWithWrongQuery = EventMonitoringMethod{ + Object: EventMonitoringConfig{ + Enabled: pointer(true), + Interval: time.Second * 5, + Query: &QueryConfig{ + Default: getValueTpl(invalidDefaultLoginEventObjectQuery), + Value: getValueTpl(invalidValueLoginObjectQuery), + }, + Cursor: &cursorConfig{Field: "EventDate"}, + }, + } + + elfEventMonitotingConfig = EventMonitoringMethod{ + EventLogFile: EventMonitoringConfig{ + Enabled: pointer(true), + Interval: time.Second * 5, + Query: &QueryConfig{ + Default: getValueTpl(defaultLoginEventLogFileQuery), + Value: getValueTpl(valueLoginEventLogFileQuery), + }, + Cursor: &cursorConfig{Field: "EventDate"}, + }, + } + elfEventMonitotingWithWrongQuery = EventMonitoringMethod{ + EventLogFile: EventMonitoringConfig{ + Enabled: pointer(true), + Interval: time.Second * 5, + Query: &QueryConfig{ + Default: getValueTpl(invalidDefaultLoginEventLogFileQuery), + Value: getValueTpl(invalidValueLoginEventLogFileQuery), + }, + Cursor: &cursorConfig{Field: "EventDate"}, + }, + } + ) + + type fields struct { + config config + ctx context.Context + cancel context.CancelCauseFunc + publisher inputcursor.Publisher + cursor *state + srcConfig *config + sfdcConfig *sfdc.Configuration + soqlr *soql.Resource + } + tests := []struct { + method string + name string + fields fields + expected []string + wantErr bool + AuthFail bool + ClientConnectionFail bool + setupServer func(testing.TB, http.HandlerFunc, *config) + handler http.HandlerFunc + }{ + // Object + { + name: "Positive/object_get_one_event", + method: "Object", + setupServer: newTestServerBasedOnConfig(httptest.NewServer), + handler: defaultHandler(NoPaginationFlow, false, "", oneObjectEvents), + fields: fields{ + config: config{ + Version: 56, + Auth: &defaultUserPassAuthConfig, + EventMonitoringMethod: &objectEventMonitotingConfig, + }, + cursor: &state{}, + }, + expected: []string{expectedObjectEvent}, + }, + { + name: "Negative/object_error_from_wrong_default_query", + method: "Object", + setupServer: newTestServerBasedOnConfig(httptest.NewServer), + handler: defaultHandler(NoPaginationFlow, false, "", oneObjectEvents), + fields: fields{ + config: config{ + Version: 56, + Auth: &defaultUserPassAuthConfig, + EventMonitoringMethod: &objectEventMonitoringWithWrongQuery, + }, + cursor: &state{}, + }, + wantErr: true, + }, + { + name: "Negative/object_error_from_wrong_value_query", + method: "Object", + setupServer: newTestServerBasedOnConfig(httptest.NewServer), + handler: defaultHandler(NoPaginationFlow, false, "", oneObjectEvents), + fields: fields{ + config: config{ + Version: 56, + Auth: &defaultUserPassAuthConfig, + EventMonitoringMethod: &objectEventMonitoringWithWrongQuery, + }, + cursor: &state{ + Object: dateTimeCursor{ + FirstEventTime: "2020-01-01T00:00:00Z", + LastEventTime: "2020-01-01T00:00:00Z", + }, + }, + }, + wantErr: true, + }, + + // EventLogFile + { + name: "Positive/elf_get_one_event", + method: "ELF", + setupServer: newTestServerBasedOnConfig(httptest.NewServer), + handler: defaultHandler(NoPaginationFlow, false, oneEventLogfileFirstResponseJSON, oneEventLogfileSecondResponseCSV), + fields: fields{ + config: config{ + Version: 56, + Auth: &defaultUserPassAuthConfig, + EventMonitoringMethod: &elfEventMonitotingConfig, + }, + cursor: &state{}, + }, + expected: []string{expectedELFEvent}, + }, + { + name: "Negative/elf_error_from_wrong_default_query", + method: "ELF", + setupServer: newTestServerBasedOnConfig(httptest.NewServer), + handler: defaultHandler(NoPaginationFlow, false, oneEventLogfileFirstResponseJSON, oneEventLogfileSecondResponseCSV), + fields: fields{ + config: config{ + Version: 56, + Auth: &defaultUserPassAuthConfig, + EventMonitoringMethod: &elfEventMonitotingWithWrongQuery, + }, + cursor: &state{}, + }, + wantErr: true, + }, + { + name: "Negative/elf_error_from_wrong_value_query", + method: "ELF", + setupServer: newTestServerBasedOnConfig(httptest.NewServer), + handler: defaultHandler(NoPaginationFlow, false, oneEventLogfileFirstResponseJSON, oneEventLogfileSecondResponseCSV), + fields: fields{ + config: config{ + Version: 56, + Auth: &defaultUserPassAuthConfig, + EventMonitoringMethod: &elfEventMonitotingWithWrongQuery, + }, + cursor: &state{ + EventLogFile: dateTimeCursor{ + FirstEventTime: "2020-01-01T00:00:00Z", + LastEventTime: "2020-01-01T00:00:00Z", + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var err error + tt.setupServer(t, tt.handler, &tt.fields.config) + + s := &salesforceInput{ + config: tt.fields.config, + ctx: tt.fields.ctx, + cancel: tt.fields.cancel, + publisher: tt.fields.publisher, + cursor: tt.fields.cursor, + srcConfig: tt.fields.srcConfig, + sfdcConfig: tt.fields.sfdcConfig, + log: logp.NewLogger("salesforceInput"), + soqlr: tt.fields.soqlr, + } + + ctx, cancel := context.WithCancelCause(context.Background()) + s.ctx = ctx + s.cancel = cancel + + var client publisher + client.done = func() { + if len(client.published) >= len(tt.expected) { + cancel(nil) + } + } + s.publisher = &client + s.srcConfig = &s.config + + s.sfdcConfig, err = getSFDCConfig(&s.config) + if err != nil && !tt.wantErr { + t.Errorf("unexpected error from running input: %v", err) + } + if tt.wantErr && tt.AuthFail { + return + } + + s.soqlr, err = s.SetupSFClientConnection() + if err != nil && !tt.wantErr { + t.Errorf("unexpected error from running input: %v", err) + } + if tt.wantErr && tt.ClientConnectionFail { + return + } + + if tt.method == "Object" { + if err := s.RunObject(); (err != nil) != tt.wantErr { + t.Errorf("salesforceInput.RunObject() error = %v, wantErr %v", err, tt.wantErr) + } + } else { + if err := s.RunEventLogFile(); (err != nil) != tt.wantErr { + t.Errorf("salesforceInput.RunEventLogFile() error = %v, wantErr %v", err, tt.wantErr) + } + } + + if len(client.published) < len(tt.expected) { + t.Errorf("unexpected number of published events: got:%d want at least:%d", len(client.published), len(tt.expected)) + tt.expected = tt.expected[:len(client.published)] + } + + client.published = client.published[:len(tt.expected)] + for i, got := range client.published { + if !reflect.DeepEqual(got.Fields["message"], tt.expected[i]) { + t.Errorf("unexpected result for event %d: got:- want:+\n%s", i, cmp.Diff(got.Fields, tt.expected[i])) + } + } + }) + } +} + +func getValueTpl(in string) *valueTpl { + vp := &valueTpl{} + vp.Unpack(in) + + return vp +} + +func newTestServerBasedOnConfig(newServer func(http.Handler) *httptest.Server) func(testing.TB, http.HandlerFunc, *config) { + return func(t testing.TB, h http.HandlerFunc, config *config) { + server := newServer(h) + config.URL = server.URL + config.Auth.OAuth2.UserPasswordFlow.TokenURL = server.URL + "/services/oauth2/token" + t.Cleanup(server.Close) + } +} + func TestPlugin(t *testing.T) { _ = Plugin(logp.NewLogger("salesforce_test"), stateStore{}) } diff --git a/x-pack/filebeat/input/salesforce/state.go b/x-pack/filebeat/input/salesforce/state.go index bd0434e272f3..d5b9e51ab79d 100644 --- a/x-pack/filebeat/input/salesforce/state.go +++ b/x-pack/filebeat/input/salesforce/state.go @@ -5,8 +5,6 @@ package salesforce import ( - "time" - "github.com/elastic/elastic-agent-libs/logp" "github.com/elastic/elastic-agent-libs/mapstr" ) @@ -27,7 +25,7 @@ type dateTimeCursor struct { // parseCursor parses the cursor from the configuration and executes the // template. If cursor is nil, the default templated query is used else // the value templated query is used. See QueryConfig struct for more. -func parseCursor(initialInterval *time.Duration, cfg *QueryConfig, cursor mapstr.M, log *logp.Logger) (string, error) { +func parseCursor(cfg *QueryConfig, cursor mapstr.M, log *logp.Logger) (string, error) { ctxTmpl := mapstr.M{"cursor": nil} if cursor != nil {