diff --git a/pkg/agent/handler/credential_test.go b/pkg/agent/handler/credential_test.go index d5c53713d..b101a8fa2 100644 --- a/pkg/agent/handler/credential_test.go +++ b/pkg/agent/handler/credential_test.go @@ -1031,6 +1031,7 @@ func createIDPConfig(s oauth.MockIDPServer) *config.IDPConfiguration { AuthMethod: config.ClientSecretBasic, AuthResponseType: oauth.AuthResponseToken, ExtraProperties: config.ExtraProperties{"key": "value"}, + LoggerOptions: &config.IDPLoggerOptions{}, } } @@ -1069,9 +1070,9 @@ func TestExternalCredentialOnPending(t *testing.T) { } p := &mockExternalCredProv{ - t: t, + t: t, expectedStatus: expectedStatus, - provisionErr: tc.provisionErr, + provisionErr: tc.provisionErr, } c := &credClient{ diff --git a/pkg/agent/idplifecycle_test.go b/pkg/agent/idplifecycle_test.go index 51a1e069a..fc20eaee2 100644 --- a/pkg/agent/idplifecycle_test.go +++ b/pkg/agent/idplifecycle_test.go @@ -19,8 +19,8 @@ import ( ) const ( - testEnvName = "test-env" - testIDPName = "test-idp" + testEnvName = "test-env" + testIDPName = "test-idp" existingIDPName = "existing-idp" ) @@ -75,6 +75,7 @@ func makeIDPConfig(metadataURL string) *config.IDPConfiguration { ClientScopes: "read", AuthMethod: config.ClientSecretBasic, AuthResponseType: oauth.AuthResponseToken, + LoggerOptions: &config.IDPLoggerOptions{}, } } diff --git a/pkg/agent/provisioning_test.go b/pkg/agent/provisioning_test.go index 7b157a29e..ca673a722 100644 --- a/pkg/agent/provisioning_test.go +++ b/pkg/agent/provisioning_test.go @@ -84,6 +84,7 @@ func TestNewCredentialRequestBuilder(t *testing.T) { AuthMethod: config.ClientSecretBasic, AuthResponseType: "token", ExtraProperties: config.ExtraProperties{"key": "value"}, + LoggerOptions: &config.IDPLoggerOptions{}, } p, _ := oauth.NewProvider(cfg, config.NewTLSConfig(), "", 30*time.Second) diff --git a/pkg/api/client.go b/pkg/api/client.go index e38301ff8..135e6899e 100644 --- a/pkg/api/client.go +++ b/pkg/api/client.go @@ -31,6 +31,11 @@ const ( responseBufferSize = 2048 ) +type RequestOptions struct { + LogReqBody bool + LogResBody bool +} + // Request - the request object used when communicating to an API type Request struct { Method string @@ -39,6 +44,7 @@ type Request struct { Headers map[string]string Body []byte FormData map[string]string + Options RequestOptions } // Response - the response object given back when communicating to an API @@ -311,6 +317,7 @@ func (c *httpClient) Send(request Request) (*Response, error) { // Logging for the HTTP request statusCode := 0 receivedData := int64(0) + responseBody := []byte{} defer func() { duration := time.Since(startTime) targetURL := req.URL.String() @@ -336,6 +343,14 @@ func (c *httpClient) Send(request Request) (*Response, error) { logger = logger.WithField("received(bytes)", receivedData) } + if request.Options.LogReqBody && len(request.Body) > 0 { + logger = logger.WithField("requestBody", string(request.Body)) + } + + if request.Options.LogResBody && len(responseBody) > 0 { + logger = logger.WithField("responseBody", string(responseBody)) + } + if err != nil { logger.WithError(err). Trace("request failed") @@ -365,5 +380,9 @@ func (c *httpClient) Send(request Request) (*Response, error) { receivedData = res.ContentLength parseResponse, err := c.prepareAPIResponse(res, timer) + if responseBody != nil { + responseBody = parseResponse.Body + } + return parseResponse, err } diff --git a/pkg/apic/provisioning/idp/provisioner_test.go b/pkg/apic/provisioning/idp/provisioner_test.go index e304c6b5a..d50e4c8f5 100644 --- a/pkg/apic/provisioning/idp/provisioner_test.go +++ b/pkg/apic/provisioning/idp/provisioner_test.go @@ -32,9 +32,10 @@ func TestProvisioner(t *testing.T) { ClientSecret: "test", UseRegistrationToken: true, }, - GrantType: oauth.GrantTypeClientCredentials, - AuthMethod: config.ClientSecretBasic, - MetadataURL: s.GetMetadataURL(), + GrantType: oauth.GrantTypeClientCredentials, + AuthMethod: config.ClientSecretBasic, + MetadataURL: s.GetMetadataURL(), + LoggerOptions: &config.IDPLoggerOptions{}, } s.SetMetadataResponseCode(http.StatusOK) diff --git a/pkg/authz/oauth/idpregistry_test.go b/pkg/authz/oauth/idpregistry_test.go index e5d906993..abfa46614 100644 --- a/pkg/authz/oauth/idpregistry_test.go +++ b/pkg/authz/oauth/idpregistry_test.go @@ -63,11 +63,12 @@ func TestRegisterProviderWithMetadata(t *testing.T) { reg := NewIdpRegistry() idpCfg := &config.IDPConfiguration{ - Name: "test-idp", - MetadataURL: idpServer.GetMetadataURL(), - AuthConfig: &config.IDPAuthConfiguration{Type: "client", ClientID: "id", ClientSecret: "secret"}, - GrantType: GrantTypeClientCredentials, - AuthMethod: config.ClientSecretBasic, + Name: "test-idp", + MetadataURL: idpServer.GetMetadataURL(), + AuthConfig: &config.IDPAuthConfiguration{Type: "client", ClientID: "id", ClientSecret: "secret"}, + GrantType: GrantTypeClientCredentials, + AuthMethod: config.ClientSecretBasic, + LoggerOptions: &config.IDPLoggerOptions{}, } err := reg.RegisterProviderWithMetadata(context.Background(), idpCfg, tc.metadata, config.NewTLSConfig(), "", 30*time.Second) diff --git a/pkg/authz/oauth/oktaprovider_test.go b/pkg/authz/oauth/oktaprovider_test.go index ce4594d7b..556cf8cbb 100644 --- a/pkg/authz/oauth/oktaprovider_test.go +++ b/pkg/authz/oauth/oktaprovider_test.go @@ -163,8 +163,9 @@ func oktaPolicyPutMustIncludeClientHandler(clientID string) http.HandlerFunc { func newIDPCredential(tsURL, group, policy string) *corecfg.IDPConfiguration { credentialObj := &corecfg.IDPConfiguration{ - MetadataURL: tsURL + oauthMetadataEndpoint, - AuthConfig: &corecfg.IDPAuthConfiguration{AccessToken: accessToken}, + MetadataURL: tsURL + oauthMetadataEndpoint, + AuthConfig: &corecfg.IDPAuthConfiguration{AccessToken: accessToken}, + LoggerOptions: &corecfg.IDPLoggerOptions{}, } if strings.TrimSpace(group) != "" || strings.TrimSpace(policy) != "" { credentialObj.Okta = &corecfg.OktaIDPConfiguration{Group: group, Policy: policy} @@ -325,9 +326,10 @@ func TestOktaPostProcessClientUnreg(t *testing.T) { defer ts.Close() credentialObj := &corecfg.IDPConfiguration{ - MetadataURL: ts.URL + oauthMetadataEndpoint, - Okta: &corecfg.OktaIDPConfiguration{Group: tc.oktaGroup}, - AuthConfig: &corecfg.IDPAuthConfiguration{AccessToken: accessToken}, + MetadataURL: ts.URL + oauthMetadataEndpoint, + Okta: &corecfg.OktaIDPConfiguration{Group: tc.oktaGroup}, + AuthConfig: &corecfg.IDPAuthConfiguration{AccessToken: accessToken}, + LoggerOptions: &corecfg.IDPLoggerOptions{}, } err := oktaProvider.postProcessClientUnregister("app123", credentialObj, apiClient) if tc.wantErr { @@ -355,8 +357,9 @@ func TestOktaPostProcessClientRegUsesIDPAccessToken(t *testing.T) { defer ts.Close() credentialObj := &corecfg.IDPConfiguration{ - MetadataURL: ts.URL + oauthMetadataEndpoint, - AuthConfig: &corecfg.IDPAuthConfiguration{AccessToken: accessToken}, + MetadataURL: ts.URL + oauthMetadataEndpoint, + AuthConfig: &corecfg.IDPAuthConfiguration{AccessToken: accessToken}, + LoggerOptions: &corecfg.IDPLoggerOptions{}, } apiClient := coreapi.NewClient(nil, "") diff --git a/pkg/authz/oauth/provider.go b/pkg/authz/oauth/provider.go index f83bbfe2f..1fb83c6b5 100644 --- a/pkg/authz/oauth/provider.go +++ b/pkg/authz/oauth/provider.go @@ -44,17 +44,18 @@ type Provider interface { } type provider struct { - logger log.FieldLogger - cfg corecfg.IDPConfig - metadataURL string - extraProperties map[string]interface{} - requestHeaders map[string]string - queryParameters map[string]string - apiClient coreapi.Client - authServerMetadata *AuthorizationServerMetadata - authClient AuthClient - idpType typedIDP - idpResourceName *string + logger log.FieldLogger + cfg corecfg.IDPConfig + metadataURL string + extraProperties map[string]interface{} + requestHeaders map[string]string + queryParameters map[string]string + apiClient coreapi.Client + authServerMetadata *AuthorizationServerMetadata + authClient AuthClient + idpType typedIDP + idpResourceName *string + logRequestAndResponse bool } type typedIDP interface { @@ -103,16 +104,17 @@ func NewProvider(idp corecfg.IDPConfig, tlsCfg corecfg.TLSConfig, proxyURL strin idpResourceName := new(string) p := &provider{ - logger: logger, - metadataURL: idp.GetMetadataURL(), - cfg: idp, - extraProperties: extraProps, - requestHeaders: idp.GetRequestHeaders(), - queryParameters: idp.GetQueryParams(), - apiClient: apiClient, - idpType: idpType, - authServerMetadata: pOpts.authServerMetadata, - idpResourceName: idpResourceName, + logger: logger, + metadataURL: idp.GetMetadataURL(), + cfg: idp, + extraProperties: extraProps, + requestHeaders: idp.GetRequestHeaders(), + queryParameters: idp.GetQueryParams(), + apiClient: apiClient, + idpType: idpType, + authServerMetadata: pOpts.authServerMetadata, + idpResourceName: idpResourceName, + logRequestAndResponse: idp.GetLoggingConfig().LogRequestResponse(), } if p.authServerMetadata == nil { @@ -154,13 +156,17 @@ func NewProvider(idp corecfg.IDPConfig, tlsCfg corecfg.TLSConfig, proxyURL strin return p, nil } -func FetchMetadata(apiClient coreapi.Client, metadataURL string) (*AuthorizationServerMetadata, error) { +func FetchMetadata(apiClient coreapi.Client, metadataURL string, logReqAndRes bool) (*AuthorizationServerMetadata, error) { if apiClient == nil || metadataURL == "" { return nil, errors.New("unexpected arguments") } request := coreapi.Request{ Method: coreapi.GET, URL: metadataURL, + Options: coreapi.RequestOptions{ + LogReqBody: logReqAndRes, + LogResBody: logReqAndRes, + }, } response, err := apiClient.Send(request) @@ -178,7 +184,7 @@ func FetchMetadata(apiClient coreapi.Client, metadataURL string) (*Authorization } func (p *provider) fetchMetadata() (*AuthorizationServerMetadata, error) { - return FetchMetadata(p.apiClient, p.metadataURL) + return FetchMetadata(p.apiClient, p.metadataURL, p.logRequestAndResponse) } func (p *provider) createAuthClient() (AuthClient, error) { @@ -374,6 +380,8 @@ func (p *provider) prepareHeaders(authPrefix, token string) map[string]string { // RegisterClient - register the OAuth client with IDP func (p *provider) RegisterClient(clientReq ClientMetadata) (ClientMetadata, error) { + logger := p.logger.WithField(logProvider, p.cfg.GetIDPName()) + authPrefix := p.idpType.getAuthorizationHeaderPrefix() err := p.enrichClientReq(clientReq) if err != nil { @@ -385,6 +393,19 @@ func (p *provider) RegisterClient(clientReq ClientMetadata) (ClientMetadata, err return nil, err } + logger = logger. + WithField("clientName", clientReq.GetClientName()). + WithField("grantType", clientReq.GetGrantTypes()). + WithField("tokenAuthMethod", clientReq.GetTokenEndpointAuthMethod()) + + if len(clientReq.GetScopes()) > 0 { + logger = logger.WithField("requestScopes", clientReq.GetScopes()) + } + + if len(clientReq.GetRedirectURIs()) > 0 { + logger = logger.WithField("requestRedirectURIs", clientReq.GetRedirectURIs()) + } + token, err := p.getClientToken() if err != nil { return nil, err @@ -396,8 +417,13 @@ func (p *provider) RegisterClient(clientReq ClientMetadata) (ClientMetadata, err QueryParams: p.queryParameters, Headers: p.prepareHeaders(authPrefix, token), Body: clientBuffer, + Options: coreapi.RequestOptions{ + LogReqBody: p.logRequestAndResponse, + LogResBody: p.logRequestAndResponse, + }, } + logger.Debug("requesting client") response, err := p.apiClient.Send(request) if err != nil { return nil, err @@ -414,15 +440,19 @@ func (p *provider) RegisterClient(clientReq ClientMetadata) (ClientMetadata, err return nil, err } - p.logger. - WithField(logProvider, p.cfg.GetIDPName()). - WithField("clientName", clientReq.GetClientName()). - WithField(logClientID, clientReq.GetClientID()). - WithField("grantType", clientReq.GetGrantTypes()). - WithField("tokenAuthMethod", clientReq.GetTokenEndpointAuthMethod()). - WithField("responseType", clientReq.GetResponseTypes()). - WithField("redirectURIs", clientReq.GetRedirectURIs()). - Info("registered client") + if len(clientRes.GetResponseTypes()) > 0 { + logger = logger.WithField("responseTypes", clientRes.GetResponseTypes()) + } + + if len(clientRes.GetScopes()) > 0 { + logger = logger.WithField("responseScopes", clientRes.GetScopes()) + } + + if len(clientRes.GetRedirectURIs()) > 0 { + logger = logger.WithField("responseRedirectURIs", clientRes.GetRedirectURIs()) + } + + logger.WithField(logClientID, clientRes.GetClientID()).Info("registered client") return clientRes, err } @@ -659,6 +689,10 @@ func (p *provider) tryUnregister(unregisterURL, clientID, authPrefix, accessToke URL: unregisterURL, QueryParams: queryParams, Headers: p.prepareHeaders(authPrefix, accessToken), + Options: coreapi.RequestOptions{ + LogReqBody: p.logRequestAndResponse, + LogResBody: p.logRequestAndResponse, + }, } response, err := p.apiClient.Send(request) diff --git a/pkg/authz/oauth/provider_test.go b/pkg/authz/oauth/provider_test.go index 3bf5e0a57..9687bf262 100644 --- a/pkg/authz/oauth/provider_test.go +++ b/pkg/authz/oauth/provider_test.go @@ -191,6 +191,7 @@ func runProviderTestCase(t *testing.T, tc providerTestCase) { ExtraProperties: config.ExtraProperties{"key": "value"}, RequestHeaders: tc.headers, QueryParams: tc.queryParams, + LoggerOptions: &config.IDPLoggerOptions{}, } s.SetMetadataResponseCode(tc.metadataResponseCode) @@ -306,6 +307,7 @@ func TestNewProviderValidatesExtraProperties(t *testing.T) { Type: config.AccessToken, AccessToken: testToken, }, + LoggerOptions: &config.IDPLoggerOptions{}, } provider, err := NewProvider(idpCfg, config.NewTLSConfig(), "", 10*time.Second) @@ -366,6 +368,7 @@ func TestNewProviderOktaValidatesConfiguredGroupAndPolicyExist(t *testing.T) { Type: config.AccessToken, AccessToken: token, }, + LoggerOptions: &config.IDPLoggerOptions{}, } p, err := NewProvider(idpCfg, config.NewTLSConfig(), "", 10*time.Second) @@ -400,6 +403,7 @@ func TestNewProviderOktaFailsFastWhenConfiguredGroupMissing(t *testing.T) { Type: config.AccessToken, AccessToken: token, }, + LoggerOptions: &config.IDPLoggerOptions{}, } p, err := NewProvider(idpCfg, config.NewTLSConfig(), "", 10*time.Second) @@ -490,6 +494,7 @@ func TestRegisterClientRollBack(t *testing.T) { Type: config.AccessToken, AccessToken: testToken, }, + LoggerOptions: &config.IDPLoggerOptions{}, } pIntf, err := NewProvider(idpCfg, config.NewTLSConfig(), "", 10*time.Second) @@ -537,6 +542,7 @@ func TestUnregisterClientDeleteHookFails(t *testing.T) { Type: config.AccessToken, AccessToken: testToken, }, + LoggerOptions: &config.IDPLoggerOptions{}, } pIntf, err := NewProvider(idpCfg, config.NewTLSConfig(), "", 10*time.Second) @@ -580,6 +586,7 @@ func TestUnregisterClientCleanupAndDeleteFail(t *testing.T) { Type: config.AccessToken, AccessToken: testToken, }, + LoggerOptions: &config.IDPLoggerOptions{}, } pIntf, err := NewProvider(idpCfg, config.NewTLSConfig(), "", 10*time.Second) diff --git a/pkg/authz/oauth/providerregistry_test.go b/pkg/authz/oauth/providerregistry_test.go index c0aff26e1..65adfec00 100644 --- a/pkg/authz/oauth/providerregistry_test.go +++ b/pkg/authz/oauth/providerregistry_test.go @@ -13,8 +13,9 @@ import ( func createIDPConfig(name, metadataURL string) *config.IDPConfiguration { return &config.IDPConfiguration{ - Name: name, - MetadataURL: metadataURL, + Name: name, + MetadataURL: metadataURL, + LoggerOptions: &config.IDPLoggerOptions{}, } } diff --git a/pkg/config/externalidpconfig.go b/pkg/config/externalidpconfig.go index 0f6b0fb04..a4c11433f 100644 --- a/pkg/config/externalidpconfig.go +++ b/pkg/config/externalidpconfig.go @@ -22,6 +22,7 @@ const ( propInsecureSkipVerify = "insecureSkipVerify" propUseCachedToken = "useCachedToken" propUseRegistrationToken = "useRegistrationToken" + propLogRequestAndResponse = "requestAndResponse" pathExternalIDP = "agentFeatures.idp" fldName = "name" fldTitle = "title" @@ -53,6 +54,8 @@ const ( fldSSLRootCACertPath = "ssl.rootCACertPath" fldSSLClientCertPath = "ssl.clientCertPath" fldSSLClientKeyPath = "ssl.clientKeyPath" + + fldLogRequestResponse = "log." + propLogRequestAndResponse ) var configProperties = []string{ @@ -86,6 +89,7 @@ var configProperties = []string{ fldAuthTokenSigningMethod, fldAuthUseCachedToken, fldAuthUseRegistrationToken, + fldLogRequestResponse, } var validIDPAuthType = map[string]bool{ @@ -224,6 +228,11 @@ type IDPAuthConfig interface { GetQueryParams() map[string]string } +// IDPLoggingConfig - interface for IdP logger config +type IDPLoggingConfig interface { + LogRequestResponse() bool +} + // IDPConfig - interface for IdP provider config type IDPConfig interface { // GetMetadataURL - URL exposed by OAuth authorization server to provide metadata information @@ -256,6 +265,8 @@ type IDPConfig interface { GetQueryParams() map[string]string // GetTLSConfig - tls config for IDP connection GetTLSConfig() TLSConfig + // GetLoggingConfig - logging options + GetLoggingConfig() IDPLoggingConfig // validate - Validates the IDP configuration validate() } @@ -283,6 +294,11 @@ type OktaIDPConfiguration struct { Policy string `json:"policy,omitempty"` } +// IDPLoggerOptions - logging options for IdP api requests +type IDPLoggerOptions struct { + RequestResponse bool `json:"-"` +} + // IDPConfiguration - Structure to hold the IdP provider config type IDPConfiguration struct { Name string `json:"name,omitempty"` @@ -298,6 +314,7 @@ type IDPConfiguration struct { ExtraProperties ExtraProperties `json:"extraProperties,omitempty"` RequestHeaders IDPRequestHeaders `json:"requestHeaders,omitempty"` QueryParams IDPQueryParams `json:"queryParams,omitempty"` + LoggerOptions IDPLoggingConfig `json:"log,omitempty"` TLSConfig TLSConfig `json:"-"` } @@ -382,12 +399,17 @@ func (i *IDPConfiguration) GetTLSConfig() TLSConfig { return i.TLSConfig } +func (i *IDPConfiguration) GetLoggingConfig() IDPLoggingConfig { + return i.LoggerOptions +} + // UnmarshalJSON - custom unmarshaler for IDPConfiguration struct func (i *IDPConfiguration) UnmarshalJSON(data []byte) error { type Alias IDPConfiguration i.RequestHeaders = make(IDPRequestHeaders) i.QueryParams = make(IDPQueryParams) i.ExtraProperties = make(ExtraProperties) + i.LoggerOptions = &IDPLoggerOptions{} i.AuthConfig = &IDPAuthConfiguration{ RequestHeaders: make(IDPRequestHeaders), @@ -406,6 +428,11 @@ func (i *IDPConfiguration) UnmarshalJSON(data []byte) error { json.Unmarshal(buf, i.AuthConfig) } + if v, ok := b["log"]; ok { + buf, _ := json.Marshal(v) + json.Unmarshal(buf, &i.LoggerOptions) + } + return nil } @@ -627,6 +654,53 @@ func (i *IDPAuthConfiguration) validateTLSClientAuthConfig(tlsCfg TLSConfig) { validateAuthFileConfig(pathExternalIDP+"."+fldSSLClientKeyPath, tlsCfg.(*TLSConfiguration).ClientKeyPath, "", "tls client key") } +// LogRequestResponse - +func (i *IDPLoggerOptions) LogRequestResponse() bool { + return i.RequestResponse +} + +// UnmarshalJSON - custom unmarshaler for IDPAuthConfiguration struct +func (i *IDPLoggerOptions) UnmarshalJSON(data []byte) error { + type Alias IDPLoggerOptions // Create an intermittent type to unmarshal the base attributes + + if err := json.Unmarshal(data, &struct{ *Alias }{Alias: (*Alias)(i)}); err != nil { + return err + } + + var allFields interface{} + json.Unmarshal(data, &allFields) + b := allFields.(map[string]interface{}) + + // Default to use not use registration access token + i.RequestResponse = false + if v, ok := b[propLogRequestAndResponse]; ok { + i.RequestResponse = (v == "true") + } + return nil +} + +// MarshalJSON - custom marshaler for Application struct +func (i *IDPLoggerOptions) MarshalJSON() ([]byte, error) { + type Alias IDPLoggerOptions // Create an intermittent type to marshal the base attributes + + app, err := json.Marshal(&struct{ *Alias }{Alias: (*Alias)(i)}) + if err != nil { + return nil, err + } + + // decode it back to get a map + var allFields interface{} + json.Unmarshal(app, &allFields) + b := allFields.(map[string]interface{}) + + if i.RequestResponse { + b[propLogRequestAndResponse] = "true" + } + + // Return encoding of the map + return json.Marshal(b) +} + func addExternalIDPProperties(props properties.Properties) { props.AddObjectSliceProperty(pathExternalIDP, configProperties) } diff --git a/pkg/config/externalidpconfig_test.go b/pkg/config/externalidpconfig_test.go index bd09d3514..9dca56ce2 100644 --- a/pkg/config/externalidpconfig_test.go +++ b/pkg/config/externalidpconfig_test.go @@ -52,6 +52,9 @@ func assertIDPRoundTrip(t *testing.T, idp IDPConfig, expectedOktaGroup string, e assert.Equal(t, idp.GetAuthConfig().GetClientSecret(), parsedIDP.GetAuthConfig().GetClientSecret()) assert.Equal(t, len(idp.GetAuthConfig().GetRequestHeaders()), len(parsedIDP.GetAuthConfig().GetRequestHeaders())) assert.Equal(t, len(idp.GetAuthConfig().GetQueryParams()), len(parsedIDP.GetAuthConfig().GetQueryParams())) + if idp.GetLoggingConfig() != nil { + assert.Equal(t, idp.GetLoggingConfig().LogRequestResponse(), parsedIDP.GetLoggingConfig().LogRequestResponse()) + } if expectedOktaGroup != "" { assert.Equal(t, expectedOktaGroup, idp.GetOktaGroup()) @@ -204,11 +207,141 @@ func TestExternalIDPConfig(t *testing.T) { }, hasError: false, }, + { + name: "log request/response disabled by default", + envNames: map[string]string{ + "AGENTFEATURES_IDP_NAME_1": "test", + "AGENTFEATURES_IDP_METADATAURL_1": "test", + "AGENTFEATURES_IDP_AUTH_TYPE_1": "accessToken", + "AGENTFEATURES_IDP_AUTH_ACCESSTOKEN_1": "accessToken", + }, + hasError: false, + }, + { + name: "log request/response enabled via env var", + envNames: map[string]string{ + "AGENTFEATURES_IDP_NAME_1": "test", + "AGENTFEATURES_IDP_METADATAURL_1": "test", + "AGENTFEATURES_IDP_AUTH_TYPE_1": "accessToken", + "AGENTFEATURES_IDP_AUTH_ACCESSTOKEN_1": "accessToken", + "AGENTFEATURES_IDP_LOG_REQUESTANDRESPONSE_1": "true", + }, + hasError: false, + }, } for _, tc := range testCases { - tc := tc t.Run(tc.name, func(t *testing.T) { runExternalIDPTestCase(t, tc) }) } } + +func TestIDPLoggerOptions(t *testing.T) { + t.Run("default is false", func(t *testing.T) { + opts := &IDPLoggerOptions{} + assert.False(t, opts.LogRequestResponse()) + }) + + t.Run("unmarshal requestAndResponse true", func(t *testing.T) { + opts := &IDPLoggerOptions{} + assert.NoError(t, json.Unmarshal([]byte(`{"requestAndResponse":"true"}`), opts)) + assert.True(t, opts.LogRequestResponse()) + }) + + t.Run("unmarshal requestAndResponse false", func(t *testing.T) { + opts := &IDPLoggerOptions{} + assert.NoError(t, json.Unmarshal([]byte(`{"requestAndResponse":"false"}`), opts)) + assert.False(t, opts.LogRequestResponse()) + }) + + t.Run("unmarshal missing requestAndResponse defaults to false", func(t *testing.T) { + opts := &IDPLoggerOptions{} + assert.NoError(t, json.Unmarshal([]byte(`{}`), opts)) + assert.False(t, opts.LogRequestResponse()) + }) + + t.Run("marshal with requestAndResponse true includes field", func(t *testing.T) { + opts := &IDPLoggerOptions{RequestResponse: true} + buf, err := json.Marshal(opts) + assert.NoError(t, err) + var m map[string]interface{} + assert.NoError(t, json.Unmarshal(buf, &m)) + assert.Equal(t, "true", m["requestAndResponse"]) + }) + + t.Run("marshal with requestAndResponse false omits field", func(t *testing.T) { + opts := &IDPLoggerOptions{RequestResponse: false} + buf, err := json.Marshal(opts) + assert.NoError(t, err) + var m map[string]interface{} + assert.NoError(t, json.Unmarshal(buf, &m)) + _, exists := m["requestAndResponse"] + assert.False(t, exists) + }) + + t.Run("round-trip preserves true", func(t *testing.T) { + opts := &IDPLoggerOptions{RequestResponse: true} + buf, err := json.Marshal(opts) + assert.NoError(t, err) + opts2 := &IDPLoggerOptions{} + assert.NoError(t, json.Unmarshal(buf, opts2)) + assert.True(t, opts2.LogRequestResponse()) + }) + + t.Run("round-trip preserves false", func(t *testing.T) { + opts := &IDPLoggerOptions{RequestResponse: false} + buf, err := json.Marshal(opts) + assert.NoError(t, err) + opts2 := &IDPLoggerOptions{} + assert.NoError(t, json.Unmarshal(buf, opts2)) + assert.False(t, opts2.LogRequestResponse()) + }) +} + +func TestIDPConfigurationGetLoggingConfig(t *testing.T) { + testCases := []struct { + name string + idp *IDPConfiguration + jsonData string + expectNil bool + expectLogReqResp bool + }{ + { + name: "GetLoggingConfig returns nil when not set", + idp: &IDPConfiguration{}, + expectNil: true, + }, + { + name: "GetLoggingConfig returns logger options when set", + idp: &IDPConfiguration{ + LoggerOptions: &IDPLoggerOptions{RequestResponse: true}, + }, + expectLogReqResp: true, + }, + { + name: "UnmarshalJSON initializes LoggerOptions", + jsonData: `{"name":"test","metadataUrl":"http://example.com","log":{"requestAndResponse":"true"}}`, + expectLogReqResp: true, + }, + { + name: "UnmarshalJSON sets LoggerOptions default false when log absent", + jsonData: `{"name":"test","metadataUrl":"http://example.com"}`, + expectLogReqResp: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + idp := tc.idp + if tc.jsonData != "" { + idp = &IDPConfiguration{} + assert.NoError(t, json.Unmarshal([]byte(tc.jsonData), idp)) + } + if tc.expectNil { + assert.Nil(t, idp.GetLoggingConfig()) + return + } + assert.NotNil(t, idp.GetLoggingConfig()) + assert.Equal(t, tc.expectLogReqResp, idp.GetLoggingConfig().LogRequestResponse()) + }) + } +}