From 541d16b5a68426a61bede53cfd43686dd7dae380 Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 3 Apr 2026 13:56:25 +0000 Subject: [PATCH] feat(ansible): support uri basic auth flags Co-Authored-By: Virgil --- mock_ssh_test.go | 12 ++++++++++++ modules.go | 14 ++++++++++++++ modules_adv_test.go | 18 ++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/mock_ssh_test.go b/mock_ssh_test.go index cdf62a5..459ea9f 100644 --- a/mock_ssh_test.go +++ b/mock_ssh_test.go @@ -1813,6 +1813,9 @@ func moduleURIWithClient(_ *Executor, client sshRunner, args map[string]any) (*T dest := getStringArg(args, "dest", "") timeout := getIntArg(args, "timeout", 0) validateCerts := getBoolArg(args, "validate_certs", true) + urlUsername := getStringArg(args, "url_username", "") + urlPassword := getStringArg(args, "url_password", "") + forceBasicAuth := getBoolArg(args, "force_basic_auth", false) if url == "" { return nil, mockError("moduleURIWithClient", "uri: url required") @@ -1822,6 +1825,15 @@ func moduleURIWithClient(_ *Executor, client sshRunner, args map[string]any) (*T curlOpts = append(curlOpts, "-s", "-S") curlOpts = append(curlOpts, "-X", method) + if urlUsername != "" || urlPassword != "" { + curlOpts = append(curlOpts, "-u", shellQuote(urlUsername+":"+urlPassword)) + if forceBasicAuth { + curlOpts = append(curlOpts, "--basic") + } + } else if forceBasicAuth { + curlOpts = append(curlOpts, "--basic") + } + // Headers if headers, ok := args["headers"].(map[string]any); ok { for k, v := range headers { diff --git a/modules.go b/modules.go index df58627..b2f7b03 100644 --- a/modules.go +++ b/modules.go @@ -1587,6 +1587,9 @@ func (e *Executor) moduleURI(ctx context.Context, client sshExecutorClient, args dest := getStringArg(args, "dest", "") timeout := getIntArg(args, "timeout", 0) validateCerts := getBoolArg(args, "validate_certs", true) + urlUsername := getStringArg(args, "url_username", "") + urlPassword := getStringArg(args, "url_password", "") + forceBasicAuth := getBoolArg(args, "force_basic_auth", false) if url == "" { return nil, coreerr.E("Executor.moduleURI", "url required", nil) @@ -1596,6 +1599,17 @@ func (e *Executor) moduleURI(ctx context.Context, client sshExecutorClient, args curlOpts = append(curlOpts, "-s", "-S") curlOpts = append(curlOpts, "-X", method) + // Basic auth is modelled explicitly so callers do not need to embed + // credentials in the URL. + if urlUsername != "" || urlPassword != "" { + curlOpts = append(curlOpts, "-u", shellQuote(urlUsername+":"+urlPassword)) + if forceBasicAuth { + curlOpts = append(curlOpts, "--basic") + } + } else if forceBasicAuth { + curlOpts = append(curlOpts, "--basic") + } + // Headers if headers, ok := args["headers"].(map[string]any); ok { for k, v := range headers { diff --git a/modules_adv_test.go b/modules_adv_test.go index d771aba..bb69ebb 100644 --- a/modules_adv_test.go +++ b/modules_adv_test.go @@ -1517,6 +1517,24 @@ func TestModulesAdv_ModuleURI_Good_PostWithBodyAndHeaders(t *testing.T) { assert.True(t, mock.containsSubstring("Authorization")) } +func TestModulesAdv_ModuleURI_Good_UsesBasicAuthFlags(t *testing.T) { + e, mock := newTestExecutorWithMock("host1") + mock.expectCommand(`curl.*secure\.example\.com`, "OK\n200", "", 0) + + result, err := moduleURIWithClient(e, mock, map[string]any{ + "url": "https://secure.example.com/api", + "url_username": "apiuser", + "url_password": "apipass", + "force_basic_auth": true, + }) + + require.NoError(t, err) + assert.False(t, result.Failed) + assert.Equal(t, 200, result.RC) + assert.True(t, mock.hasExecuted(`-u .*apiuser:apipass`)) + assert.True(t, mock.hasExecuted(`--basic`)) +} + func TestModulesAdv_ModuleURI_Good_FormURLEncodedBody(t *testing.T) { e, mock := newTestExecutorWithMock("host1") mock.expectCommand(`curl.*form\.example\.com`, "created\n201", "", 0)