diff --git a/.changeset/clean-penguins-fly.md b/.changeset/clean-penguins-fly.md new file mode 100644 index 000000000..f9b6766db --- /dev/null +++ b/.changeset/clean-penguins-fly.md @@ -0,0 +1,5 @@ +--- +'grafana-infinity-datasource': minor +--- + +Added support for configuring secure HTTP POST form data diff --git a/docs/sources/references/url.md b/docs/sources/references/url.md index 8baed77b7..6d139e984 100644 --- a/docs/sources/references/url.md +++ b/docs/sources/references/url.md @@ -60,6 +60,10 @@ You can configure the headers required for the URL in the datasource config and Note: We suggest adding secure headers only via configuration and not in query. +## Sending secure params as HTTP POST Form data + +When you are using HTTP POST method and one of the HTTP Form body types (**Form Data**/**X-WWW-FORM-URLENCODED**), you can pass additional secure form data via config option. The parameters set in config will be set in addition to the form parameters set in the query. You can find the settings under **HTTP Options** in the datasource config + ## Allowed Hosts Leaving blank will allow all the hosts. This is by default. diff --git a/pkg/infinity/client.go b/pkg/infinity/client.go index b72e86af0..84db4b856 100644 --- a/pkg/infinity/client.go +++ b/pkg/infinity/client.go @@ -278,7 +278,7 @@ func (client *Client) GetResults(ctx context.Context, query models.Query, reques } switch strings.ToUpper(query.URLOptions.Method) { case http.MethodPost: - body := GetQueryBody(ctx, query) + body := GetQueryBody(ctx,query, client.Settings) return client.req(ctx, query.URL, body, client.Settings, query, requestHeaders) default: return client.req(ctx, query.URL, nil, client.Settings, query, requestHeaders) @@ -311,7 +311,7 @@ func CanAllowURL(url string, allowedHosts []string) bool { return allow } -func GetQueryBody(ctx context.Context, query models.Query) io.Reader { +func GetQueryBody(ctx context.Context, query models.Query, settings models.InfinitySettings) io.Reader { logger := backend.Logger.FromContext(ctx) var body io.Reader if strings.EqualFold(query.URLOptions.Method, http.MethodPost) { @@ -324,6 +324,9 @@ func GetQueryBody(ctx context.Context, query models.Query) io.Reader { for _, f := range query.URLOptions.BodyForm { _ = writer.WriteField(f.Key, f.Value) } + for k, v := range settings.FormPostItems { + _ = writer.WriteField(k, v) + } if err := writer.Close(); err != nil { logger.Error("error closing the query body reader") return nil @@ -334,6 +337,9 @@ func GetQueryBody(ctx context.Context, query models.Query) io.Reader { for _, f := range query.URLOptions.BodyForm { form.Set(f.Key, f.Value) } + for k, v := range settings.FormPostItems { + form.Set(k, v) + } body = strings.NewReader(form.Encode()) case "graphql": var variables map[string]interface{} diff --git a/pkg/infinity/request.go b/pkg/infinity/request.go index 6e3985ea8..bdca8b3ae 100644 --- a/pkg/infinity/request.go +++ b/pkg/infinity/request.go @@ -95,7 +95,7 @@ func NormalizeURL(u string) string { func (client *Client) GetExecutedURL(ctx context.Context, query models.Query) string { out := []string{} if query.Source != "inline" && query.Source != "azure-blob" { - req, err := GetRequest(ctx, client.Settings, GetQueryBody(ctx, query), query, map[string]string{}, false) + req, err := GetRequest(ctx, client.Settings, GetQueryBody(query, models.InfinitySettings{}), query, map[string]string{}, false) if err != nil { return fmt.Sprintf("error retrieving full url. %s", query.URL) } diff --git a/pkg/models/settings.go b/pkg/models/settings.go index e6ffd04d2..ada5e5c42 100644 --- a/pkg/models/settings.go +++ b/pkg/models/settings.go @@ -98,6 +98,7 @@ type InfinitySettings struct { ForwardOauthIdentity bool CustomHeaders map[string]string SecureQueryFields map[string]string + FormPostItems map[string]string InsecureSkipVerify bool ServerName string TimeoutInSeconds int64 @@ -171,6 +172,9 @@ func (s *InfinitySettings) HaveSecureHeaders() bool { } return false } + if len(s.FormPostItems) > 0 { + return true + } return false } @@ -294,6 +298,7 @@ func LoadSettings(ctx context.Context, config backend.DataSourceInstanceSettings } settings.CustomHeaders = GetSecrets(config, "httpHeaderName", "httpHeaderValue") settings.SecureQueryFields = GetSecrets(config, "secureQueryName", "secureQueryValue") + settings.FormPostItems = GetSecrets(config, "formPostItemName", "formPostItemValue") settings.OAuth2Settings.EndpointParams = GetSecrets(config, "oauth2EndPointParamsName", "oauth2EndPointParamsValue") if settings.AuthenticationMethod == "" { settings.AuthenticationMethod = AuthenticationMethodNone diff --git a/pkg/models/settings_test.go b/pkg/models/settings_test.go index 8019028dc..f97f1fe72 100644 --- a/pkg/models/settings_test.go +++ b/pkg/models/settings_test.go @@ -26,6 +26,7 @@ func TestLoadSettings(t *testing.T) { OAuth2Settings: models.OAuth2Settings{ EndpointParams: map[string]string{}, }, + FormPostItems: map[string]string{}, CustomHeaders: map[string]string{}, SecureQueryFields: map[string]string{}, }, @@ -45,12 +46,14 @@ func TestLoadSettings(t *testing.T) { JSONData: []byte(`{ "datasource_mode" : "advanced", "secureQueryName1" : "foo", + "formPostItemName1" : "hello", "httpHeaderName1" : "header1" }`), DecryptedSecureJSONData: map[string]string{ - "basicAuthPassword": "password", - "secureQueryValue1": "bar", - "httpHeaderValue1": "headervalue1", + "basicAuthPassword": "password", + "secureQueryValue1": "bar", + "formPostItemValue1": "world", + "httpHeaderValue1": "headervalue1", }, }, wantSettings: models.InfinitySettings{ @@ -66,6 +69,9 @@ func TestLoadSettings(t *testing.T) { OAuth2Settings: models.OAuth2Settings{ EndpointParams: map[string]string{}, }, + FormPostItems: map[string]string{ + "hello": "world", + }, CustomHeaders: map[string]string{ "header1": "headervalue1", }, @@ -105,6 +111,7 @@ func TestLoadSettings(t *testing.T) { OAuth2Settings: models.OAuth2Settings{ EndpointParams: map[string]string{}, }, + FormPostItems: map[string]string{}, CustomHeaders: map[string]string{ "header1": "headervalue1", }, @@ -144,6 +151,7 @@ func TestAllSettingsAgainstFrontEnd(t *testing.T) { "apiKeyType" : "query", "datasource_mode" : "advanced", "secureQueryName1" : "foo", + "formPostItemName1": "hello", "httpHeaderName1" : "header1", "timeoutInSeconds" : 30, "tlsAuth" : true, @@ -178,6 +186,7 @@ func TestAllSettingsAgainstFrontEnd(t *testing.T) { "tlsClientKey": "myTlsClientKey", "basicAuthPassword": "password", "secureQueryValue1": "bar", + "formPostItemValue1": "world", "httpHeaderValue1": "headervalue1", "apiKeyValue": "earth", "bearerToken": "myBearerToken", @@ -244,6 +253,9 @@ func TestAllSettingsAgainstFrontEnd(t *testing.T) { CustomHealthCheckEnabled: true, CustomHealthCheckUrl: "https://foo-check/", UnsecuredQueryHandling: models.UnsecuredQueryHandlingDeny, + FormPostItems: map[string]string{ + "hello": "world", + }, CustomHeaders: map[string]string{ "header1": "headervalue1", }, diff --git a/src/editors/config.editor.tsx b/src/editors/config.editor.tsx index d28ad3e7f..9ef70fe47 100644 --- a/src/editors/config.editor.tsx +++ b/src/editors/config.editor.tsx @@ -80,6 +80,16 @@ export const HeadersEditor = (props: DataSourcePluginOptionsEditorProps + + + @@ -138,7 +148,7 @@ export const MiscEditor = (props: DataSourcePluginOptionsEditorProps = [ { value: 'main', label: 'Main' }, { value: 'auth', label: 'Authentication' }, - { value: 'headers_and_params', label: 'Headers & URL params' }, + { value: 'http_options', label: 'HTTP Options' }, { value: 'network', label: 'Network' }, { value: 'security', label: 'Security' }, { value: 'health_check', label: 'Health check' }, @@ -214,7 +224,7 @@ export const InfinityConfigEditor = (props: DataSourcePluginOptionsEditorProps ) : activeTab === 'auth' ? ( - ) : activeTab === 'headers_and_params' ? ( + ) : activeTab === 'http_options' ? ( ) : activeTab === 'network' ? (