diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 74c28efc4c..44554a5357 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -167,8 +167,8 @@ global: version: "v20231121-e91239f0" name: compass-pairing-adapter director: - dir: prod/incubator/ - version: "v20231128-76050135" + dir: dev/incubator/ + version: "PR-3515" name: compass-director hydrator: dir: prod/incubator/ @@ -216,16 +216,16 @@ global: dir: version: "0a651695" external_services_mock: - dir: prod/incubator/ - version: "v20231128-10ebaa4f" + dir: dev/incubator/ + version: "PR-3515" name: compass-external-services-mock console: dir: prod/incubator/ version: "v20230421-e8840c18" name: compass-console e2e_tests: - dir: prod/incubator/ - version: "v20231128-10ebaa4f" + dir: dev/incubator/ + version: "PR-3515" name: compass-e2e-tests isLocalEnv: false isForTesting: false diff --git a/components/director/internal/destinationcreator/fixtures_test.go b/components/director/internal/destinationcreator/fixtures_test.go index 0d785c3684..4fa184157c 100644 --- a/components/director/internal/destinationcreator/fixtures_test.go +++ b/components/director/internal/destinationcreator/fixtures_test.go @@ -25,6 +25,7 @@ const ( samlAssertionDestName = "test-saml-assertion-dest-name" samlAssertionDestURL = "test-saml-assertion-dest-url" clientCertAuthDestName = "test-client-cert-auth-dest-name" + oauth2ClientCredsDestName = "test-oauth2-client-creds-dest-name" destinationDescription = "test-dest-description" destinationTypeHTTP = string(destinationcreatorpkg.TypeHTTP) destinationProxyTypeInternet = string(destinationcreatorpkg.ProxyTypeInternet) @@ -38,6 +39,10 @@ const ( basicDestURL = "basic-url" basicDestUser = "basic-user" basicDestPassword = "basic-pwd" + oauth2ClientCredsURL = "oauth2-url" + oauth2ClientCredsTokenURL = "oauth2-token-url" + oauth2ClientCredsClientID = "oauth2-client-id" + oauth2ClientCredsClientSecret = "oauth2-client-secret" // Destination Certificate constants certificateName = "testCertificateName" @@ -75,6 +80,7 @@ var ( appTemplateID = "testAppTemplateID" appBaseURL = "http://app-test-base-url" appEmptyBaseURL = "" + testURLPath = "/test/path" TestEmptyErrorValueRawJSON = json.RawMessage(`\"\"`) TestConfigValueRawJSON = json.RawMessage(`{"configKey":"configValue"}`) @@ -172,6 +178,14 @@ func fixDestinationConfig() *destinationcreator.Config { } } +func fixDestinationInfo(authType, destType, url string) *destinationcreatorpkg.DestinationInfo { + return &destinationcreatorpkg.DestinationInfo{ + AuthenticationType: destinationcreatorpkg.AuthType(authType), + Type: destinationcreatorpkg.Type(destType), + URL: url, + } +} + func fixDesignTimeDestinationDetails() operators.Destination { return fixDestinationDetails(designTimeDestName, string(destinationcreatorpkg.AuthTypeNoAuth), destinationExternalSubaccountID) } @@ -194,6 +208,12 @@ func fixClientCertAuthDestinationDetails() operators.Destination { return fixDestinationDetails(clientCertAuthDestName, string(destinationcreatorpkg.AuthTypeClientCertificate), destinationExternalSubaccountID) } +func fixOAuth2ClientCredsDestinationDetails() operators.Destination { + dest := fixDestinationDetails(oauth2ClientCredsDestName, string(destinationcreatorpkg.AuthTypeOAuth2ClientCredentials), destinationExternalSubaccountID) + dest.TokenServiceURLType = string(destinationcreatorpkg.DedicatedTokenServiceURLType) + return dest +} + func fixDestinationsDetailsWithoutSubaccountID() []operators.Destination { return []operators.Destination{ fixDestinationDetails(samlAssertionDestName, string(destinationcreatorpkg.AuthTypeSAMLAssertion), ""), @@ -233,6 +253,15 @@ func fixSAMLAssertionAuthCreds(url string) *operators.SAMLAssertionAuthenticatio } } +func fixOAuth2ClientCreds(url, tokenServiceURL, clientID, clientSecret string) *operators.OAuth2ClientCredentialsAuthentication { + return &operators.OAuth2ClientCredentialsAuthentication{ + URL: url, + TokenServiceURL: tokenServiceURL, + ClientID: clientID, + ClientSecret: clientSecret, + } +} + func fixClientCertAuthTypeCreds() *operators.ClientCertAuthentication { return &operators.ClientCertAuthentication{URL: destinationURL} } diff --git a/components/director/internal/destinationcreator/service.go b/components/director/internal/destinationcreator/service.go index 6da5ac83c1..ebfdd6c06f 100644 --- a/components/director/internal/destinationcreator/service.go +++ b/components/director/internal/destinationcreator/service.go @@ -165,11 +165,11 @@ func (s *Service) CreateDesignTimeDestinations(ctx context.Context, destinationD } // CreateBasicCredentialDestinations is responsible to create a basic destination resource in the remote destination service -func (s *Service) CreateBasicCredentialDestinations(ctx context.Context, destinationDetails operators.Destination, basicAuthenticationCredentials operators.BasicAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) error { +func (s *Service) CreateBasicCredentialDestinations(ctx context.Context, destinationDetails operators.Destination, basicAuthenticationCredentials operators.BasicAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) (*destinationcreatorpkg.DestinationInfo, error) { subaccountID := destinationDetails.SubaccountID region, err := s.getRegionLabel(ctx, subaccountID) if err != nil { - return errors.Wrapf(err, "while getting region label for tenant with ID: %s", subaccountID) + return nil, errors.Wrapf(err, "while getting region label for tenant with ID: %s", subaccountID) } destinationName := destinationDetails.Name @@ -180,43 +180,47 @@ func (s *Service) CreateBasicCredentialDestinations(ctx context.Context, destina InstanceID: destinationDetails.InstanceID, }, false) if err != nil { - return errors.Wrapf(err, "while building destination URL") + return nil, errors.Wrapf(err, "while building destination URL") } reqBody, err := s.PrepareBasicRequestBody(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs) if err != nil { - return err + return nil, err } log.C(ctx).Infof("Creating inbound basic destination with name: %q, subaccount ID: %q and assignment ID: %q in the destination service", destinationName, subaccountID, formationAssignment.ID) _, statusCode, err := s.executeCreateRequest(ctx, strURL, reqBody, destinationName) if err != nil { - return errors.Wrapf(err, "while creating inbound basic destination with name: %q in the destination service", destinationName) + return nil, errors.Wrapf(err, "while creating inbound basic destination with name: %q in the destination service", destinationName) } if statusCode == http.StatusConflict { log.C(ctx).Infof("The destination with name: %q already exists. Will be deleted and created again...", destinationName) depth++ if depth > DepthLimit { - return errors.Errorf("Destination creator service retry limit: %d is exceeded", DepthLimit) + return nil, errors.Errorf("Destination creator service retry limit: %d is exceeded", DepthLimit) } if err := s.DeleteDestination(ctx, destinationName, subaccountID, destinationDetails.InstanceID, formationAssignment, skipSubaccountValidation); err != nil { - return errors.Wrapf(err, "while deleting destination with name: %q and subaccount ID: %q", destinationName, subaccountID) + return nil, errors.Wrapf(err, "while deleting destination with name: %q and subaccount ID: %q", destinationName, subaccountID) } return s.CreateBasicCredentialDestinations(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs, depth, skipSubaccountValidation) } - return nil + return &destinationcreatorpkg.DestinationInfo{ + AuthenticationType: reqBody.AuthenticationType, + Type: reqBody.Type, + URL: reqBody.URL, + }, nil } // CreateSAMLAssertionDestination is responsible to create SAML Assertion destination resource in the DB as well as in the remote destination service -func (s *Service) CreateSAMLAssertionDestination(ctx context.Context, destinationDetails operators.Destination, samlAuthCreds *operators.SAMLAssertionAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) error { +func (s *Service) CreateSAMLAssertionDestination(ctx context.Context, destinationDetails operators.Destination, samlAuthCreds *operators.SAMLAssertionAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) (*destinationcreatorpkg.DestinationInfo, error) { subaccountID := destinationDetails.SubaccountID region, err := s.getRegionLabel(ctx, subaccountID) if err != nil { - return errors.Wrapf(err, "while getting region label for tenant with ID: %s", subaccountID) + return nil, errors.Wrapf(err, "while getting region label for tenant with ID: %s", subaccountID) } destinationName := destinationDetails.Name @@ -227,12 +231,12 @@ func (s *Service) CreateSAMLAssertionDestination(ctx context.Context, destinatio InstanceID: destinationDetails.InstanceID, }, false) if err != nil { - return errors.Wrapf(err, "while building destination URL") + return nil, errors.Wrapf(err, "while building destination URL") } certName, err := GetDestinationCertificateName(ctx, destinationcreatorpkg.AuthTypeSAMLAssertion, formationAssignment.ID) if err != nil { - return errors.Wrapf(err, "while getting destination certificate name for destination auth type: %s", destinationcreatorpkg.AuthTypeSAMLAssertion) + return nil, errors.Wrapf(err, "while getting destination certificate name for destination auth type: %s", destinationcreatorpkg.AuthTypeSAMLAssertion) } destReqBody := &SAMLAssertionDestinationRequestBody{ @@ -248,13 +252,13 @@ func (s *Service) CreateSAMLAssertionDestination(ctx context.Context, destinatio enrichedProperties, err := enrichDestinationAdditionalPropertiesWithCorrelationIDs(s.config, correlationIDs, destinationDetails.AdditionalProperties) if err != nil { - return err + return nil, err } destReqBody.AdditionalProperties = enrichedProperties u, err := s.calculateDestinationURL(ctx, destinationDetails.URL, samlAuthCreds.URL, destinationcreatorpkg.AuthTypeSAMLAssertion, formationAssignment.TenantID, formationAssignment.Target) if err != nil { - return errors.Wrapf(err, "while calculating destination URL") + return nil, errors.Wrapf(err, "while calculating destination URL") } destReqBody.URL = u @@ -267,50 +271,54 @@ func (s *Service) CreateSAMLAssertionDestination(ctx context.Context, destinatio } if destinationDetails.Authentication != "" && destinationcreatorpkg.AuthType(destinationDetails.Authentication) != destinationcreatorpkg.AuthTypeSAMLAssertion { - return errors.Errorf("The provided authentication type: %s in the destination details is invalid. It should be %s", destinationDetails.Authentication, destinationcreatorpkg.AuthTypeSAMLAssertion) + return nil, errors.Errorf("The provided authentication type: %s in the destination details is invalid. It should be %s", destinationDetails.Authentication, destinationcreatorpkg.AuthTypeSAMLAssertion) } app, err := s.applicationRepository.GetByID(ctx, formationAssignment.TenantID, formationAssignment.Source) if err != nil { - return errors.Wrapf(err, "while getting application with ID: %q", formationAssignment.Source) + return nil, errors.Wrapf(err, "while getting application with ID: %q", formationAssignment.Source) } if app.BaseURL != nil { destReqBody.Audience = *app.BaseURL } if err := destReqBody.Validate(); err != nil { - return errors.Wrapf(err, "while validating SAML assertion destination request body") + return nil, errors.Wrapf(err, "while validating SAML assertion destination request body") } log.C(ctx).Infof("Creating SAML assertion destination with name: %q, subaccount ID: %q and assignment ID: %q in the destination service", destinationName, subaccountID, formationAssignment.ID) _, statusCode, err := s.executeCreateRequest(ctx, strURL, destReqBody, destinationName) if err != nil { - return errors.Wrapf(err, "while creating SAML assertion destination with name: %q in the destination service", destinationName) + return nil, errors.Wrapf(err, "while creating SAML assertion destination with name: %q in the destination service", destinationName) } if statusCode == http.StatusConflict { log.C(ctx).Infof("The destination with name: %q already exists. Will be deleted and created again...", destinationName) depth++ if depth > DepthLimit { - return errors.Errorf("Destination creator service retry limit: %d is exceeded", DepthLimit) + return nil, errors.Errorf("Destination creator service retry limit: %d is exceeded", DepthLimit) } if err := s.DeleteDestination(ctx, destinationName, subaccountID, destinationDetails.InstanceID, formationAssignment, skipSubaccountValidation); err != nil { - return errors.Wrapf(err, "while deleting destination with name: %q and subaccount ID: %q", destinationName, subaccountID) + return nil, errors.Wrapf(err, "while deleting destination with name: %q and subaccount ID: %q", destinationName, subaccountID) } return s.CreateSAMLAssertionDestination(ctx, destinationDetails, samlAuthCreds, formationAssignment, correlationIDs, depth, skipSubaccountValidation) } - return nil + return &destinationcreatorpkg.DestinationInfo{ + AuthenticationType: destReqBody.AuthenticationType, + Type: destReqBody.Type, + URL: destReqBody.URL, + }, nil } // CreateClientCertificateDestination is responsible to create client certificate destination resource in the DB as well as in the remote destination service -func (s *Service) CreateClientCertificateDestination(ctx context.Context, destinationDetails operators.Destination, clientCertAuthCreds *operators.ClientCertAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) error { +func (s *Service) CreateClientCertificateDestination(ctx context.Context, destinationDetails operators.Destination, clientCertAuthCreds *operators.ClientCertAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) (*destinationcreatorpkg.DestinationInfo, error) { subaccountID := destinationDetails.SubaccountID region, err := s.getRegionLabel(ctx, subaccountID) if err != nil { - return errors.Wrapf(err, "while getting region label for tenant with ID: %s", subaccountID) + return nil, errors.Wrapf(err, "while getting region label for tenant with ID: %s", subaccountID) } destinationName := destinationDetails.Name @@ -321,12 +329,12 @@ func (s *Service) CreateClientCertificateDestination(ctx context.Context, destin InstanceID: destinationDetails.InstanceID, }, false) if err != nil { - return errors.Wrapf(err, "while building destination URL") + return nil, errors.Wrapf(err, "while building destination URL") } certName, err := GetDestinationCertificateName(ctx, destinationcreatorpkg.AuthTypeClientCertificate, formationAssignment.ID) if err != nil { - return errors.Wrapf(err, "while getting destination certificate name for destination auth type: %s", destinationcreatorpkg.AuthTypeClientCertificate) + return nil, errors.Wrapf(err, "while getting destination certificate name for destination auth type: %s", destinationcreatorpkg.AuthTypeClientCertificate) } destReqBody := &ClientCertAuthDestinationRequestBody{ @@ -342,13 +350,13 @@ func (s *Service) CreateClientCertificateDestination(ctx context.Context, destin enrichedProperties, err := enrichDestinationAdditionalPropertiesWithCorrelationIDs(s.config, correlationIDs, destinationDetails.AdditionalProperties) if err != nil { - return err + return nil, err } destReqBody.AdditionalProperties = enrichedProperties u, err := s.calculateDestinationURL(ctx, destinationDetails.URL, clientCertAuthCreds.URL, destinationcreatorpkg.AuthTypeClientCertificate, formationAssignment.TenantID, formationAssignment.Target) if err != nil { - return errors.Wrapf(err, "while calculating destination URL") + return nil, errors.Wrapf(err, "while calculating destination URL") } destReqBody.URL = u @@ -361,34 +369,128 @@ func (s *Service) CreateClientCertificateDestination(ctx context.Context, destin } if destinationDetails.Authentication != "" && destinationcreatorpkg.AuthType(destinationDetails.Authentication) != destinationcreatorpkg.AuthTypeClientCertificate { - return errors.Errorf("The provided authentication type: %s in the destination details is invalid. It should be %s", destinationDetails.Authentication, destinationcreatorpkg.AuthTypeClientCertificate) + return nil, errors.Errorf("The provided authentication type: %s in the destination details is invalid. It should be %s", destinationDetails.Authentication, destinationcreatorpkg.AuthTypeClientCertificate) } if err := destReqBody.Validate(); err != nil { - return errors.Wrapf(err, "while validating client certificate destination request body") + return nil, errors.Wrapf(err, "while validating client certificate destination request body") } log.C(ctx).Infof("Creating client certificate authentication destination with name: %q, subaccount ID: %q and assignment ID: %q in the destination service", destinationName, subaccountID, formationAssignment.ID) _, statusCode, err := s.executeCreateRequest(ctx, strURL, destReqBody, destinationName) if err != nil { - return errors.Wrapf(err, "while creating client certificate authentication destination with name: %q in the destination service", destinationName) + return nil, errors.Wrapf(err, "while creating client certificate authentication destination with name: %q in the destination service", destinationName) } if statusCode == http.StatusConflict { log.C(ctx).Infof("The destination with name: %q already exists. Will be deleted and created again...", destinationName) depth++ if depth > DepthLimit { - return errors.Errorf("Destination creator service retry limit: %d is exceeded", DepthLimit) + return nil, errors.Errorf("Destination creator service retry limit: %d is exceeded", DepthLimit) } if err := s.DeleteDestination(ctx, destinationName, subaccountID, destinationDetails.InstanceID, formationAssignment, skipSubaccountValidation); err != nil { - return errors.Wrapf(err, "while deleting destination with name: %q and subaccount ID: %q", destinationName, subaccountID) + return nil, errors.Wrapf(err, "while deleting destination with name: %q and subaccount ID: %q", destinationName, subaccountID) } return s.CreateClientCertificateDestination(ctx, destinationDetails, clientCertAuthCreds, formationAssignment, correlationIDs, depth, skipSubaccountValidation) } - return nil + return &destinationcreatorpkg.DestinationInfo{ + AuthenticationType: destReqBody.AuthenticationType, + Type: destReqBody.Type, + URL: destReqBody.URL, + }, nil +} + +// CreateOAuth2ClientCredentialsDestinations is responsible to create an oauth2 client credentials destination resource in the remote destination service +func (s *Service) CreateOAuth2ClientCredentialsDestinations(ctx context.Context, destinationDetails operators.Destination, oauth2ClientCredsCredentials *operators.OAuth2ClientCredentialsAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) (*destinationcreatorpkg.DestinationInfo, error) { + subaccountID := destinationDetails.SubaccountID + region, err := s.getRegionLabel(ctx, subaccountID) + if err != nil { + return nil, errors.Wrapf(err, "while getting region label for tenant with ID: %s", subaccountID) + } + + destinationName := destinationDetails.Name + strURL, err := buildDestinationURL(ctx, s.config.DestinationAPIConfig, URLParameters{ + EntityName: destinationName, + Region: region, + SubaccountID: subaccountID, + InstanceID: destinationDetails.InstanceID, + }, false) + if err != nil { + return nil, errors.Wrapf(err, "while building destination URL") + } + + reqBody := &OAuth2ClientCredsDestinationRequestBody{ + BaseDestinationRequestBody: BaseDestinationRequestBody{ + Name: destinationDetails.Name, + Type: destinationcreatorpkg.TypeHTTP, + ProxyType: destinationcreatorpkg.ProxyTypeInternet, + AuthenticationType: destinationcreatorpkg.AuthTypeOAuth2ClientCredentials, + }, + ClientID: oauth2ClientCredsCredentials.ClientID, + ClientSecret: oauth2ClientCredsCredentials.ClientSecret, + TokenServiceURL: oauth2ClientCredsCredentials.TokenServiceURL, + } + + enrichedProperties, err := enrichDestinationAdditionalPropertiesWithCorrelationIDs(s.config, correlationIDs, destinationDetails.AdditionalProperties) + if err != nil { + return nil, err + } + reqBody.AdditionalProperties = enrichedProperties + + u, err := s.calculateDestinationURL(ctx, destinationDetails.URL, oauth2ClientCredsCredentials.URL, destinationcreatorpkg.AuthTypeOAuth2ClientCredentials, formationAssignment.TenantID, formationAssignment.Target) + if err != nil { + return nil, errors.Wrapf(err, "while calculating destination URL") + } + reqBody.URL = u + + if destinationDetails.Type != "" { + reqBody.Type = destinationcreatorpkg.Type(destinationDetails.Type) + } + + if destinationDetails.ProxyType != "" { + reqBody.ProxyType = destinationcreatorpkg.ProxyType(destinationDetails.ProxyType) + } + + if destinationDetails.TokenServiceURLType != "" { + reqBody.TokenServiceURLType = destinationDetails.TokenServiceURLType + } + + if destinationDetails.Authentication != "" && destinationcreatorpkg.AuthType(destinationDetails.Authentication) != destinationcreatorpkg.AuthTypeOAuth2ClientCredentials { + return nil, errors.Errorf("The provided authentication type: %s in the destination details is invalid. It should be %s", destinationDetails.Authentication, destinationcreatorpkg.AuthTypeOAuth2ClientCredentials) + } + + if err := reqBody.Validate(); err != nil { + return nil, errors.Wrapf(err, "while validating oauth2 client credentials destination request body") + } + + log.C(ctx).Infof("Creating inbound oauth2 client credentials destination with name: %q, subaccount ID: %q and assignment ID: %q in the destination service", destinationName, subaccountID, formationAssignment.ID) + _, statusCode, err := s.executeCreateRequest(ctx, strURL, reqBody, destinationName) + if err != nil { + return nil, errors.Wrapf(err, "while creating inbound oauth2 client credentials destination with name: %q in the destination service", destinationName) + } + + if statusCode == http.StatusConflict { + log.C(ctx).Infof("The destination with name: %q already exists. Will be deleted and created again...", destinationName) + depth++ + if depth > DepthLimit { + return nil, errors.Errorf("Destination creator service retry limit: %d is exceeded", DepthLimit) + } + + if err := s.DeleteDestination(ctx, destinationName, subaccountID, destinationDetails.InstanceID, formationAssignment, skipSubaccountValidation); err != nil { + return nil, errors.Wrapf(err, "while deleting destination with name: %q and subaccount ID: %q", destinationName, subaccountID) + } + + return s.CreateOAuth2ClientCredentialsDestinations(ctx, destinationDetails, oauth2ClientCredsCredentials, formationAssignment, correlationIDs, depth, skipSubaccountValidation) + } + + return &destinationcreatorpkg.DestinationInfo{ + AuthenticationType: reqBody.AuthenticationType, + Type: reqBody.Type, + URL: reqBody.URL, + }, nil } // CreateCertificate is responsible to create certificate resource in the remote destination service @@ -908,12 +1010,7 @@ func (s *Service) validateRuntimeContextProviderSubaccount(ctx context.Context, return s.validateRuntimeProviderSubaccount(ctx, rtmCtx.RuntimeID, externalDestSubaccountID) } -func (s *Service) executeCreateRequest( - ctx context.Context, - url string, - reqBody interface{}, - entityName string, -) (defaultRespBody []byte, defaultStatusCode int, err error) { +func (s *Service) executeCreateRequest(ctx context.Context, url string, reqBody interface{}, entityName string) (defaultRespBody []byte, defaultStatusCode int, err error) { reqBodyBytes, err := json.Marshal(reqBody) if err != nil { return defaultRespBody, defaultStatusCode, errors.Wrapf(err, "while marshalling request body") diff --git a/components/director/internal/destinationcreator/service_test.go b/components/director/internal/destinationcreator/service_test.go index 0d76d6c3ba..09ec006605 100644 --- a/components/director/internal/destinationcreator/service_test.go +++ b/components/director/internal/destinationcreator/service_test.go @@ -28,12 +28,21 @@ var ( destConfig = fixDestinationConfig() - basicDestDetails = fixBasicDestinationDetails() - samlAssertionDestsDetails = fixSAMLAssertionDestinationsDetails() + basicDestDetails = fixBasicDestinationDetails() + samlAssertionDestsDetails = fixSAMLAssertionDestinationsDetails() + samlAssertionSingleDestDetails = fixSAMLAssertionDestinationDetails() + clientCertAuthDestDetails = fixClientCertAuthDestinationDetails() + oauth2ClientCredsDestDetails = fixOAuth2ClientCredsDestinationDetails() + + basicDestInfo = fixDestinationInfo(basicDestDetails.Authentication, basicDestDetails.Type, basicDestDetails.URL) + samlDestInfo = fixDestinationInfo(samlAssertionSingleDestDetails.Authentication, samlAssertionSingleDestDetails.Type, samlAssertionSingleDestDetails.URL) + clientCertDestInfo = fixDestinationInfo(clientCertAuthDestDetails.Authentication, clientCertAuthDestDetails.Type, clientCertAuthDestDetails.URL) + oauth2ClientCredsDestInfo = fixDestinationInfo(oauth2ClientCredsDestDetails.Authentication, oauth2ClientCredsDestDetails.Type, oauth2ClientCredsDestDetails.URL) basicAuthCreds = fixBasicAuthCreds(basicDestURL, basicDestUser, basicDestPassword) samlAssertionAuthCreds = fixSAMLAssertionAuthCreds(samlAssertionDestURL) samlAssertionAuthCredsWithoutURL = &operators.SAMLAssertionAuthentication{} + oauth2ClientCreds = fixOAuth2ClientCreds(oauth2ClientCredsURL, oauth2ClientCredsTokenURL, oauth2ClientCredsClientID, oauth2ClientCredsClientSecret) createResp = fixHTTPResponse(http.StatusCreated, "") createRespWithConflict = fixHTTPResponse(http.StatusConflict, "") @@ -341,23 +350,25 @@ func Test_CreateBasicDestinations(t *testing.T) { basicDestDetailsWithURLOnlyWithScheme.URL = "https://" basicDestDetailsWithURLOnlyWithPath := fixBasicDestinationDetails() - basicDestDetailsWithURLOnlyWithPath.URL = "/test/path" + basicDestDetailsWithURLOnlyWithPath.URL = testURLPath testCases := []struct { - name string - destinationDetails operators.Destination - formationAssignment *model.FormationAssignment - correlationIDs []string - httpClient func() *automock.HttpClient - labelRepoFn func() *automock.LabelRepository - tenantRepoFn func() *automock.TenantRepository - expectedErrMessage string + name string + destinationDetails operators.Destination + expectedDestinationInfo *destinationcreatorpkg.DestinationInfo + formationAssignment *model.FormationAssignment + correlationIDs []string + httpClient func() *automock.HttpClient + labelRepoFn func() *automock.LabelRepository + tenantRepoFn func() *automock.TenantRepository + expectedErrMessage string }{ { - name: "Success", - destinationDetails: basicDestDetails, - formationAssignment: faWithSourceAppAndTargetApp, - correlationIDs: testCorrelationIDs, + name: "Success", + destinationDetails: basicDestDetails, + expectedDestinationInfo: basicDestInfo, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, httpClient: func() *automock.HttpClient { client := &automock.HttpClient{} client.On("Do", requestThatHasMethod(http.MethodPost)).Return(createResp, nil).Once() @@ -375,10 +386,11 @@ func Test_CreateBasicDestinations(t *testing.T) { }, }, { - name: "Success with empty correlation IDs", - destinationDetails: basicDestDetails, - formationAssignment: faWithSourceAppAndTargetApp, - correlationIDs: emptyCorrelationIDs, + name: "Success with empty correlation IDs", + destinationDetails: basicDestDetails, + expectedDestinationInfo: basicDestInfo, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: emptyCorrelationIDs, httpClient: func() *automock.HttpClient { client := &automock.HttpClient{} client.On("Do", requestThatHasMethod(http.MethodPost)).Return(createResp, nil).Once() @@ -457,10 +469,11 @@ func Test_CreateBasicDestinations(t *testing.T) { expectedErrMessage: "The provided URL in the destination details has only scheme", }, { - name: "Success while calculating url and it has only path", - destinationDetails: basicDestDetailsWithURLOnlyWithPath, - formationAssignment: faWithSourceAppAndTargetApp, - correlationIDs: testCorrelationIDs, + name: "Success while calculating url and it has only path", + destinationDetails: basicDestDetailsWithURLOnlyWithPath, + expectedDestinationInfo: fixDestinationInfo(basicDestDetailsWithURLOnlyWithPath.Authentication, basicDestDetailsWithURLOnlyWithPath.Type, fmt.Sprintf("%s%s", basicDestURL, testURLPath)), + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, httpClient: func() *automock.HttpClient { client := &automock.HttpClient{} client.On("Do", requestThatHasMethod(http.MethodPost)).Return(createResp, nil).Once() @@ -517,10 +530,11 @@ func Test_CreateBasicDestinations(t *testing.T) { expectedErrMessage: fmt.Sprintf("while creating inbound basic destination with name: %q in the destination service: %s", basicDestName, testErr.Error()), }, { - name: "Success while executing remote basic destination request and the status code is conflict", - destinationDetails: basicDestDetails, - formationAssignment: faWithSourceAppAndTargetApp, - correlationIDs: testCorrelationIDs, + name: "Success while executing remote basic destination request and the status code is conflict", + destinationDetails: basicDestDetails, + expectedDestinationInfo: basicDestInfo, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, httpClient: func() *automock.HttpClient { client := &automock.HttpClient{} client.On("Do", requestThatHasMethod(http.MethodPost)).Return(createRespWithConflict, nil).Once() @@ -610,12 +624,14 @@ func Test_CreateBasicDestinations(t *testing.T) { svc := destinationcreator.NewService(httpClient, destConfig, nil, nil, nil, labelRepo, tenantRepo) - err := svc.CreateBasicCredentialDestinations(emptyCtx, testCase.destinationDetails, basicAuthCreds, testCase.formationAssignment, testCase.correlationIDs, 0, false) + destInfo, err := svc.CreateBasicCredentialDestinations(emptyCtx, testCase.destinationDetails, basicAuthCreds, testCase.formationAssignment, testCase.correlationIDs, 0, false) if testCase.expectedErrMessage != "" { require.Error(t, err) require.Contains(t, err.Error(), testCase.expectedErrMessage) + require.Nil(t, destInfo) } else { require.NoError(t, err) + require.Equal(t, testCase.expectedDestinationInfo, destInfo) } }) } @@ -631,7 +647,7 @@ func Test_CreateSAMLAssertionDestinations(t *testing.T) { samlAssertionDestDetailsWithInvalidAuth.Authentication = invalidDestAuthType samlAssertionDestDetailsOnlyWithPath := fixSAMLAssertionDestinationDetails() - samlAssertionDestDetailsOnlyWithPath.URL = "/test/path" + samlAssertionDestDetailsOnlyWithPath.URL = testURLPath samlAssertionDestDetailsWithoutURL := fixSAMLAssertionDestinationDetails() samlAssertionDestDetailsWithoutURL.URL = "" @@ -639,6 +655,7 @@ func Test_CreateSAMLAssertionDestinations(t *testing.T) { testCases := []struct { name string destinationDetails operators.Destination + expectedDestInfo *destinationcreatorpkg.DestinationInfo samlAssertionAuthCreds *operators.SAMLAssertionAuthentication formationAssignment *model.FormationAssignment httpClient func() *automock.HttpClient @@ -650,6 +667,7 @@ func Test_CreateSAMLAssertionDestinations(t *testing.T) { { name: "Success", destinationDetails: samlAssertionDestDetails, + expectedDestInfo: samlDestInfo, formationAssignment: faWithSourceAppAndTargetApp, httpClient: func() *automock.HttpClient { client := &automock.HttpClient{} @@ -761,6 +779,7 @@ func Test_CreateSAMLAssertionDestinations(t *testing.T) { { name: "Success when calculating url using base URL of the system with destination details containing only path", destinationDetails: samlAssertionDestDetailsOnlyWithPath, + expectedDestInfo: fixDestinationInfo(samlAssertionDestDetailsOnlyWithPath.Authentication, samlAssertionDestDetailsOnlyWithPath.Type, fmt.Sprintf("%s%s", appBaseURL, testURLPath)), samlAssertionAuthCreds: samlAssertionAuthCredsWithoutURL, formationAssignment: faWithSourceAppAndTargetApp, httpClient: func() *automock.HttpClient { @@ -835,6 +854,7 @@ func Test_CreateSAMLAssertionDestinations(t *testing.T) { { name: "Success while executing remote saml assertion destination request and the status code is conflict", destinationDetails: samlAssertionDestDetails, + expectedDestInfo: samlDestInfo, formationAssignment: faWithSourceAppAndTargetApp, httpClient: func() *automock.HttpClient { client := &automock.HttpClient{} @@ -948,19 +968,20 @@ func Test_CreateSAMLAssertionDestinations(t *testing.T) { svc := destinationcreator.NewService(httpClient, destConfig, appRepo, nil, nil, labelRepo, tenantRepo) - err := svc.CreateSAMLAssertionDestination(emptyCtx, testCase.destinationDetails, samlAuthCreds, testCase.formationAssignment, testCorrelationIDs, 0, false) + destInfo, err := svc.CreateSAMLAssertionDestination(emptyCtx, testCase.destinationDetails, samlAuthCreds, testCase.formationAssignment, testCorrelationIDs, 0, false) if testCase.expectedErrMessage != "" { require.Error(t, err) require.Contains(t, err.Error(), testCase.expectedErrMessage) + require.Nil(t, destInfo) } else { require.NoError(t, err) + require.Equal(t, testCase.expectedDestInfo, destInfo) } }) } } func Test_CreateClientCertificateDestination(t *testing.T) { - clientCertAuthDestDetails := fixClientCertAuthDestinationDetails() clientCertAuthTypeCreds := fixClientCertAuthTypeCreds() clientCertAuthDestDetailsWithoutName := fixClientCertAuthDestinationDetails() @@ -972,6 +993,7 @@ func Test_CreateClientCertificateDestination(t *testing.T) { testCases := []struct { name string destinationDetails operators.Destination + expectedDestInfo *destinationcreatorpkg.DestinationInfo samlAssertionAuthCreds operators.SAMLAssertionAuthentication formationAssignment *model.FormationAssignment httpClient func() *automock.HttpClient @@ -982,6 +1004,7 @@ func Test_CreateClientCertificateDestination(t *testing.T) { { name: "Success", destinationDetails: clientCertAuthDestDetails, + expectedDestInfo: clientCertDestInfo, formationAssignment: faWithSourceAppAndTargetApp, httpClient: func() *automock.HttpClient { client := &automock.HttpClient{} @@ -1082,6 +1105,7 @@ func Test_CreateClientCertificateDestination(t *testing.T) { { name: "Success while executing remote saml assertion destination request and the status code is conflict", destinationDetails: clientCertAuthDestDetails, + expectedDestInfo: clientCertDestInfo, formationAssignment: faWithSourceAppAndTargetApp, httpClient: func() *automock.HttpClient { client := &automock.HttpClient{} @@ -1170,12 +1194,14 @@ func Test_CreateClientCertificateDestination(t *testing.T) { svc := destinationcreator.NewService(httpClient, destConfig, nil, nil, nil, labelRepo, tenantRepo) - err := svc.CreateClientCertificateDestination(emptyCtx, testCase.destinationDetails, clientCertAuthTypeCreds, testCase.formationAssignment, testCorrelationIDs, 0, false) + destInfo, err := svc.CreateClientCertificateDestination(emptyCtx, testCase.destinationDetails, clientCertAuthTypeCreds, testCase.formationAssignment, testCorrelationIDs, 0, false) if testCase.expectedErrMessage != "" { require.Error(t, err) require.Contains(t, err.Error(), testCase.expectedErrMessage) + require.Nil(t, destInfo) } else { require.NoError(t, err) + require.Equal(t, testCase.expectedDestInfo, destInfo) } }) } @@ -1358,6 +1384,324 @@ func Test_DeleteDestination(t *testing.T) { } } +func Test_CreateOauth2ClientCredentialsDestinations(t *testing.T) { + oauth2DestDetailsWithInvalidAuth := fixOAuth2ClientCredsDestinationDetails() + oauth2DestDetailsWithInvalidAuth.Authentication = invalidDestAuthType + + oauth2DestDetailsWithInvalidBaseURL := fixOAuth2ClientCredsDestinationDetails() + oauth2DestDetailsWithInvalidBaseURL.URL = ":wrong" + + oauth2DestDetailsWithURLOnlyWithScheme := fixOAuth2ClientCredsDestinationDetails() + oauth2DestDetailsWithURLOnlyWithScheme.URL = "https://" + + oauth2DestDetailsWithURLOnlyWithPath := fixOAuth2ClientCredsDestinationDetails() + oauth2DestDetailsWithURLOnlyWithPath.URL = testURLPath + + oauth2DestDetailsWithoutName := fixOAuth2ClientCredsDestinationDetails() + oauth2DestDetailsWithoutName.Name = "" + + testCases := []struct { + name string + destinationDetails operators.Destination + expectedDestinationInfo *destinationcreatorpkg.DestinationInfo + formationAssignment *model.FormationAssignment + correlationIDs []string + httpClient func() *automock.HttpClient + labelRepoFn func() *automock.LabelRepository + tenantRepoFn func() *automock.TenantRepository + expectedErrMessage string + }{ + { + name: "Success", + destinationDetails: oauth2ClientCredsDestDetails, + expectedDestinationInfo: oauth2ClientCredsDestInfo, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, + httpClient: func() *automock.HttpClient { + client := &automock.HttpClient{} + client.On("Do", requestThatHasMethod(http.MethodPost)).Return(createResp, nil).Once() + return client + }, + labelRepoFn: func() *automock.LabelRepository { + labelRepo := &automock.LabelRepository{} + labelRepo.On("GetByKey", emptyCtx, destinationInternalSubaccountID, model.TenantLabelableObject, destinationExternalSubaccountID, destinationcreator.RegionLabelKey).Return(regionLbl, nil).Once() + return labelRepo + }, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(subaccTenant, nil).Once() + return tenantRepo + }, + }, + { + name: "Success with empty correlation IDs", + destinationDetails: oauth2ClientCredsDestDetails, + expectedDestinationInfo: oauth2ClientCredsDestInfo, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: emptyCorrelationIDs, + httpClient: func() *automock.HttpClient { + client := &automock.HttpClient{} + client.On("Do", requestThatHasMethod(http.MethodPost)).Return(createResp, nil).Once() + return client + }, + labelRepoFn: func() *automock.LabelRepository { + labelRepo := &automock.LabelRepository{} + labelRepo.On("GetByKey", emptyCtx, destinationInternalSubaccountID, model.TenantLabelableObject, destinationExternalSubaccountID, destinationcreator.RegionLabelKey).Return(regionLbl, nil).Once() + return labelRepo + }, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(subaccTenant, nil).Once() + return tenantRepo + }, + }, + { + name: "Error while getting region and get external tenant fail", + destinationDetails: oauth2ClientCredsDestDetails, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(nil, testErr).Once() + return tenantRepo + }, + expectedErrMessage: fmt.Sprintf("while getting region label for tenant with ID: %s: while getting tenant by external ID: %q", destinationExternalSubaccountID, destinationExternalSubaccountID), + }, + { + name: "Error while building url and region is empty", + destinationDetails: oauth2ClientCredsDestDetails, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, + labelRepoFn: func() *automock.LabelRepository { + labelRepo := &automock.LabelRepository{} + labelRepo.On("GetByKey", emptyCtx, destinationInternalSubaccountID, model.TenantLabelableObject, destinationExternalSubaccountID, destinationcreator.RegionLabelKey).Return(lblWithEmptyValue, nil).Once() + return labelRepo + }, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(subaccTenant, nil).Once() + return tenantRepo + }, + expectedErrMessage: "The provided region and/or subaccount for the URL couldn't be empty", + }, + { + name: "Error while calculating url and parse url fail", + destinationDetails: oauth2DestDetailsWithInvalidBaseURL, + formationAssignment: faWithSourceAppAndTargetApp, + labelRepoFn: func() *automock.LabelRepository { + labelRepo := &automock.LabelRepository{} + labelRepo.On("GetByKey", emptyCtx, destinationInternalSubaccountID, model.TenantLabelableObject, destinationExternalSubaccountID, destinationcreator.RegionLabelKey).Return(regionLbl, nil).Once() + return labelRepo + }, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(subaccTenant, nil).Once() + return tenantRepo + }, + expectedErrMessage: "missing protocol scheme", + }, + { + name: "Error while calculating url when it has only scheme", + destinationDetails: oauth2DestDetailsWithURLOnlyWithScheme, + formationAssignment: faWithSourceAppAndTargetApp, + labelRepoFn: func() *automock.LabelRepository { + labelRepo := &automock.LabelRepository{} + labelRepo.On("GetByKey", emptyCtx, destinationInternalSubaccountID, model.TenantLabelableObject, destinationExternalSubaccountID, destinationcreator.RegionLabelKey).Return(regionLbl, nil).Once() + return labelRepo + }, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(subaccTenant, nil).Once() + return tenantRepo + }, + expectedErrMessage: "The provided URL in the destination details has only scheme", + }, + { + name: "Success while calculating url and it has only path", + destinationDetails: oauth2DestDetailsWithURLOnlyWithPath, + expectedDestinationInfo: fixDestinationInfo(oauth2DestDetailsWithURLOnlyWithPath.Authentication, oauth2DestDetailsWithURLOnlyWithPath.Type, fmt.Sprintf("%s%s", oauth2ClientCredsURL, testURLPath)), + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, + httpClient: func() *automock.HttpClient { + client := &automock.HttpClient{} + client.On("Do", requestThatHasMethod(http.MethodPost)).Return(createResp, nil).Once() + return client + }, + labelRepoFn: func() *automock.LabelRepository { + labelRepo := &automock.LabelRepository{} + labelRepo.On("GetByKey", emptyCtx, destinationInternalSubaccountID, model.TenantLabelableObject, destinationExternalSubaccountID, destinationcreator.RegionLabelKey).Return(regionLbl, nil).Once() + return labelRepo + }, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(subaccTenant, nil).Once() + return tenantRepo + }, + }, + { + name: "Error when preparing oauth2 request body fail", + destinationDetails: oauth2DestDetailsWithInvalidAuth, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, + labelRepoFn: func() *automock.LabelRepository { + labelRepo := &automock.LabelRepository{} + labelRepo.On("GetByKey", emptyCtx, destinationInternalSubaccountID, model.TenantLabelableObject, destinationExternalSubaccountID, destinationcreator.RegionLabelKey).Return(regionLbl, nil).Once() + return labelRepo + }, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(subaccTenant, nil).Once() + return tenantRepo + }, + expectedErrMessage: fmt.Sprintf("The provided authentication type: %s in the destination details is invalid. It should be %s", invalidDestAuthType, destinationcreatorpkg.AuthTypeOAuth2ClientCredentials), + }, + { + name: "Error when validation of request body fails", + destinationDetails: oauth2DestDetailsWithoutName, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, + labelRepoFn: func() *automock.LabelRepository { + labelRepo := &automock.LabelRepository{} + labelRepo.On("GetByKey", emptyCtx, destinationInternalSubaccountID, model.TenantLabelableObject, destinationExternalSubaccountID, destinationcreator.RegionLabelKey).Return(regionLbl, nil).Once() + return labelRepo + }, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(subaccTenant, nil).Once() + return tenantRepo + }, + expectedErrMessage: "while validating oauth2 client credentials destination request body", + }, + { + name: "Error when executing remote oauth2 client creds destination request fail", + destinationDetails: oauth2ClientCredsDestDetails, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, + httpClient: func() *automock.HttpClient { + client := &automock.HttpClient{} + client.On("Do", requestThatHasMethod(http.MethodPost)).Return(nil, testErr).Once() + return client + }, + labelRepoFn: func() *automock.LabelRepository { + labelRepo := &automock.LabelRepository{} + labelRepo.On("GetByKey", emptyCtx, destinationInternalSubaccountID, model.TenantLabelableObject, destinationExternalSubaccountID, destinationcreator.RegionLabelKey).Return(regionLbl, nil).Once() + return labelRepo + }, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(subaccTenant, nil).Once() + return tenantRepo + }, + expectedErrMessage: fmt.Sprintf("while creating inbound oauth2 client credentials destination with name: %q in the destination service: %s", oauth2ClientCredsDestName, testErr.Error()), + }, + { + name: "Success while executing remote basic destination request and the status code is conflict", + destinationDetails: oauth2ClientCredsDestDetails, + expectedDestinationInfo: oauth2ClientCredsDestInfo, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, + httpClient: func() *automock.HttpClient { + client := &automock.HttpClient{} + client.On("Do", requestThatHasMethod(http.MethodPost)).Return(createRespWithConflict, nil).Once() + client.On("Do", requestThatHasMethod(http.MethodDelete)).Return(deleteResp, nil).Once() + client.On("Do", requestThatHasMethod(http.MethodPost)).Return(createResp, nil).Once() + return client + }, + labelRepoFn: func() *automock.LabelRepository { + labelRepo := &automock.LabelRepository{} + labelRepo.On("ListForObject", emptyCtx, testTenantID, model.ApplicationLabelableObject, testTargetID).Return(subaccountnLbl, nil).Times(1) + labelRepo.On("GetByKey", emptyCtx, destinationInternalSubaccountID, model.TenantLabelableObject, destinationExternalSubaccountID, destinationcreator.RegionLabelKey).Return(regionLbl, nil).Times(3) + return labelRepo + }, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(subaccTenant, nil).Times(3) + return tenantRepo + }, + }, + { + name: "Error while executing remote basic destination request and maximum depth is reached", + destinationDetails: oauth2ClientCredsDestDetails, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, + httpClient: func() *automock.HttpClient { + client := &automock.HttpClient{} + client.On("Do", requestThatHasMethod(http.MethodPost)).Return(createRespWithConflict, nil).Times(3) + client.On("Do", requestThatHasMethod(http.MethodDelete)).Return(deleteResp, nil).Twice() + return client + }, + labelRepoFn: func() *automock.LabelRepository { + labelRepo := &automock.LabelRepository{} + labelRepo.On("ListForObject", emptyCtx, testTenantID, model.ApplicationLabelableObject, testTargetID).Return(subaccountnLbl, nil).Times(2) + labelRepo.On("GetByKey", emptyCtx, destinationInternalSubaccountID, model.TenantLabelableObject, destinationExternalSubaccountID, destinationcreator.RegionLabelKey).Return(regionLbl, nil).Times(5) + return labelRepo + }, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(subaccTenant, nil).Times(5) + return tenantRepo + }, + expectedErrMessage: fmt.Sprintf("Destination creator service retry limit: %d is exceeded", destinationcreator.DepthLimit), + }, + { + name: "Error while executing remote basic destination request in case of conflict and delete destination fail", + destinationDetails: oauth2ClientCredsDestDetails, + formationAssignment: faWithSourceAppAndTargetApp, + correlationIDs: testCorrelationIDs, + httpClient: func() *automock.HttpClient { + client := &automock.HttpClient{} + client.On("Do", requestThatHasMethod(http.MethodPost)).Return(createRespWithConflict, nil).Once() + return client + }, + labelRepoFn: func() *automock.LabelRepository { + labelRepo := &automock.LabelRepository{} + labelRepo.On("ListForObject", emptyCtx, testTenantID, model.ApplicationLabelableObject, testTargetID).Return(subaccountnLbl, nil).Once() + labelRepo.On("GetByKey", emptyCtx, destinationInternalSubaccountID, model.TenantLabelableObject, destinationExternalSubaccountID, destinationcreator.RegionLabelKey).Return(regionLbl, nil).Once() + return labelRepo + }, + tenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(subaccTenant, nil).Once() + tenantRepo.On("GetByExternalTenant", emptyCtx, destinationExternalSubaccountID).Return(nil, testErr).Once() + return tenantRepo + }, + expectedErrMessage: fmt.Sprintf("while getting tenant by external ID: %q: %s", destinationExternalSubaccountID, testErr.Error()), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + httpClient := fixUnusedHTTPClient() + if testCase.httpClient != nil { + httpClient = testCase.httpClient() + } + + labelRepo := fixUnusedLabelRepo() + if testCase.labelRepoFn != nil { + labelRepo = testCase.labelRepoFn() + } + + tenantRepo := fixUnusedTenantRepo() + if testCase.tenantRepoFn != nil { + tenantRepo = testCase.tenantRepoFn() + } + defer mock.AssertExpectationsForObjects(t, httpClient, labelRepo, tenantRepo) + + svc := destinationcreator.NewService(httpClient, destConfig, nil, nil, nil, labelRepo, tenantRepo) + + destInfo, err := svc.CreateOAuth2ClientCredentialsDestinations(emptyCtx, testCase.destinationDetails, oauth2ClientCreds, testCase.formationAssignment, testCase.correlationIDs, 0, false) + if testCase.expectedErrMessage != "" { + require.Error(t, err) + require.Contains(t, err.Error(), testCase.expectedErrMessage) + require.Nil(t, destInfo) + } else { + require.NoError(t, err) + require.Equal(t, testCase.expectedDestinationInfo, destInfo) + } + }) + } +} + func Test_CreateCertificate(t *testing.T) { samlAssertionDestsDetailsWithoutName := fixSAMLAssertionDestinationsDetails() samlAssertionDestDetailsWithoutName := samlAssertionDestsDetailsWithoutName[0] diff --git a/components/director/internal/destinationcreator/types.go b/components/director/internal/destinationcreator/types.go index 20b5244995..d529139673 100644 --- a/components/director/internal/destinationcreator/types.go +++ b/components/director/internal/destinationcreator/types.go @@ -65,6 +65,15 @@ type ClientCertAuthDestinationRequestBody struct { KeyStoreLocation string `json:"keyStoreLocation"` } +// OAuth2ClientCredsDestinationRequestBody contains the necessary fields for the destination request body with authentication type OAuth2ClientCredentials +type OAuth2ClientCredsDestinationRequestBody struct { + BaseDestinationRequestBody + TokenServiceURL string `json:"tokenServiceURL"` + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` + TokenServiceURLType string `json:"tokenServiceURLType,omitempty"` +} + // CertificateRequestBody contains the necessary fields for the destination creator certificate request body type CertificateRequestBody struct { Name string `json:"name"` @@ -74,7 +83,7 @@ type CertificateRequestBody struct { // reqBodyNameRegex is a regex defined by the destination creator API specifying what destination names are allowed var reqBodyNameRegex = "[a-zA-Z0-9_-]{1,64}" -// Validate validates that the AuthTypeNoAuth request body contains the required fields and they are valid +// Validate validates that the AuthTypeNoAuth request body contains the required fields, and they are valid func (n *NoAuthDestinationRequestBody) Validate() error { return validation.ValidateStruct(n, validation.Field(&n.Name, validation.Required, validation.Length(1, destinationcreatorpkg.MaxDestinationNameLength), validation.Match(regexp.MustCompile(reqBodyNameRegex))), @@ -85,7 +94,7 @@ func (n *NoAuthDestinationRequestBody) Validate() error { ) } -// Validate validates that the AuthTypeBasic request body contains the required fields and they are valid +// Validate validates that the AuthTypeBasic request body contains the required fields, and they are valid func (b *BasicAuthDestinationRequestBody) Validate() error { return validation.ValidateStruct(b, validation.Field(&b.Name, validation.Required, validation.Length(1, destinationcreatorpkg.MaxDestinationNameLength), validation.Match(regexp.MustCompile(reqBodyNameRegex))), @@ -97,7 +106,7 @@ func (b *BasicAuthDestinationRequestBody) Validate() error { ) } -// Validate validates that the AuthTypeSAMLAssertion request body contains the required fields and they are valid +// Validate validates that the AuthTypeSAMLAssertion request body contains the required fields, and they are valid func (s *SAMLAssertionDestinationRequestBody) Validate() error { return validation.ValidateStruct(s, validation.Field(&s.Name, validation.Required, validation.Length(1, destinationcreatorpkg.MaxDestinationNameLength), validation.Match(regexp.MustCompile(reqBodyNameRegex))), @@ -110,7 +119,7 @@ func (s *SAMLAssertionDestinationRequestBody) Validate() error { ) } -// Validate validates that the AuthTypeClientCertificate request body contains the required fields and they are valid +// Validate validates that the AuthTypeClientCertificate request body contains the required fields, and they are valid func (s *ClientCertAuthDestinationRequestBody) Validate() error { return validation.ValidateStruct(s, validation.Field(&s.Name, validation.Required, validation.Length(1, destinationcreatorpkg.MaxDestinationNameLength), validation.Match(regexp.MustCompile(reqBodyNameRegex))), @@ -122,14 +131,29 @@ func (s *ClientCertAuthDestinationRequestBody) Validate() error { ) } -// Validate validates that the SAML assertion certificate request body contains the required fields and they are valid +// Validate validates that the AuthTypeBasic request body contains the required fields, and they are valid +func (b *OAuth2ClientCredsDestinationRequestBody) Validate() error { + return validation.ValidateStruct(b, + validation.Field(&b.Name, validation.Required, validation.Length(1, destinationcreatorpkg.MaxDestinationNameLength), validation.Match(regexp.MustCompile(reqBodyNameRegex))), + validation.Field(&b.URL, validation.Required), + validation.Field(&b.Type, validation.In(destinationcreatorpkg.TypeHTTP, destinationcreatorpkg.TypeRFC, destinationcreatorpkg.TypeLDAP, destinationcreatorpkg.TypeMAIL)), + validation.Field(&b.ProxyType, validation.In(destinationcreatorpkg.ProxyTypeInternet, destinationcreatorpkg.ProxyTypeOnPremise, destinationcreatorpkg.ProxyTypePrivateLink)), + validation.Field(&b.AuthenticationType, validation.In(destinationcreatorpkg.AuthTypeOAuth2ClientCredentials)), + validation.Field(&b.ClientID, validation.Required), + validation.Field(&b.ClientSecret, validation.Required), + validation.Field(&b.TokenServiceURL, validation.Required), + validation.Field(&b.TokenServiceURLType, validation.In(string(destinationcreatorpkg.DedicatedTokenServiceURLType), string(destinationcreatorpkg.CommonTokenServiceURLType))), + ) +} + +// Validate validates that the SAML assertion certificate request body contains the required fields, and they are valid func (c *CertificateRequestBody) Validate() error { return validation.ValidateStruct(c, validation.Field(&c.Name, validation.Required, validation.Length(1, destinationcreatorpkg.MaxDestinationNameLength), validation.Match(regexp.MustCompile(reqBodyNameRegex))), ) } -// Validate validates that the SAML assertion certificate response body contains the required fields and they are valid +// Validate validates that the SAML assertion certificate response body contains the required fields, and they are valid func (cr *CertificateResponse) Validate() error { return validation.ValidateStruct(cr, validation.Field(&cr.FileName, validation.Required), diff --git a/components/director/internal/domain/destination/automock/destination_creator_service.go b/components/director/internal/domain/destination/automock/destination_creator_service.go index fde4c88e3d..3ae24b4ddf 100644 --- a/components/director/internal/domain/destination/automock/destination_creator_service.go +++ b/components/director/internal/domain/destination/automock/destination_creator_service.go @@ -5,7 +5,7 @@ package automock import ( context "context" - destinationcreator "github.com/kyma-incubator/compass/components/director/internal/destinationcreator" + destinationcreator "github.com/kyma-incubator/compass/components/director/pkg/destinationcreator" mock "github.com/stretchr/testify/mock" @@ -20,31 +20,55 @@ type DestinationCreatorService struct { } // CreateBasicCredentialDestinations provides a mock function with given fields: ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs, depth, skipSubaccountValidation -func (_m *DestinationCreatorService) CreateBasicCredentialDestinations(ctx context.Context, destinationDetails operators.Destination, basicAuthenticationCredentials operators.BasicAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) error { +func (_m *DestinationCreatorService) CreateBasicCredentialDestinations(ctx context.Context, destinationDetails operators.Destination, basicAuthenticationCredentials operators.BasicAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) (*destinationcreator.DestinationInfo, error) { ret := _m.Called(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs, depth, skipSubaccountValidation) - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, operators.BasicAuthentication, *model.FormationAssignment, []string, uint8, bool) error); ok { + var r0 *destinationcreator.DestinationInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, operators.BasicAuthentication, *model.FormationAssignment, []string, uint8, bool) (*destinationcreator.DestinationInfo, error)); ok { + return rf(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs, depth, skipSubaccountValidation) + } + if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, operators.BasicAuthentication, *model.FormationAssignment, []string, uint8, bool) *destinationcreator.DestinationInfo); ok { r0 = rf(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs, depth, skipSubaccountValidation) } else { - r0 = ret.Error(0) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*destinationcreator.DestinationInfo) + } } - return r0 + if rf, ok := ret.Get(1).(func(context.Context, operators.Destination, operators.BasicAuthentication, *model.FormationAssignment, []string, uint8, bool) error); ok { + r1 = rf(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs, depth, skipSubaccountValidation) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // CreateClientCertificateDestination provides a mock function with given fields: ctx, destinationDetails, clientCertAuthCreds, formationAssignment, correlationIDs, depth, skipSubaccountValidation -func (_m *DestinationCreatorService) CreateClientCertificateDestination(ctx context.Context, destinationDetails operators.Destination, clientCertAuthCreds *operators.ClientCertAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) error { +func (_m *DestinationCreatorService) CreateClientCertificateDestination(ctx context.Context, destinationDetails operators.Destination, clientCertAuthCreds *operators.ClientCertAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) (*destinationcreator.DestinationInfo, error) { ret := _m.Called(ctx, destinationDetails, clientCertAuthCreds, formationAssignment, correlationIDs, depth, skipSubaccountValidation) - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, *operators.ClientCertAuthentication, *model.FormationAssignment, []string, uint8, bool) error); ok { + var r0 *destinationcreator.DestinationInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, *operators.ClientCertAuthentication, *model.FormationAssignment, []string, uint8, bool) (*destinationcreator.DestinationInfo, error)); ok { + return rf(ctx, destinationDetails, clientCertAuthCreds, formationAssignment, correlationIDs, depth, skipSubaccountValidation) + } + if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, *operators.ClientCertAuthentication, *model.FormationAssignment, []string, uint8, bool) *destinationcreator.DestinationInfo); ok { r0 = rf(ctx, destinationDetails, clientCertAuthCreds, formationAssignment, correlationIDs, depth, skipSubaccountValidation) } else { - r0 = ret.Error(0) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*destinationcreator.DestinationInfo) + } } - return r0 + if rf, ok := ret.Get(1).(func(context.Context, operators.Destination, *operators.ClientCertAuthentication, *model.FormationAssignment, []string, uint8, bool) error); ok { + r1 = rf(ctx, destinationDetails, clientCertAuthCreds, formationAssignment, correlationIDs, depth, skipSubaccountValidation) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // CreateDesignTimeDestinations provides a mock function with given fields: ctx, destinationDetails, formationAssignment, depth, skipSubaccountValidation @@ -61,18 +85,56 @@ func (_m *DestinationCreatorService) CreateDesignTimeDestinations(ctx context.Co return r0 } +// CreateOAuth2ClientCredentialsDestinations provides a mock function with given fields: ctx, destinationDetails, oauth2ClientCredsCredentials, formationAssignment, correlationIDs, depth, skipSubaccountValidation +func (_m *DestinationCreatorService) CreateOAuth2ClientCredentialsDestinations(ctx context.Context, destinationDetails operators.Destination, oauth2ClientCredsCredentials *operators.OAuth2ClientCredentialsAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) (*destinationcreator.DestinationInfo, error) { + ret := _m.Called(ctx, destinationDetails, oauth2ClientCredsCredentials, formationAssignment, correlationIDs, depth, skipSubaccountValidation) + + var r0 *destinationcreator.DestinationInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, *operators.OAuth2ClientCredentialsAuthentication, *model.FormationAssignment, []string, uint8, bool) (*destinationcreator.DestinationInfo, error)); ok { + return rf(ctx, destinationDetails, oauth2ClientCredsCredentials, formationAssignment, correlationIDs, depth, skipSubaccountValidation) + } + if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, *operators.OAuth2ClientCredentialsAuthentication, *model.FormationAssignment, []string, uint8, bool) *destinationcreator.DestinationInfo); ok { + r0 = rf(ctx, destinationDetails, oauth2ClientCredsCredentials, formationAssignment, correlationIDs, depth, skipSubaccountValidation) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*destinationcreator.DestinationInfo) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, operators.Destination, *operators.OAuth2ClientCredentialsAuthentication, *model.FormationAssignment, []string, uint8, bool) error); ok { + r1 = rf(ctx, destinationDetails, oauth2ClientCredsCredentials, formationAssignment, correlationIDs, depth, skipSubaccountValidation) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // CreateSAMLAssertionDestination provides a mock function with given fields: ctx, destinationDetails, samlAuthCreds, formationAssignment, correlationIDs, depth, skipSubaccountValidation -func (_m *DestinationCreatorService) CreateSAMLAssertionDestination(ctx context.Context, destinationDetails operators.Destination, samlAuthCreds *operators.SAMLAssertionAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) error { +func (_m *DestinationCreatorService) CreateSAMLAssertionDestination(ctx context.Context, destinationDetails operators.Destination, samlAuthCreds *operators.SAMLAssertionAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) (*destinationcreator.DestinationInfo, error) { ret := _m.Called(ctx, destinationDetails, samlAuthCreds, formationAssignment, correlationIDs, depth, skipSubaccountValidation) - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, *operators.SAMLAssertionAuthentication, *model.FormationAssignment, []string, uint8, bool) error); ok { + var r0 *destinationcreator.DestinationInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, *operators.SAMLAssertionAuthentication, *model.FormationAssignment, []string, uint8, bool) (*destinationcreator.DestinationInfo, error)); ok { + return rf(ctx, destinationDetails, samlAuthCreds, formationAssignment, correlationIDs, depth, skipSubaccountValidation) + } + if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, *operators.SAMLAssertionAuthentication, *model.FormationAssignment, []string, uint8, bool) *destinationcreator.DestinationInfo); ok { r0 = rf(ctx, destinationDetails, samlAuthCreds, formationAssignment, correlationIDs, depth, skipSubaccountValidation) } else { - r0 = ret.Error(0) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*destinationcreator.DestinationInfo) + } } - return r0 + if rf, ok := ret.Get(1).(func(context.Context, operators.Destination, *operators.SAMLAssertionAuthentication, *model.FormationAssignment, []string, uint8, bool) error); ok { + r1 = rf(ctx, destinationDetails, samlAuthCreds, formationAssignment, correlationIDs, depth, skipSubaccountValidation) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // DeleteCertificate provides a mock function with given fields: ctx, certificateName, externalDestSubaccountID, instanceID, formationAssignment, skipSubaccountValidation @@ -165,32 +227,6 @@ func (_m *DestinationCreatorService) GetConsumerTenant(ctx context.Context, form return r0, r1 } -// PrepareBasicRequestBody provides a mock function with given fields: ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs -func (_m *DestinationCreatorService) PrepareBasicRequestBody(ctx context.Context, destinationDetails operators.Destination, basicAuthenticationCredentials operators.BasicAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string) (*destinationcreator.BasicAuthDestinationRequestBody, error) { - ret := _m.Called(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs) - - var r0 *destinationcreator.BasicAuthDestinationRequestBody - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, operators.BasicAuthentication, *model.FormationAssignment, []string) (*destinationcreator.BasicAuthDestinationRequestBody, error)); ok { - return rf(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs) - } - if rf, ok := ret.Get(0).(func(context.Context, operators.Destination, operators.BasicAuthentication, *model.FormationAssignment, []string) *destinationcreator.BasicAuthDestinationRequestBody); ok { - r0 = rf(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*destinationcreator.BasicAuthDestinationRequestBody) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, operators.Destination, operators.BasicAuthentication, *model.FormationAssignment, []string) error); ok { - r1 = rf(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // NewDestinationCreatorService creates a new instance of DestinationCreatorService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewDestinationCreatorService(t interface { diff --git a/components/director/internal/domain/destination/fixtures_test.go b/components/director/internal/domain/destination/fixtures_test.go index 3a2fca81a4..5cfa8fd37b 100644 --- a/components/director/internal/domain/destination/fixtures_test.go +++ b/components/director/internal/domain/destination/fixtures_test.go @@ -1,7 +1,6 @@ package destination_test import ( - "github.com/kyma-incubator/compass/components/director/internal/destinationcreator" "github.com/kyma-incubator/compass/components/director/internal/domain/destination" "github.com/kyma-incubator/compass/components/director/internal/domain/destination/automock" "github.com/kyma-incubator/compass/components/director/internal/domain/formationconstraint/operators" @@ -21,20 +20,24 @@ const ( destinationInstanceID = "999ac686-5773-4ad0-8eb1-2349e931f852" // Destination constants - destinationName = "test-destination-name" - destinationType = destinationcreatorpkg.TypeHTTP - destinationProxyType = destinationcreatorpkg.ProxyTypeInternet - destinationNoAuthn = destinationcreatorpkg.AuthTypeNoAuth - designTimeDestName = "test-design-time-dest-name" - basicDestName = "test-basic-dest-name" - samlAssertionDestName = "test-saml-assertion-dest-name" - clientCertAuthDestName = "test-client-cert-auth-dest-name" - destinationURL = "http://dest-test-url" - destinationDescription = "test-dest-description" + destinationName = "test-destination-name" + destinationType = destinationcreatorpkg.TypeHTTP + destinationProxyType = destinationcreatorpkg.ProxyTypeInternet + destinationNoAuthn = destinationcreatorpkg.AuthTypeNoAuth + designTimeDestName = "test-design-time-dest-name" + basicDestName = "test-basic-dest-name" + samlAssertionDestName = "test-saml-assertion-dest-name" + clientCertAuthDestName = "test-client-cert-auth-dest-name" + oauth2ClientCredsDestName = "test-oauth2-client-creds-dest-name" + destinationURL = "http://dest-test-url" + destinationDescription = "test-dest-description" // Destination Creds constants - basicDestUser = "basic-user" - basicDestPassword = "basic-pwd" + basicDestUser = "basic-user" + basicDestPwd = "basic-pwd" + oauth2ClientCredsTokenServiceURL = "http://oauth2-token-service-url" + oauth2ClientCredsClientID = "test-client-id" + oauth2ClientCredsClientSecret = "test-client-secret" // Other destinationLatestRevision = "2" @@ -109,27 +112,36 @@ func fixDestinationDetails(name, authentication, subaccountID string) operators. } } +func fixDestinationInfo(authType destinationcreatorpkg.AuthType, destType destinationcreatorpkg.Type, url string) *destinationcreatorpkg.DestinationInfo { + return &destinationcreatorpkg.DestinationInfo{ + AuthenticationType: authType, + Type: destType, + URL: url, + } +} + func fixBasicAuthn() operators.BasicAuthentication { return operators.BasicAuthentication{ URL: destinationURL, - UIURL: destinationURL, Username: basicDestUser, - Password: basicDestPassword, + Password: basicDestPwd, } } -func fixBasicReqBody() *destinationcreator.BasicAuthDestinationRequestBody { - return &destinationcreator.BasicAuthDestinationRequestBody{ - BaseDestinationRequestBody: destinationcreator.BaseDestinationRequestBody{ - Name: basicDestName, - URL: destinationURL, - Type: destinationType, - ProxyType: destinationProxyType, - AuthenticationType: destinationcreatorpkg.AuthTypeBasic, - }, - User: basicDestUser, - Password: basicDestPassword, - } +func fixBasicDestInfo() *destinationcreatorpkg.DestinationInfo { + return fixDestinationInfo(destinationcreatorpkg.AuthTypeBasic, destinationType, destinationURL) +} + +func fixSAMLDestInfo() *destinationcreatorpkg.DestinationInfo { + return fixDestinationInfo(destinationcreatorpkg.AuthTypeSAMLAssertion, destinationType, destinationURL) +} + +func fixClientCertDestInfo() *destinationcreatorpkg.DestinationInfo { + return fixDestinationInfo(destinationcreatorpkg.AuthTypeClientCertificate, destinationType, destinationURL) +} + +func fixOAuth2ClientCredsDestInfo() *destinationcreatorpkg.DestinationInfo { + return fixDestinationInfo(destinationcreatorpkg.AuthTypeOAuth2ClientCredentials, destinationType, destinationURL) } func fixSAMLAssertionAuthentication() *operators.SAMLAssertionAuthentication { @@ -164,6 +176,21 @@ func fixClientCertAuthDestinationsDetails() []operators.Destination { } } +func fixOAuth2ClientCredsDestinationsDetails() []operators.Destination { + return []operators.Destination{ + fixDestinationDetails(oauth2ClientCredsDestName, string(destinationcreatorpkg.AuthTypeOAuth2ClientCredentials), externalDestinationSubaccountID), + } +} + +func fixOAuth2ClientCredsAuthn() *operators.OAuth2ClientCredentialsAuthentication { + return &operators.OAuth2ClientCredentialsAuthentication{ + URL: destinationURL, + TokenServiceURL: oauth2ClientCredsTokenServiceURL, + ClientID: oauth2ClientCredsClientID, + ClientSecret: oauth2ClientCredsClientSecret, + } +} + func fixColumns() []string { return []string{"id", "name", "type", "url", "authentication", "tenant_id", "bundle_id", "revision", "instance_id", "formation_assignment_id"} } diff --git a/components/director/internal/domain/destination/service.go b/components/director/internal/domain/destination/service.go index 6ece1e6c50..d9c5c4d459 100644 --- a/components/director/internal/domain/destination/service.go +++ b/components/director/internal/domain/destination/service.go @@ -41,13 +41,13 @@ type UIDService interface { //go:generate mockery --exported --name=destinationCreatorService --output=automock --outpkg=automock --case=underscore --disable-version-string type destinationCreatorService interface { CreateDesignTimeDestinations(ctx context.Context, destinationDetails operators.Destination, formationAssignment *model.FormationAssignment, depth uint8, skipSubaccountValidation bool) error - CreateBasicCredentialDestinations(ctx context.Context, destinationDetails operators.Destination, basicAuthenticationCredentials operators.BasicAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) error - CreateSAMLAssertionDestination(ctx context.Context, destinationDetails operators.Destination, samlAuthCreds *operators.SAMLAssertionAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) error - CreateClientCertificateDestination(ctx context.Context, destinationDetails operators.Destination, clientCertAuthCreds *operators.ClientCertAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) error + CreateBasicCredentialDestinations(ctx context.Context, destinationDetails operators.Destination, basicAuthenticationCredentials operators.BasicAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) (*destinationcreatorpkg.DestinationInfo, error) + CreateSAMLAssertionDestination(ctx context.Context, destinationDetails operators.Destination, samlAuthCreds *operators.SAMLAssertionAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) (*destinationcreatorpkg.DestinationInfo, error) + CreateClientCertificateDestination(ctx context.Context, destinationDetails operators.Destination, clientCertAuthCreds *operators.ClientCertAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) (*destinationcreatorpkg.DestinationInfo, error) + CreateOAuth2ClientCredentialsDestinations(ctx context.Context, destinationDetails operators.Destination, oauth2ClientCredsCredentials *operators.OAuth2ClientCredentialsAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, depth uint8, skipSubaccountValidation bool) (*destinationcreatorpkg.DestinationInfo, error) DeleteDestination(ctx context.Context, destinationName, externalDestSubaccountID, instanceID string, formationAssignment *model.FormationAssignment, skipSubaccountValidation bool) error DeleteCertificate(ctx context.Context, certificateName, externalDestSubaccountID, instanceID string, formationAssignment *model.FormationAssignment, skipSubaccountValidation bool) error DetermineDestinationSubaccount(ctx context.Context, externalDestSubaccountID string, formationAssignment *model.FormationAssignment, skipSubaccountValidation bool) (string, error) - PrepareBasicRequestBody(ctx context.Context, destinationDetails operators.Destination, basicAuthenticationCredentials operators.BasicAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string) (*destinationcreator.BasicAuthDestinationRequestBody, error) GetConsumerTenant(ctx context.Context, formationAssignment *model.FormationAssignment) (string, error) EnsureDestinationSubaccountIDsCorrectness(ctx context.Context, destinationsDetails []operators.Destination, formationAssignment *model.FormationAssignment, skipSubaccountValidation bool) error } @@ -180,21 +180,17 @@ func (s *Service) createBasicCredentialDestination(ctx context.Context, destinat return errors.Errorf("Already have destination with name: %q and tenant ID: %q for assignment ID: %q. Could not have second destination with the same name and tenant ID but with different assignment ID: %q", destinationDetails.Name, tenantID, *destinationFromDB.FormationAssignmentID, formationAssignment.ID) } - if err = s.destinationCreatorSvc.CreateBasicCredentialDestinations(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs, 0, skipSubaccountValidation); err != nil { - return err - } - - basicReqBody, err := s.destinationCreatorSvc.PrepareBasicRequestBody(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs) + destInfo, err := s.destinationCreatorSvc.CreateBasicCredentialDestinations(ctx, destinationDetails, basicAuthenticationCredentials, formationAssignment, correlationIDs, 0, skipSubaccountValidation) if err != nil { return err } destModel := &model.Destination{ ID: s.uidSvc.Generate(), - Name: basicReqBody.Name, - Type: string(basicReqBody.Type), - URL: basicReqBody.URL, - Authentication: string(basicReqBody.AuthenticationType), + Name: destinationDetails.Name, + Type: string(destInfo.Type), + URL: destInfo.URL, + Authentication: string(destInfo.AuthenticationType), SubaccountID: t.ID, InstanceID: &destinationDetails.InstanceID, FormationAssignmentID: &formationAssignment.ID, @@ -242,16 +238,17 @@ func (s *Service) createSAMLAssertionDestination(ctx context.Context, destinatio return errors.Errorf("Already have destination with name: %q and tenant ID: %q for assignment ID: %q. Could not have second destination with the same name and tenant ID but with different assignment ID: %q", destinationDetails.Name, tenantID, *destinationFromDB.FormationAssignmentID, formationAssignment.ID) } - if err = s.destinationCreatorSvc.CreateSAMLAssertionDestination(ctx, destinationDetails, samlAssertionAuthCredentials, formationAssignment, correlationIDs, 0, skipSubaccountValidation); err != nil { + destInfo, err := s.destinationCreatorSvc.CreateSAMLAssertionDestination(ctx, destinationDetails, samlAssertionAuthCredentials, formationAssignment, correlationIDs, 0, skipSubaccountValidation) + if err != nil { return err } destModel := &model.Destination{ ID: s.uidSvc.Generate(), Name: destinationDetails.Name, - Type: string(destinationcreatorpkg.TypeHTTP), - URL: samlAssertionAuthCredentials.URL, - Authentication: string(destinationcreatorpkg.AuthTypeSAMLAssertion), + Type: string(destInfo.Type), + URL: destInfo.URL, + Authentication: string(destInfo.AuthenticationType), SubaccountID: t.ID, InstanceID: &destinationDetails.InstanceID, FormationAssignmentID: &formationAssignment.ID, @@ -298,16 +295,17 @@ func (s *Service) createClientCertificateAuthenticationDestination(ctx context.C return errors.Errorf("Already have destination with name: %q and tenant ID: %q for assignment ID: %q. Could not have second destination with the same name and tenant ID but with different assignment ID: %q", destinationDetails.Name, tenantID, *destinationFromDB.FormationAssignmentID, formationAssignment.ID) } - if err = s.destinationCreatorSvc.CreateClientCertificateDestination(ctx, destinationDetails, clientCertAuthCredentials, formationAssignment, correlationIDs, 0, skipSubaccountValidation); err != nil { + destInfo, err := s.destinationCreatorSvc.CreateClientCertificateDestination(ctx, destinationDetails, clientCertAuthCredentials, formationAssignment, correlationIDs, 0, skipSubaccountValidation) + if err != nil { return err } destModel := &model.Destination{ ID: s.uidSvc.Generate(), Name: destinationDetails.Name, - Type: string(destinationcreatorpkg.TypeHTTP), - URL: clientCertAuthCredentials.URL, - Authentication: string(destinationcreatorpkg.AuthTypeClientCertificate), + Type: string(destInfo.Type), + URL: destInfo.URL, + Authentication: string(destInfo.AuthenticationType), SubaccountID: t.ID, InstanceID: &destinationDetails.InstanceID, FormationAssignmentID: &formationAssignment.ID, @@ -320,6 +318,65 @@ func (s *Service) createClientCertificateAuthenticationDestination(ctx context.C return nil } +// CreateOAuth2ClientCredentialsDestinations is responsible to create an oauth2 client credentials destination resource in the remote destination service as well as in our DB +func (s *Service) CreateOAuth2ClientCredentialsDestinations(ctx context.Context, destinationsDetails []operators.Destination, oauth2ClientCredsCredentials *operators.OAuth2ClientCredentialsAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, skipSubaccountValidation bool) error { + for _, destinationDetails := range destinationsDetails { + if err := s.createOAuth2ClientCredentialsDestinations(ctx, destinationDetails, oauth2ClientCredsCredentials, formationAssignment, correlationIDs, skipSubaccountValidation); err != nil { + return errors.Wrapf(err, "while creating oauth2 client credentials destination with name: %q", destinationDetails.Name) + } + } + return nil +} + +// createOAuth2ClientCredentialsDestinations is responsible to create an oauth2 client credentials destination resource in the remote destination service as well as in our DB +func (s *Service) createOAuth2ClientCredentialsDestinations(ctx context.Context, destinationDetails operators.Destination, oauth2ClientCredsCredentials *operators.OAuth2ClientCredentialsAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, skipSubaccountValidation bool) error { + subaccountID, err := s.destinationCreatorSvc.DetermineDestinationSubaccount(ctx, destinationDetails.SubaccountID, formationAssignment, skipSubaccountValidation) + if err != nil { + return err + } + destinationDetails.SubaccountID = subaccountID + + t, err := s.tenantRepo.GetByExternalTenant(ctx, subaccountID) + if err != nil { + return errors.Wrapf(err, "while getting tenant by external ID: %q", subaccountID) + } + + tenantID := t.ID + destinationFromDB, err := s.destinationRepo.GetDestinationByNameAndTenant(ctx, destinationDetails.Name, tenantID) + if err != nil { + if !apperrors.IsNotFoundError(err) { + return err + } + log.C(ctx).Infof("Destination with name: %q and tenant ID: %q was not found in our DB, it will be created...", destinationDetails.Name, tenantID) + } + + if destinationFromDB != nil && destinationFromDB.FormationAssignmentID != nil && *destinationFromDB.FormationAssignmentID != formationAssignment.ID { + return errors.Errorf("Already have destination with name: %q and tenant ID: %q for assignment ID: %q. Could not have second destination with the same name and tenant ID but with different assignment ID: %q", destinationDetails.Name, tenantID, *destinationFromDB.FormationAssignmentID, formationAssignment.ID) + } + + destInfo, err := s.destinationCreatorSvc.CreateOAuth2ClientCredentialsDestinations(ctx, destinationDetails, oauth2ClientCredsCredentials, formationAssignment, correlationIDs, 0, skipSubaccountValidation) + if err != nil { + return err + } + + destModel := &model.Destination{ + ID: s.uidSvc.Generate(), + Name: destinationDetails.Name, + Type: string(destInfo.Type), + URL: destInfo.URL, + Authentication: string(destInfo.AuthenticationType), + SubaccountID: t.ID, + InstanceID: &destinationDetails.InstanceID, + FormationAssignmentID: &formationAssignment.ID, + } + + if err = s.destinationRepo.UpsertWithEmbeddedTenant(ctx, destModel); err != nil { + return errors.Wrapf(err, "while upserting oauth2 client creds destination with name: %q and assignment ID: %q in the DB", destinationDetails.Name, formationAssignment.ID) + } + + return nil +} + // DeleteDestinations is responsible to delete all types of destinations associated with the given `formationAssignment` // from the DB as well as from the remote destination service func (s *Service) DeleteDestinations(ctx context.Context, formationAssignment *model.FormationAssignment, skipSubaccountValidation bool) error { diff --git a/components/director/internal/domain/destination/service_test.go b/components/director/internal/domain/destination/service_test.go index 446bec0d05..25a5554994 100644 --- a/components/director/internal/domain/destination/service_test.go +++ b/components/director/internal/domain/destination/service_test.go @@ -259,14 +259,14 @@ func TestService_CreateDesignTimeDestinations(t *testing.T) { func TestService_CreateBasicCredentialDestinations(t *testing.T) { basicDestsDetails := fixBasicDestinationsDetails() basicDestDetails := basicDestsDetails[0] - basicReqBody := fixBasicReqBody() + basicDestInfo := fixBasicDestInfo() destModel := &model.Destination{ ID: fixUUID(), - Name: basicReqBody.Name, - Type: string(basicReqBody.Type), - URL: basicReqBody.URL, - Authentication: string(basicReqBody.AuthenticationType), + Name: basicDestDetails.Name, + Type: string(basicDestInfo.Type), + URL: basicDestInfo.URL, + Authentication: string(basicDestInfo.AuthenticationType), SubaccountID: tenant.ID, InstanceID: &basicDestDetails.InstanceID, FormationAssignmentID: &fa.ID, @@ -287,8 +287,7 @@ func TestService_CreateBasicCredentialDestinations(t *testing.T) { DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { destCreatorSvc := &automock.DestinationCreatorService{} destCreatorSvc.On("DetermineDestinationSubaccount", ctx, basicDestDetails.SubaccountID, &fa, false).Return(basicDestDetails.SubaccountID, nil) - destCreatorSvc.On("CreateBasicCredentialDestinations", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs, initialDepth, false).Return(nil) - destCreatorSvc.On("PrepareBasicRequestBody", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs).Return(basicReqBody, nil) + destCreatorSvc.On("CreateBasicCredentialDestinations", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs, initialDepth, false).Return(basicDestInfo, nil) return destCreatorSvc }, TenantRepoFn: func() *automock.TenantRepository { @@ -313,8 +312,7 @@ func TestService_CreateBasicCredentialDestinations(t *testing.T) { DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { destCreatorSvc := &automock.DestinationCreatorService{} destCreatorSvc.On("DetermineDestinationSubaccount", ctx, basicDestDetails.SubaccountID, &fa, false).Return(basicDestDetails.SubaccountID, nil) - destCreatorSvc.On("CreateBasicCredentialDestinations", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs, initialDepth, false).Return(nil) - destCreatorSvc.On("PrepareBasicRequestBody", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs).Return(basicReqBody, nil) + destCreatorSvc.On("CreateBasicCredentialDestinations", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs, initialDepth, false).Return(basicDestInfo, nil) return destCreatorSvc }, TenantRepoFn: func() *automock.TenantRepository { @@ -400,28 +398,7 @@ func TestService_CreateBasicCredentialDestinations(t *testing.T) { DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { destCreatorSvc := &automock.DestinationCreatorService{} destCreatorSvc.On("DetermineDestinationSubaccount", ctx, basicDestDetails.SubaccountID, &fa, false).Return(basicDestDetails.SubaccountID, nil) - destCreatorSvc.On("CreateBasicCredentialDestinations", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs, initialDepth, false).Return(testErr) - return destCreatorSvc - }, - TenantRepoFn: func() *automock.TenantRepository { - tenantRepo := &automock.TenantRepository{} - tenantRepo.On("GetByExternalTenant", ctx, basicDestDetails.SubaccountID).Return(tenant, nil) - return tenantRepo - }, - DestinationRepoFn: func() *automock.DestinationRepository { - destinationRepo := &automock.DestinationRepository{} - destinationRepo.On("GetDestinationByNameAndTenant", ctx, basicDestDetails.Name, tenant.ID).Return(destModel, nil) - return destinationRepo - }, - ExpectedErrMessage: errMsg, - }, - { - Name: "Error when preparing basic req body", - DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { - destCreatorSvc := &automock.DestinationCreatorService{} - destCreatorSvc.On("DetermineDestinationSubaccount", ctx, basicDestDetails.SubaccountID, &fa, false).Return(basicDestDetails.SubaccountID, nil) - destCreatorSvc.On("CreateBasicCredentialDestinations", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs, initialDepth, false).Return(nil) - destCreatorSvc.On("PrepareBasicRequestBody", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs).Return(nil, testErr) + destCreatorSvc.On("CreateBasicCredentialDestinations", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs, initialDepth, false).Return(nil, testErr) return destCreatorSvc }, TenantRepoFn: func() *automock.TenantRepository { @@ -441,8 +418,7 @@ func TestService_CreateBasicCredentialDestinations(t *testing.T) { DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { destCreatorSvc := &automock.DestinationCreatorService{} destCreatorSvc.On("DetermineDestinationSubaccount", ctx, basicDestDetails.SubaccountID, &fa, false).Return(basicDestDetails.SubaccountID, nil) - destCreatorSvc.On("CreateBasicCredentialDestinations", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs, initialDepth, false).Return(nil) - destCreatorSvc.On("PrepareBasicRequestBody", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs).Return(basicReqBody, nil) + destCreatorSvc.On("CreateBasicCredentialDestinations", ctx, basicDestDetails, basicAuthCreds, &fa, correlationIDs, initialDepth, false).Return(basicDestInfo, nil) return destCreatorSvc }, TenantRepoFn: func() *automock.TenantRepository { @@ -509,13 +485,14 @@ func TestService_CreateClientCertificateAuthenticationDestination(t *testing.T) clientCertAuthDestsDetails := fixClientCertAuthDestinationsDetails() clientCertAuthDestDetails := clientCertAuthDestsDetails[0] clientCertAuthTypeCreds := fixClientCertAuthTypeAuthentication() + clientCertDestInfo := fixClientCertDestInfo() destModel := &model.Destination{ ID: fixUUID(), Name: clientCertAuthDestDetails.Name, - Type: clientCertAuthDestDetails.Type, - URL: clientCertAuthTypeCreds.URL, - Authentication: clientCertAuthDestDetails.Authentication, + Type: string(clientCertDestInfo.Type), + URL: clientCertDestInfo.URL, + Authentication: string(clientCertDestInfo.AuthenticationType), SubaccountID: tenant.ID, InstanceID: &clientCertAuthDestDetails.InstanceID, FormationAssignmentID: &fa.ID, @@ -535,7 +512,7 @@ func TestService_CreateClientCertificateAuthenticationDestination(t *testing.T) DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { destCreatorSvc := &automock.DestinationCreatorService{} destCreatorSvc.On("EnsureDestinationSubaccountIDsCorrectness", ctx, clientCertAuthDestsDetails, &fa, false).Return(nil) - destCreatorSvc.On("CreateClientCertificateDestination", ctx, clientCertAuthDestDetails, clientCertAuthTypeCreds, &fa, correlationIDs, initialDepth, false).Return(nil) + destCreatorSvc.On("CreateClientCertificateDestination", ctx, clientCertAuthDestDetails, clientCertAuthTypeCreds, &fa, correlationIDs, initialDepth, false).Return(clientCertDestInfo, nil) return destCreatorSvc }, TenantRepoFn: func() *automock.TenantRepository { @@ -560,7 +537,7 @@ func TestService_CreateClientCertificateAuthenticationDestination(t *testing.T) DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { destCreatorSvc := &automock.DestinationCreatorService{} destCreatorSvc.On("EnsureDestinationSubaccountIDsCorrectness", ctx, clientCertAuthDestsDetails, &fa, false).Return(nil) - destCreatorSvc.On("CreateClientCertificateDestination", ctx, clientCertAuthDestDetails, clientCertAuthTypeCreds, &fa, correlationIDs, initialDepth, false).Return(nil) + destCreatorSvc.On("CreateClientCertificateDestination", ctx, clientCertAuthDestDetails, clientCertAuthTypeCreds, &fa, correlationIDs, initialDepth, false).Return(clientCertDestInfo, nil) return destCreatorSvc }, TenantRepoFn: func() *automock.TenantRepository { @@ -646,7 +623,7 @@ func TestService_CreateClientCertificateAuthenticationDestination(t *testing.T) DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { destCreatorSvc := &automock.DestinationCreatorService{} destCreatorSvc.On("EnsureDestinationSubaccountIDsCorrectness", ctx, clientCertAuthDestsDetails, &fa, false).Return(nil) - destCreatorSvc.On("CreateClientCertificateDestination", ctx, clientCertAuthDestDetails, clientCertAuthTypeCreds, &fa, correlationIDs, initialDepth, false).Return(testErr) + destCreatorSvc.On("CreateClientCertificateDestination", ctx, clientCertAuthDestDetails, clientCertAuthTypeCreds, &fa, correlationIDs, initialDepth, false).Return(nil, testErr) return destCreatorSvc }, TenantRepoFn: func() *automock.TenantRepository { @@ -666,7 +643,7 @@ func TestService_CreateClientCertificateAuthenticationDestination(t *testing.T) DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { destCreatorSvc := &automock.DestinationCreatorService{} destCreatorSvc.On("EnsureDestinationSubaccountIDsCorrectness", ctx, clientCertAuthDestsDetails, &fa, false).Return(nil) - destCreatorSvc.On("CreateClientCertificateDestination", ctx, clientCertAuthDestDetails, clientCertAuthTypeCreds, &fa, correlationIDs, initialDepth, false).Return(nil) + destCreatorSvc.On("CreateClientCertificateDestination", ctx, clientCertAuthDestDetails, clientCertAuthTypeCreds, &fa, correlationIDs, initialDepth, false).Return(clientCertDestInfo, nil) return destCreatorSvc }, TenantRepoFn: func() *automock.TenantRepository { @@ -733,13 +710,14 @@ func TestService_CreateSAMLAssertionDestinations(t *testing.T) { samlAssertionDestsDetails := fixSAMLAssertionDestinationsDetails() samlAssertionDestDetails := samlAssertionDestsDetails[0] samlAuthCreds := fixSAMLAssertionAuthentication() + samlDestInfo := fixSAMLDestInfo() destModel := &model.Destination{ ID: fixUUID(), Name: samlAssertionDestDetails.Name, - Type: samlAssertionDestDetails.Type, - URL: samlAuthCreds.URL, - Authentication: samlAssertionDestDetails.Authentication, + Type: string(samlDestInfo.Type), + URL: samlDestInfo.URL, + Authentication: string(samlDestInfo.AuthenticationType), SubaccountID: tenant.ID, InstanceID: &samlAssertionDestDetails.InstanceID, FormationAssignmentID: &fa.ID, @@ -759,7 +737,7 @@ func TestService_CreateSAMLAssertionDestinations(t *testing.T) { DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { destCreatorSvc := &automock.DestinationCreatorService{} destCreatorSvc.On("EnsureDestinationSubaccountIDsCorrectness", ctx, samlAssertionDestsDetails, &fa, false).Return(nil) - destCreatorSvc.On("CreateSAMLAssertionDestination", ctx, samlAssertionDestDetails, samlAuthCreds, &fa, correlationIDs, initialDepth, false).Return(nil) + destCreatorSvc.On("CreateSAMLAssertionDestination", ctx, samlAssertionDestDetails, samlAuthCreds, &fa, correlationIDs, initialDepth, false).Return(samlDestInfo, nil) return destCreatorSvc }, TenantRepoFn: func() *automock.TenantRepository { @@ -784,7 +762,7 @@ func TestService_CreateSAMLAssertionDestinations(t *testing.T) { DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { destCreatorSvc := &automock.DestinationCreatorService{} destCreatorSvc.On("EnsureDestinationSubaccountIDsCorrectness", ctx, samlAssertionDestsDetails, &fa, false).Return(nil) - destCreatorSvc.On("CreateSAMLAssertionDestination", ctx, samlAssertionDestDetails, samlAuthCreds, &fa, correlationIDs, initialDepth, false).Return(nil) + destCreatorSvc.On("CreateSAMLAssertionDestination", ctx, samlAssertionDestDetails, samlAuthCreds, &fa, correlationIDs, initialDepth, false).Return(samlDestInfo, nil) return destCreatorSvc }, TenantRepoFn: func() *automock.TenantRepository { @@ -870,7 +848,7 @@ func TestService_CreateSAMLAssertionDestinations(t *testing.T) { DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { destCreatorSvc := &automock.DestinationCreatorService{} destCreatorSvc.On("EnsureDestinationSubaccountIDsCorrectness", ctx, samlAssertionDestsDetails, &fa, false).Return(nil) - destCreatorSvc.On("CreateSAMLAssertionDestination", ctx, samlAssertionDestDetails, samlAuthCreds, &fa, correlationIDs, initialDepth, false).Return(testErr) + destCreatorSvc.On("CreateSAMLAssertionDestination", ctx, samlAssertionDestDetails, samlAuthCreds, &fa, correlationIDs, initialDepth, false).Return(nil, testErr) return destCreatorSvc }, TenantRepoFn: func() *automock.TenantRepository { @@ -890,7 +868,7 @@ func TestService_CreateSAMLAssertionDestinations(t *testing.T) { DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { destCreatorSvc := &automock.DestinationCreatorService{} destCreatorSvc.On("EnsureDestinationSubaccountIDsCorrectness", ctx, samlAssertionDestsDetails, &fa, false).Return(nil) - destCreatorSvc.On("CreateSAMLAssertionDestination", ctx, samlAssertionDestDetails, samlAuthCreds, &fa, correlationIDs, initialDepth, false).Return(nil) + destCreatorSvc.On("CreateSAMLAssertionDestination", ctx, samlAssertionDestDetails, samlAuthCreds, &fa, correlationIDs, initialDepth, false).Return(samlDestInfo, nil) return destCreatorSvc }, TenantRepoFn: func() *automock.TenantRepository { @@ -953,6 +931,231 @@ func TestService_CreateSAMLAssertionDestinations(t *testing.T) { } } +func TestService_CreateOAuth2ClientCredentialsDestinations(t *testing.T) { + oauth2ClientCredsDestsDetails := fixOAuth2ClientCredsDestinationsDetails() + oauth2ClientCredsDestDetails := oauth2ClientCredsDestsDetails[0] + oauth2ClientCredsDestInfo := fixOAuth2ClientCredsDestInfo() + + destModel := &model.Destination{ + ID: fixUUID(), + Name: oauth2ClientCredsDestDetails.Name, + Type: string(oauth2ClientCredsDestInfo.Type), + URL: oauth2ClientCredsDestInfo.URL, + Authentication: string(oauth2ClientCredsDestInfo.AuthenticationType), + SubaccountID: tenant.ID, + InstanceID: &oauth2ClientCredsDestDetails.InstanceID, + FormationAssignmentID: &fa.ID, + } + destModelWithDifferentFAID := fixDestinationModelWithAuthnAndFAID(oauth2ClientCredsDestName, string(destinationcreatorpkg.AuthTypeOAuth2ClientCredentials), secondDestinationFormationAssignmentID) + oaut2ClientCreds := fixOAuth2ClientCredsAuthn() + + testCases := []struct { + Name string + DestinationCreatorServiceFn func() *automock.DestinationCreatorService + TenantRepoFn func() *automock.TenantRepository + DestinationRepoFn func() *automock.DestinationRepository + UIDServiceFn func() *automock.UIDService + ExpectedErrMessage string + }{ + { + Name: "Success", + DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { + destCreatorSvc := &automock.DestinationCreatorService{} + destCreatorSvc.On("DetermineDestinationSubaccount", ctx, oauth2ClientCredsDestDetails.SubaccountID, &fa, false).Return(oauth2ClientCredsDestDetails.SubaccountID, nil) + destCreatorSvc.On("CreateOAuth2ClientCredentialsDestinations", ctx, oauth2ClientCredsDestDetails, oaut2ClientCreds, &fa, correlationIDs, initialDepth, false).Return(oauth2ClientCredsDestInfo, nil) + return destCreatorSvc + }, + TenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", ctx, oauth2ClientCredsDestDetails.SubaccountID).Return(tenant, nil) + return tenantRepo + }, + DestinationRepoFn: func() *automock.DestinationRepository { + destinationRepo := &automock.DestinationRepository{} + destinationRepo.On("GetDestinationByNameAndTenant", ctx, oauth2ClientCredsDestDetails.Name, tenant.ID).Return(destModel, nil) + destinationRepo.On("UpsertWithEmbeddedTenant", ctx, destModel).Return(nil) + return destinationRepo + }, + UIDServiceFn: func() *automock.UIDService { + uidSvc := &automock.UIDService{} + uidSvc.On("Generate").Return(fixUUID()) + return uidSvc + }, + }, + { + Name: "Success when there is no destination in db", + DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { + destCreatorSvc := &automock.DestinationCreatorService{} + destCreatorSvc.On("DetermineDestinationSubaccount", ctx, oauth2ClientCredsDestDetails.SubaccountID, &fa, false).Return(oauth2ClientCredsDestDetails.SubaccountID, nil) + destCreatorSvc.On("CreateOAuth2ClientCredentialsDestinations", ctx, oauth2ClientCredsDestDetails, oaut2ClientCreds, &fa, correlationIDs, initialDepth, false).Return(oauth2ClientCredsDestInfo, nil) + return destCreatorSvc + }, + TenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", ctx, oauth2ClientCredsDestDetails.SubaccountID).Return(tenant, nil) + return tenantRepo + }, + DestinationRepoFn: func() *automock.DestinationRepository { + destinationRepo := &automock.DestinationRepository{} + destinationRepo.On("GetDestinationByNameAndTenant", ctx, oauth2ClientCredsDestDetails.Name, tenant.ID).Return(nil, apperrors.NewNotFoundErrorWithType(resource.Destination)) + destinationRepo.On("UpsertWithEmbeddedTenant", ctx, destModel).Return(nil) + return destinationRepo + }, + UIDServiceFn: func() *automock.UIDService { + uidSvc := &automock.UIDService{} + uidSvc.On("Generate").Return(fixUUID()) + return uidSvc + }, + }, + { + Name: "Error when validating destination subaccount", + DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { + destCreatorSvc := &automock.DestinationCreatorService{} + destCreatorSvc.On("DetermineDestinationSubaccount", ctx, oauth2ClientCredsDestDetails.SubaccountID, &fa, false).Return("", testErr) + return destCreatorSvc + }, + ExpectedErrMessage: errMsg, + }, + { + Name: "Error when getting tenant by external ID", + DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { + destCreatorSvc := &automock.DestinationCreatorService{} + destCreatorSvc.On("DetermineDestinationSubaccount", ctx, oauth2ClientCredsDestDetails.SubaccountID, &fa, false).Return(oauth2ClientCredsDestDetails.SubaccountID, nil) + return destCreatorSvc + }, + TenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", ctx, oauth2ClientCredsDestDetails.SubaccountID).Return(nil, testErr) + return tenantRepo + }, + ExpectedErrMessage: "while getting tenant by external ID", + }, + { + Name: "Error when getting destination from db and error is different from 'Not Found'", + DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { + destCreatorSvc := &automock.DestinationCreatorService{} + destCreatorSvc.On("DetermineDestinationSubaccount", ctx, oauth2ClientCredsDestDetails.SubaccountID, &fa, false).Return(oauth2ClientCredsDestDetails.SubaccountID, nil) + return destCreatorSvc + }, + TenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", ctx, oauth2ClientCredsDestDetails.SubaccountID).Return(tenant, nil) + return tenantRepo + }, + DestinationRepoFn: func() *automock.DestinationRepository { + destinationRepo := &automock.DestinationRepository{} + destinationRepo.On("GetDestinationByNameAndTenant", ctx, oauth2ClientCredsDestDetails.Name, tenant.ID).Return(nil, testErr) + return destinationRepo + }, + ExpectedErrMessage: errMsg, + }, + { + Name: "Error when destination from db is found but its formation assignment id is different from the provided formation assignment", + DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { + destCreatorSvc := &automock.DestinationCreatorService{} + destCreatorSvc.On("DetermineDestinationSubaccount", ctx, oauth2ClientCredsDestDetails.SubaccountID, &fa, false).Return(oauth2ClientCredsDestDetails.SubaccountID, nil) + return destCreatorSvc + }, + TenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", ctx, oauth2ClientCredsDestDetails.SubaccountID).Return(tenant, nil) + return tenantRepo + }, + DestinationRepoFn: func() *automock.DestinationRepository { + destinationRepo := &automock.DestinationRepository{} + destinationRepo.On("GetDestinationByNameAndTenant", ctx, oauth2ClientCredsDestDetails.Name, tenant.ID).Return(destModelWithDifferentFAID, nil) + return destinationRepo + }, + ExpectedErrMessage: "Could not have second destination with the same name and tenant ID but with different assignment ID", + }, + { + Name: "Error when creating destination via destination creator service", + DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { + destCreatorSvc := &automock.DestinationCreatorService{} + destCreatorSvc.On("DetermineDestinationSubaccount", ctx, oauth2ClientCredsDestDetails.SubaccountID, &fa, false).Return(oauth2ClientCredsDestDetails.SubaccountID, nil) + destCreatorSvc.On("CreateOAuth2ClientCredentialsDestinations", ctx, oauth2ClientCredsDestDetails, oaut2ClientCreds, &fa, correlationIDs, initialDepth, false).Return(nil, testErr) + return destCreatorSvc + }, + TenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", ctx, oauth2ClientCredsDestDetails.SubaccountID).Return(tenant, nil) + return tenantRepo + }, + DestinationRepoFn: func() *automock.DestinationRepository { + destinationRepo := &automock.DestinationRepository{} + destinationRepo.On("GetDestinationByNameAndTenant", ctx, oauth2ClientCredsDestDetails.Name, tenant.ID).Return(destModel, nil) + return destinationRepo + }, + ExpectedErrMessage: errMsg, + }, + { + Name: "Error when upserting destination in db", + DestinationCreatorServiceFn: func() *automock.DestinationCreatorService { + destCreatorSvc := &automock.DestinationCreatorService{} + destCreatorSvc.On("DetermineDestinationSubaccount", ctx, oauth2ClientCredsDestDetails.SubaccountID, &fa, false).Return(oauth2ClientCredsDestDetails.SubaccountID, nil) + destCreatorSvc.On("CreateOAuth2ClientCredentialsDestinations", ctx, oauth2ClientCredsDestDetails, oaut2ClientCreds, &fa, correlationIDs, initialDepth, false).Return(oauth2ClientCredsDestInfo, nil) + return destCreatorSvc + }, + TenantRepoFn: func() *automock.TenantRepository { + tenantRepo := &automock.TenantRepository{} + tenantRepo.On("GetByExternalTenant", ctx, oauth2ClientCredsDestDetails.SubaccountID).Return(tenant, nil) + return tenantRepo + }, + DestinationRepoFn: func() *automock.DestinationRepository { + destinationRepo := &automock.DestinationRepository{} + destinationRepo.On("GetDestinationByNameAndTenant", ctx, oauth2ClientCredsDestDetails.Name, tenant.ID).Return(destModel, nil) + destinationRepo.On("UpsertWithEmbeddedTenant", ctx, destModel).Return(testErr) + return destinationRepo + }, + UIDServiceFn: func() *automock.UIDService { + uidSvc := &automock.UIDService{} + uidSvc.On("Generate").Return(fixUUID()) + return uidSvc + }, + ExpectedErrMessage: "while upserting oauth2 client creds destination with name", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + // GIVEN + destCreatorSvc := unusedDestinationCreatorService() + if testCase.DestinationCreatorServiceFn != nil { + destCreatorSvc = testCase.DestinationCreatorServiceFn() + } + + tntRepo := unusedTenantRepository() + if testCase.TenantRepoFn != nil { + tntRepo = testCase.TenantRepoFn() + } + + destRepo := unusedDestinationRepository() + if testCase.DestinationRepoFn != nil { + destRepo = testCase.DestinationRepoFn() + } + + uidSvc := unusedUIDService() + if testCase.UIDServiceFn != nil { + uidSvc = testCase.UIDServiceFn() + } + defer mock.AssertExpectationsForObjects(t, destCreatorSvc, tntRepo, destRepo, uidSvc) + + svc := destination.NewService(nil, destRepo, tntRepo, uidSvc, destCreatorSvc) + + // WHEN + err := svc.CreateOAuth2ClientCredentialsDestinations(ctx, oauth2ClientCredsDestsDetails, oaut2ClientCreds, &fa, correlationIDs, false) + + // THEN + if testCase.ExpectedErrMessage == "" { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), testCase.ExpectedErrMessage) + } + }) + } +} + func TestService_DeleteDestinations(t *testing.T) { basicDestModel := fixDestinationModelWithAuthnAndFAID(basicDestName, string(destinationcreatorpkg.AuthTypeBasic), fa.ID) samlDestModel := fixDestinationModelWithAuthnAndFAID(samlAssertionDestName, string(destinationcreatorpkg.AuthTypeSAMLAssertion), fa.ID) diff --git a/components/director/internal/domain/formationconstraint/operators/automock/destination_service.go b/components/director/internal/domain/formationconstraint/operators/automock/destination_service.go index 805f5c879c..c966e158bb 100644 --- a/components/director/internal/domain/formationconstraint/operators/automock/destination_service.go +++ b/components/director/internal/domain/formationconstraint/operators/automock/destination_service.go @@ -58,6 +58,20 @@ func (_m *DestinationService) CreateDesignTimeDestinations(ctx context.Context, return r0 } +// CreateOAuth2ClientCredentialsDestinations provides a mock function with given fields: ctx, destinationsDetails, oauth2ClientCredsCredentials, formationAssignment, correlationIDs, skipSubaccountValidation +func (_m *DestinationService) CreateOAuth2ClientCredentialsDestinations(ctx context.Context, destinationsDetails []operators.Destination, oauth2ClientCredsCredentials *operators.OAuth2ClientCredentialsAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, skipSubaccountValidation bool) error { + ret := _m.Called(ctx, destinationsDetails, oauth2ClientCredsCredentials, formationAssignment, correlationIDs, skipSubaccountValidation) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []operators.Destination, *operators.OAuth2ClientCredentialsAuthentication, *model.FormationAssignment, []string, bool) error); ok { + r0 = rf(ctx, destinationsDetails, oauth2ClientCredsCredentials, formationAssignment, correlationIDs, skipSubaccountValidation) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // CreateSAMLAssertionDestination provides a mock function with given fields: ctx, destinationsDetails, samlAssertionAuthCredentials, formationAssignment, correlationIDs, skipSubaccountValidation func (_m *DestinationService) CreateSAMLAssertionDestination(ctx context.Context, destinationsDetails []operators.Destination, samlAssertionAuthCredentials *operators.SAMLAssertionAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, skipSubaccountValidation bool) error { ret := _m.Called(ctx, destinationsDetails, samlAssertionAuthCredentials, formationAssignment, correlationIDs, skipSubaccountValidation) diff --git a/components/director/internal/domain/formationconstraint/operators/constraint_engine.go b/components/director/internal/domain/formationconstraint/operators/constraint_engine.go index 66aa7e6f36..9c162088f1 100644 --- a/components/director/internal/domain/formationconstraint/operators/constraint_engine.go +++ b/components/director/internal/domain/formationconstraint/operators/constraint_engine.go @@ -41,6 +41,7 @@ type destinationService interface { CreateBasicCredentialDestinations(ctx context.Context, destinationsDetails []Destination, basicAuthenticationCredentials BasicAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, skipSubaccountValidation bool) error CreateSAMLAssertionDestination(ctx context.Context, destinationsDetails []Destination, samlAssertionAuthCredentials *SAMLAssertionAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, skipSubaccountValidation bool) error CreateClientCertificateAuthenticationDestination(ctx context.Context, destinationsDetails []Destination, clientCertAuthCredentials *ClientCertAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, skipSubaccountValidation bool) error + CreateOAuth2ClientCredentialsDestinations(ctx context.Context, destinationsDetails []Destination, oauth2ClientCredsCredentials *OAuth2ClientCredentialsAuthentication, formationAssignment *model.FormationAssignment, correlationIDs []string, skipSubaccountValidation bool) error DeleteDestinations(ctx context.Context, formationAssignment *model.FormationAssignment, skipSubaccountValidation bool) error } diff --git a/components/director/internal/domain/formationconstraint/operators/destination_creator_operator.go b/components/director/internal/domain/formationconstraint/operators/destination_creator_operator.go index 7778ab026c..064f59f5db 100644 --- a/components/director/internal/domain/formationconstraint/operators/destination_creator_operator.go +++ b/components/director/internal/domain/formationconstraint/operators/destination_creator_operator.go @@ -200,6 +200,15 @@ func (e *ConstraintEngine) DestinationCreator(ctx context.Context, input Operato } } + oauth2ClientCredsDetails := assignmentConfig.Credentials.InboundCommunicationDetails.OAuth2ClientCredentialsDetails + oauth2ClientCredsCreds := reverseAssignmentConfig.Credentials.OutboundCommunicationCredentials.OAuth2ClientCredentialsAuthentication + if oauth2ClientCredsDetails != nil && oauth2ClientCredsCreds != nil && len(oauth2ClientCredsDetails.Destinations) > 0 { + log.C(ctx).Infof("There is/are %d inbound oauth2 client credentials destination(s) details available in the configuration", len(oauth2ClientCredsDetails.Destinations)) + if err := e.destinationSvc.CreateOAuth2ClientCredentialsDestinations(ctx, oauth2ClientCredsDetails.Destinations, oauth2ClientCredsCreds, formationAssignment, oauth2ClientCredsDetails.CorrelationIDs, di.SkipSubaccountValidation); err != nil { + return false, errors.Wrap(err, "while creating oauth2 client credentials destinations") + } + } + log.C(ctx).Infof("Finished executing operator: %q for location with constraint type: %q and operation name: %q during %q operation", DestinationCreatorOperator, di.Location.ConstraintType, di.Location.OperationName, model.AssignFormation) return true, nil } @@ -251,14 +260,12 @@ type OutboundCommunicationCredentials struct { // NoAuthentication represents outbound communication without any authentication type NoAuthentication struct { URL string `json:"url"` - UIURL string `json:"uiUrl,omitempty"` CorrelationIds []string `json:"correlationIds,omitempty"` } // BasicAuthentication represents outbound communication with basic authentication type BasicAuthentication struct { URL string `json:"url"` - UIURL string `json:"uiUrl,omitempty"` Username string `json:"username"` Password string `json:"password"` CorrelationIds []string `json:"correlationIds,omitempty"` @@ -280,14 +287,12 @@ type OAuth2SAMLBearerAssertionAuthentication struct { // ClientCertAuthentication represents outbound communication with client certificate authentication type ClientCertAuthentication struct { URL string `json:"url"` - UIURL string `json:"uiUrl,omitempty"` CorrelationIds []string `json:"correlationIds,omitempty"` } // OAuth2ClientCredentialsAuthentication represents outbound communication with OAuth 2 client credentials authentication type OAuth2ClientCredentialsAuthentication struct { URL string `json:"url"` - UIURL string `json:"uiUrl,omitempty"` TokenServiceURL string `json:"tokenServiceUrl"` ClientID string `json:"clientId"` ClientSecret string `json:"clientSecret"` @@ -300,6 +305,7 @@ type InboundCommunicationDetails struct { SAMLAssertionDetails *InboundSAMLAssertionDetails `json:"samlAssertion,omitempty"` OAuth2SAMLBearerAssertionDetails *InboundOAuth2SAMLBearerAssertionDetails `json:"oauth2SamlBearerAssertion,omitempty"` ClientCertificateAuthenticationDetails *InboundClientCertAuthenticationDetails `json:"clientCertificateAuthentication,omitempty"` + OAuth2ClientCredentialsDetails *InboundOAuth2ClientCredentialsDetails `json:"oauth2ClientCredentials,omitempty"` } // InboundBasicAuthenticationDetails represents inbound communication configuration details for basic authentication @@ -331,6 +337,12 @@ type InboundClientCertAuthenticationDetails struct { Certificate *string `json:"certificate,omitempty"` } +// InboundOAuth2ClientCredentialsDetails represents inbound communication configuration details for oauth2 client credentials authentication +type InboundOAuth2ClientCredentialsDetails struct { + CorrelationIDs []string `json:"correlationIds"` + Destinations []Destination `json:"destinations"` +} + // Destination holds different destination types properties type Destination struct { Name string `json:"name"` @@ -342,6 +354,7 @@ type Destination struct { SubaccountID string `json:"subaccountId,omitempty"` InstanceID string `json:"instanceId,omitempty"` AdditionalProperties json.RawMessage `json:"additionalProperties,omitempty"` + TokenServiceURLType string `json:"tokenServiceURLType,omitempty"` } // CertificateData contains the data for the certificate resource from the destination creator component diff --git a/components/director/internal/domain/formationconstraint/operators/destination_creator_operator_test.go b/components/director/internal/domain/formationconstraint/operators/destination_creator_operator_test.go index ce28339611..edc0cd2e74 100644 --- a/components/director/internal/domain/formationconstraint/operators/destination_creator_operator_test.go +++ b/components/director/internal/domain/formationconstraint/operators/destination_creator_operator_test.go @@ -22,10 +22,12 @@ func TestConstraintOperators_DestinationCreator(t *testing.T) { basicDests := fixBasicDestinations() samlAssertionDests := fixSAMLAssertionDestinations() clientCertAuthDests := fixClientCertAuthDestinations() + oauth2ClientCredsDests := fixOAuth2ClientCredsDestinations() basicCreds := fixBasicCreds() samlAssertionCreds := fixSAMLCreds() clientCertAuthCreds := fixClientCertAuthCreds() + oauth2ClientCreds := fixOAuth2ClientCreds() testCases := []struct { Name string @@ -94,6 +96,7 @@ func TestConstraintOperators_DestinationCreator(t *testing.T) { destSvc.On("CreateBasicCredentialDestinations", ctx, basicDests, basicCreds, fa, corrleationIDs, false).Return(nil).Once() destSvc.On("CreateSAMLAssertionDestination", ctx, samlAssertionDests, samlAssertionCreds, fa, corrleationIDs, false).Return(nil).Once() destSvc.On("CreateClientCertificateAuthenticationDestination", ctx, clientCertAuthDests, clientCertAuthCreds, fa, corrleationIDs, false).Return(nil).Once() + destSvc.On("CreateOAuth2ClientCredentialsDestinations", ctx, oauth2ClientCredsDests, oauth2ClientCreds, fa, corrleationIDs, false).Return(nil).Once() return destSvc }, ExpectedResult: true, @@ -295,6 +298,19 @@ func TestConstraintOperators_DestinationCreator(t *testing.T) { }, ExpectedErrorMsg: fmt.Sprintf("while creating client certificate authentication destinations: %s", testErr.Error()), }, + { + Name: "Error when operation is 'assign' and location is 'SendNotification' and the creation of oauth2 client creds destinations fails", + Input: inputForAssignSendNotification, + DestinationSvc: func() *automock.DestinationService { + destSvc := &automock.DestinationService{} + destSvc.On("CreateBasicCredentialDestinations", ctx, basicDests, basicCreds, fa, corrleationIDs, false).Return(nil).Once() + destSvc.On("CreateSAMLAssertionDestination", ctx, samlAssertionDests, samlAssertionCreds, fa, corrleationIDs, false).Return(nil).Once() + destSvc.On("CreateClientCertificateAuthenticationDestination", ctx, clientCertAuthDests, clientCertAuthCreds, fa, corrleationIDs, false).Return(nil).Once() + destSvc.On("CreateOAuth2ClientCredentialsDestinations", ctx, oauth2ClientCredsDests, oauth2ClientCreds, fa, corrleationIDs, false).Return(testErr).Once() + return destSvc + }, + ExpectedErrorMsg: fmt.Sprintf("while creating oauth2 client credentials destinations: %s", testErr.Error()), + }, } for _, testCase := range testCases { t.Run(testCase.Name, func(t *testing.T) { diff --git a/components/director/internal/domain/formationconstraint/operators/fixtures_test.go b/components/director/internal/domain/formationconstraint/operators/fixtures_test.go index 113ac74685..992de2f94a 100644 --- a/components/director/internal/domain/formationconstraint/operators/fixtures_test.go +++ b/components/director/internal/domain/formationconstraint/operators/fixtures_test.go @@ -38,18 +38,22 @@ const ( testCertChain = "test-cert-chain" // Destination constants - designTimeDestName = "design-time-name" - basicDestName = "name-basic" - samlAssertionDestName = "saml-assertion-name" - clientCertAuthDestName = "client-cert-auth-dest-name" - destinationURL = "http://test-url" - destinationType = destinationcreatorpkg.TypeHTTP - destinationProxyType = destinationcreatorpkg.ProxyTypeInternet - destinationNoAuthn = destinationcreatorpkg.AuthTypeNoAuth + designTimeDestName = "design-time-name" + basicDestName = "name-basic" + samlAssertionDestName = "saml-assertion-name" + clientCertAuthDestName = "client-cert-auth-dest-name" + oauth2ClientCredsDestName = "oauth2-client-creds-name" + destinationURL = "http://test-url" + destinationType = destinationcreatorpkg.TypeHTTP + destinationProxyType = destinationcreatorpkg.ProxyTypeInternet + destinationNoAuthn = destinationcreatorpkg.AuthTypeNoAuth // Creds constants - basicDestUser = "user" - basicDestPassword = "pwd" + basicDestUser = "user" + basicDestPassword = "pwd" + oauth2ClientCredsDestTokenServiceURL = "http://test-token-url" + oauth2ClientCredsDestClientID = "test-client-id" + oauth2ClientCredsDestClientSecret = "test-client-secret" // Other formationConstraintName = "test constraint" @@ -116,10 +120,10 @@ var ( invalidFAConfig = json.RawMessage("invalid-destination-config") configWithDifferentStructure = json.RawMessage(testJSONConfig) destsConfigValueRawJSON = json.RawMessage( - fmt.Sprintf(`{"credentials":{"inboundCommunication":{"samlAssertion":{"destinations":[{"url":"%s","name":"%s"}]},"clientCertificateAuthentication":{"destinations":[{"url":"%s","name":"%s"}]},"basicAuthentication":{"destinations":[{"url":"%s","name":"%s"}]}}},"destinations":[{"url":"%s","name":"%s","type":"%s","proxyType":"%s","authentication":"%s"}]}`, destinationURL, samlAssertionDestName, destinationURL, clientCertAuthDestName, destinationURL, basicDestName, destinationURL, designTimeDestName, string(destinationType), string(destinationProxyType), string(destinationNoAuthn)), + fmt.Sprintf(`{"credentials":{"inboundCommunication":{"samlAssertion":{"destinations":[{"url":"%s","name":"%s"}]},"clientCertificateAuthentication":{"destinations":[{"url":"%s","name":"%s"}]},"basicAuthentication":{"destinations":[{"url":"%s","name":"%s"}]},"oauth2ClientCredentials":{"destinations":[{"url":"%s","name":"%s"}]}}},"destinations":[{"url":"%s","name":"%s","type":"%s","proxyType":"%s","authentication":"%s"}]}`, destinationURL, samlAssertionDestName, destinationURL, clientCertAuthDestName, destinationURL, basicDestName, destinationURL, oauth2ClientCredsDestName, destinationURL, designTimeDestName, string(destinationType), string(destinationProxyType), string(destinationNoAuthn)), ) destsReverseConfigValueRawJSON = json.RawMessage( - fmt.Sprintf(`{"credentials":{"inboundCommunication":{"samlAssertion":{"destinations":[{"url":"%s","name":"%s"}]},"basicAuthentication":{"destinations":[{"url":"%s","name":"%s"}]}},"outboundCommunication":{"basicAuthentication":{"url":"%s","username":"%s","password":"%s"},"samlAssertion":{"url":"%s"},"clientCertificateAuthentication":{"url":"%s"}}},"destinations":[{"url":"%s","name":"%s","type":"%s","proxyType":"%s","authentication":"%s"}]}`, destinationURL, samlAssertionDestName, destinationURL, basicDestName, destinationURL, basicDestUser, basicDestPassword, destinationURL, destinationURL, destinationURL, designTimeDestName, string(destinationType), string(destinationProxyType), string(destinationNoAuthn)), + fmt.Sprintf(`{"credentials":{"inboundCommunication":{"samlAssertion":{"destinations":[{"url":"%s","name":"%s"}]},"basicAuthentication":{"destinations":[{"url":"%s","name":"%s"}]}},"outboundCommunication":{"basicAuthentication":{"url":"%s","username":"%s","password":"%s"},"samlAssertion":{"url":"%s"},"clientCertificateAuthentication":{"url":"%s"},"oauth2ClientCredentials":{"url":"%s","tokenServiceURL":"%s","clientId":"%s","clientSecret":"%s"}}},"destinations":[{"url":"%s","name":"%s","type":"%s","proxyType":"%s","authentication":"%s"}]}`, destinationURL, samlAssertionDestName, destinationURL, basicDestName, destinationURL, basicDestUser, basicDestPassword, destinationURL, destinationURL, destinationURL, oauth2ClientCredsDestTokenServiceURL, oauth2ClientCredsDestClientID, oauth2ClientCredsDestClientSecret, destinationURL, designTimeDestName, string(destinationType), string(destinationProxyType), string(destinationNoAuthn)), ) destsConfigWithSAMLCertDataRawJSON = json.RawMessage( @@ -389,6 +393,12 @@ func fixClientCertAuthDestinations() []operators.Destination { } } +func fixOAuth2ClientCredsDestinations() []operators.Destination { + return []operators.Destination{ + fixDestination(oauth2ClientCredsDestName, destinationURL), + } +} + func fixDestination(name, url string) operators.Destination { return operators.Destination{ Name: name, @@ -416,6 +426,15 @@ func fixClientCertAuthCreds() *operators.ClientCertAuthentication { } } +func fixOAuth2ClientCreds() *operators.OAuth2ClientCredentialsAuthentication { + return &operators.OAuth2ClientCredentialsAuthentication{ + URL: destinationURL, + TokenServiceURL: oauth2ClientCredsDestTokenServiceURL, + ClientID: oauth2ClientCredsDestClientID, + ClientSecret: oauth2ClientCredsDestClientSecret, + } +} + func fixCertificateData() *operators.CertificateData { return &operators.CertificateData{ FileName: testFileName, diff --git a/components/director/pkg/destinationcreator/types.go b/components/director/pkg/destinationcreator/types.go index ed4f3aac92..6c5b90f0a3 100644 --- a/components/director/pkg/destinationcreator/types.go +++ b/components/director/pkg/destinationcreator/types.go @@ -20,6 +20,8 @@ const ( AuthTypeSAMLBearerAssertion AuthType = "OAuth2SAMLBearerAssertion" // AuthTypeClientCertificate represents the ClientCertificate destination authentication AuthTypeClientCertificate AuthType = "ClientCertificateAuthentication" + // AuthTypeOAuth2ClientCredentials represents the OAuth2ClientCredentials destination authentication + AuthTypeOAuth2ClientCredentials AuthType = "OAuth2ClientCredentials" // ProxyTypeInternet represents the Internet proxy type ProxyTypeInternet ProxyType = "Internet" @@ -37,6 +39,11 @@ const ( SAMLAssertionDestPath = "credentials.inboundCommunication.samlAssertion" // ClientCertAuthDestPath represents the client certificate authentication destination type in the assignment config ClientCertAuthDestPath = "credentials.inboundCommunication.clientCertificateAuthentication" + + // DedicatedTokenServiceURLType represents the 'Dedicated' token service URL type of OAuth2ClientCredentials destination + DedicatedTokenServiceURLType TokenServiceURLType = "Dedicated" + // CommonTokenServiceURLType represents the 'Common' token service URL type of OAuth2ClientCredentials destination + CommonTokenServiceURLType TokenServiceURLType = "Common" ) // Type represents the destination type @@ -47,3 +54,15 @@ type AuthType string // ProxyType represents the destination proxy type type ProxyType string + +// TokenServiceURLType represents the token service URL type of OAuth2ClientCredentials destination +type TokenServiceURLType string + +// DestinationInfo holds information about some destination fields +// these fields are calculated before calling DestinationCreator and then are passed to it +// however, after we call the DestinationCreator, we have to store the given destination in our db; we want to reuse the already calculated fields via this struct so that we can use them when storing the destination in the db +type DestinationInfo struct { + AuthenticationType AuthType + Type Type + URL string +} diff --git a/components/external-services-mock/go.mod b/components/external-services-mock/go.mod index 09228d5efa..77c1a5b999 100644 --- a/components/external-services-mock/go.mod +++ b/components/external-services-mock/go.mod @@ -31,7 +31,7 @@ require ( require ( github.com/go-ozzo/ozzo-validation/v4 v4.3.0 - github.com/kyma-incubator/compass/components/director v0.0.0-20231120121524-9ee5c58d6fb0 + github.com/kyma-incubator/compass/components/director v0.0.0-20231124153642-13a4704c40cf github.com/kyma-incubator/compass/components/gateway v0.0.0-20231120121524-9ee5c58d6fb0 github.com/tidwall/sjson v1.2.5 k8s.io/apimachinery v0.26.9 @@ -62,7 +62,7 @@ require ( github.com/tidwall/pretty v1.2.0 // indirect github.com/vektah/gqlparser/v2 v2.1.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/oauth2 v0.11.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect diff --git a/components/external-services-mock/go.sum b/components/external-services-mock/go.sum index 70a65cc491..d6453aba5c 100644 --- a/components/external-services-mock/go.sum +++ b/components/external-services-mock/go.sum @@ -96,8 +96,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kyma-incubator/compass/components/director v0.0.0-20231120121524-9ee5c58d6fb0 h1:C4/i82PXIpiYhqHcV0w8JLBEIar71GKGhYlat+s92Sk= -github.com/kyma-incubator/compass/components/director v0.0.0-20231120121524-9ee5c58d6fb0/go.mod h1:e38rK7ggp6z3wztnwzYKuBIzqKeZPez6TGXKALvbW+A= +github.com/kyma-incubator/compass/components/director v0.0.0-20231124153642-13a4704c40cf h1:P9h2uNSO7qr2F2/04zJLGmLKimNnaSiLTgDOJiNcBIE= +github.com/kyma-incubator/compass/components/director v0.0.0-20231124153642-13a4704c40cf/go.mod h1:yRzGhOVE0t/ORiHPLLeVHN0P24cJ91NPVqnTDNFY6Ok= github.com/kyma-incubator/compass/components/gateway v0.0.0-20231120121524-9ee5c58d6fb0 h1:aWWboEJuEqEEX3wNd9vBAOZ77pQN+6zRt0c6k+o54qk= github.com/kyma-incubator/compass/components/gateway v0.0.0-20231120121524-9ee5c58d6fb0/go.mod h1:J2eSb/ghFiGt7OjWpw/POUYuuJOcvPSoGNwwKYQ/O0k= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= @@ -222,8 +222,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/components/external-services-mock/internal/destinationcreator/handler.go b/components/external-services-mock/internal/destinationcreator/handler.go index 07734942b9..31b32cf45a 100644 --- a/components/external-services-mock/internal/destinationcreator/handler.go +++ b/components/external-services-mock/internal/destinationcreator/handler.go @@ -95,6 +95,8 @@ func (h *Handler) CreateDestinations(writer http.ResponseWriter, r *http.Request destinationRequestBody = &SAMLAssertionDestRequestBody{} case destinationcreatorpkg.AuthTypeClientCertificate: destinationRequestBody = &ClientCertificateAuthDestRequestBody{} + case destinationcreatorpkg.AuthTypeOAuth2ClientCredentials: + destinationRequestBody = &OAuth2ClientCredsDestRequestBody{} default: err := errors.Errorf("The provided destination authentication type: %s is invalid", authTypeResult.String()) httphelpers.RespondWithError(ctx, writer, err, err.Error(), correlationID, http.StatusInternalServerError) @@ -250,7 +252,7 @@ func (h *Handler) createDestination(ctx context.Context, bodyBytes []byte, reqBo } log.C(ctx).Infof("Validating %s destination request body...", destinationTypeName) - if err := reqBody.Validate(h.Config); err != nil { + if err := reqBody.Validate(); err != nil { return http.StatusBadRequest, errors.Wrapf(err, "An error occurred while validating %s destination request body", destinationTypeName) } @@ -372,6 +374,12 @@ func (h *Handler) buildFindAPIResponse(dest destinationcreator.Destination, r *h return "", err } findAPIResponse = fmt.Sprintf(FindAPIClientCertDestResponseTemplate, subaccountID, instanceID, clientCertDest.Name, clientCertDest.Type, clientCertDest.URL, clientCertDest.Authentication, clientCertDest.ProxyType, clientCertDest.KeyStoreLocation, certResponseName) + case destinationcreator.OAuth2ClientCredentialsType: + oauth2ClientCredsDest, ok := dest.(*destinationcreator.OAuth2ClientCredentialsDestination) + if !ok { + return "", errors.New("error while type asserting destination to OAuth2ClientCredentials one") + } + findAPIResponse = fmt.Sprintf(FindAPIOAuth2ClientCredsDestResponseTemplate, subaccountID, instanceID, oauth2ClientCredsDest.Name, oauth2ClientCredsDest.Type, oauth2ClientCredsDest.URL, oauth2ClientCredsDest.Authentication, oauth2ClientCredsDest.ProxyType, oauth2ClientCredsDest.ClientID, oauth2ClientCredsDest.ClientSecret, oauth2ClientCredsDest.TokenServiceURL) } return findAPIResponse, nil diff --git a/components/external-services-mock/internal/destinationcreator/handler_test.go b/components/external-services-mock/internal/destinationcreator/handler_test.go index fee335a143..477b1de70d 100644 --- a/components/external-services-mock/internal/destinationcreator/handler_test.go +++ b/components/external-services-mock/internal/destinationcreator/handler_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "net/http/httptest" "testing" @@ -331,7 +330,7 @@ func TestHandler_DeleteDestinations(t *testing.T) { h.DeleteDestinations(r, req) resp := r.Result() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) require.NoError(t, err) // THEN @@ -449,7 +448,7 @@ func TestHandler_CreateCertificate(t *testing.T) { h.CreateCertificate(r, req) resp := r.Result() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) require.NoError(t, err) // THEN @@ -562,7 +561,7 @@ func TestHandler_DeleteCertificate(t *testing.T) { h.DeleteCertificate(r, req) resp := r.Result() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) require.NoError(t, err) // THEN @@ -786,7 +785,7 @@ func TestHandler_GetDestinationCertificateByNameFromDestinationSvc(t *testing.T) h.GetDestinationCertificateByNameFromDestinationSvc(r, req) resp := r.Result() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) require.NoError(t, err) // THEN diff --git a/components/external-services-mock/internal/destinationcreator/mockdata.go b/components/external-services-mock/internal/destinationcreator/mockdata.go index 4257baf7f4..1cf3b50877 100644 --- a/components/external-services-mock/internal/destinationcreator/mockdata.go +++ b/components/external-services-mock/internal/destinationcreator/mockdata.go @@ -96,3 +96,33 @@ var FindAPIClientCertDestResponseTemplate = ` } ] }` + +var FindAPIOAuth2ClientCredsDestResponseTemplate = ` +{ + "owner": { + "SubaccountId": "%s", + "InstanceId": "%s" + }, + "destinationConfiguration": { + "Name": "%s", + "Type": "%s", + "URL": "%s", + "Authentication": "%s", + "ProxyType": "%s", + "clientId": "%s", + "clientSecret": "%s", + "tokenServiceURL": "%s" + }, + "authTokens": [ + { + "type": "bearer", + "value": "eyJhbGc", + "http_header": { + "key": "Authorization", + "value": "Bearer eyJhbGc" + }, + "expires_in": "43199", + "scope": "test.scope" + } + ] +}` diff --git a/components/external-services-mock/internal/destinationcreator/types.go b/components/external-services-mock/internal/destinationcreator/types.go index 6160fa3c63..6ceed7074d 100644 --- a/components/external-services-mock/internal/destinationcreator/types.go +++ b/components/external-services-mock/internal/destinationcreator/types.go @@ -41,7 +41,7 @@ type CertificateAPIConfig struct { type DestinationRequestBody interface { ToDestination() destinationcreator.Destination - Validate(destinationCreatorCfg *Config) error + Validate() error GetDestinationType() string GetDestinationUniqueIdentifier(subaccountID, instanceID string) string } @@ -81,6 +81,14 @@ type ClientCertificateAuthDestRequestBody struct { KeyStoreLocation string `json:"keyStoreLocation"` } +// OAuth2ClientCredsDestRequestBody contains the necessary fields for the destination request body with authentication type OAuth2ClientCredentials +type OAuth2ClientCredsDestRequestBody struct { + BaseDestinationRequestBody + TokenServiceURL string `json:"tokenServiceURL"` + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` +} + // CertificateRequestBody contains the necessary fields for the destination creator certificate request body type CertificateRequestBody struct { Name string `json:"name"` @@ -100,8 +108,8 @@ func (b *BaseDestinationRequestBody) GetDestinationUniqueIdentifier(subaccountID return fmt.Sprintf("name_%s_subacc_%s_instance_%s", b.Name, subaccountID, instanceID) } -// Validate validates that the AuthTypeNoAuth request body contains the required fields and they are valid -func (n *DesignTimeDestRequestBody) Validate(destinationCreatorCfg *Config) error { +// Validate validates that the AuthTypeNoAuth request body contains the required fields, and they are valid +func (n *DesignTimeDestRequestBody) Validate() error { return validation.ValidateStruct(n, validation.Field(&n.Name, validation.Required, validation.Length(1, destinationcreatorpkg.MaxDestinationNameLength), validation.Match(regexp.MustCompile(reqBodyNameRegex))), validation.Field(&n.URL, validation.Required), @@ -125,8 +133,8 @@ func (n *DesignTimeDestRequestBody) GetDestinationType() string { return destinationcreator.DesignTimeDestinationType } -// Validate validates that the AuthTypeBasic request body contains the required fields and they are valid -func (b *BasicDestRequestBody) Validate(destinationCreatorCfg *Config) error { +// Validate validates that the AuthTypeBasic request body contains the required fields, and they are valid +func (b *BasicDestRequestBody) Validate() error { return validation.ValidateStruct(b, validation.Field(&b.Name, validation.Required, validation.Length(1, destinationcreatorpkg.MaxDestinationNameLength), validation.Match(regexp.MustCompile(reqBodyNameRegex))), validation.Field(&b.URL, validation.Required), @@ -155,8 +163,8 @@ func (b *BasicDestRequestBody) GetDestinationType() string { return destinationcreator.BasicAuthDestinationType } -// Validate validates that the AuthTypeSAMLAssertion request body contains the required fields and they are valid -func (s *SAMLAssertionDestRequestBody) Validate(destinationCreatorCfg *Config) error { +// Validate validates that the AuthTypeSAMLAssertion request body contains the required fields, and they are valid +func (s *SAMLAssertionDestRequestBody) Validate() error { return validation.ValidateStruct(s, validation.Field(&s.Name, validation.Required, validation.Length(1, destinationcreatorpkg.MaxDestinationNameLength), validation.Match(regexp.MustCompile(reqBodyNameRegex))), validation.Field(&s.URL, validation.Required), @@ -186,8 +194,8 @@ func (s *SAMLAssertionDestRequestBody) GetDestinationType() string { return destinationcreator.SAMLAssertionDestinationType } -// Validate validates that the AuthTypeClientCertificate request body contains the required fields and they are valid -func (s *ClientCertificateAuthDestRequestBody) Validate(destinationCreatorCfg *Config) error { +// Validate validates that the AuthTypeClientCertificate request body contains the required fields, and they are valid +func (s *ClientCertificateAuthDestRequestBody) Validate() error { return validation.ValidateStruct(s, validation.Field(&s.Name, validation.Required, validation.Length(1, destinationcreatorpkg.MaxDestinationNameLength), validation.Match(regexp.MustCompile(reqBodyNameRegex))), validation.Field(&s.URL, validation.Required), @@ -215,7 +223,40 @@ func (s *ClientCertificateAuthDestRequestBody) GetDestinationType() string { return destinationcreator.ClientCertDestinationType } -// Validate validates that the SAML assertion certificate request body contains the required fields and they are valid +// Validate validates that the AuthTypeBasic request body contains the required fields, and they are valid +func (b *OAuth2ClientCredsDestRequestBody) Validate() error { + return validation.ValidateStruct(b, + validation.Field(&b.Name, validation.Required, validation.Length(1, destinationcreatorpkg.MaxDestinationNameLength), validation.Match(regexp.MustCompile(reqBodyNameRegex))), + validation.Field(&b.URL, validation.Required), + validation.Field(&b.Type, validation.In(destinationcreatorpkg.TypeHTTP, destinationcreatorpkg.TypeRFC, destinationcreatorpkg.TypeLDAP, destinationcreatorpkg.TypeMAIL)), + validation.Field(&b.ProxyType, validation.In(destinationcreatorpkg.ProxyTypeInternet, destinationcreatorpkg.ProxyTypeOnPremise, destinationcreatorpkg.ProxyTypePrivateLink)), + validation.Field(&b.AuthenticationType, validation.In(destinationcreatorpkg.AuthTypeOAuth2ClientCredentials)), + validation.Field(&b.TokenServiceURL, validation.Required), + validation.Field(&b.ClientID, validation.Required), + validation.Field(&b.ClientSecret, validation.Required), + ) +} + +func (b *OAuth2ClientCredsDestRequestBody) ToDestination() destinationcreator.Destination { + return &destinationcreator.OAuth2ClientCredentialsDestination{ + NoAuthenticationDestination: destinationcreator.NoAuthenticationDestination{ + Name: b.Name, + Type: b.Type, + URL: b.URL, + Authentication: b.AuthenticationType, + ProxyType: b.ProxyType, + }, + TokenServiceURL: b.TokenServiceURL, + ClientID: b.ClientID, + ClientSecret: b.ClientSecret, + } +} + +func (b *OAuth2ClientCredsDestRequestBody) GetDestinationType() string { + return destinationcreator.OAuth2ClientCredentialsType +} + +// Validate validates that the SAML assertion certificate request body contains the required fields, and they are valid func (c *CertificateRequestBody) Validate() error { return validation.ValidateStruct(c, validation.Field(&c.Name, validation.Required, validation.Length(1, destinationcreatorpkg.MaxDestinationNameLength), validation.Match(regexp.MustCompile(reqBodyNameRegex))), diff --git a/components/external-services-mock/internal/formationnotification/handler.go b/components/external-services-mock/internal/formationnotification/handler.go index 49708297d8..af68e9a87e 100644 --- a/components/external-services-mock/internal/formationnotification/handler.go +++ b/components/external-services-mock/internal/formationnotification/handler.go @@ -78,14 +78,14 @@ type FormationRequestBody struct { // FormationAssignmentResponseBody contains the synchronous formation assignment notification response body type FormationAssignmentResponseBody struct { Config *FormationAssignmentResponseConfig `json:"config,omitempty"` - Error string `json:"error,omitempty"` + Error string `json:"error,omitempty"` } // FormationAssignmentResponseBodyWithState contains the synchronous formation assignment notification response body with state in it type FormationAssignmentResponseBodyWithState struct { Config *FormationAssignmentResponseConfig `json:"config,omitempty"` - Error string `json:"error,omitempty"` - State FormationAssignmentState `json:"state"` + Error string `json:"error,omitempty"` + State FormationAssignmentState `json:"state"` } // FormationAssignmentResponseConfig contains the configuration of the formation response body @@ -275,11 +275,12 @@ func (h *Handler) RespondWithIncompleteAndDestinationDetails(writer http.Respons responseFunc := func(bodyBytes []byte) { if config := gjson.Get(string(bodyBytes), "receiverTenant.configuration").String(); config == "" { // NoAuthentication destination on 'provider' subaccount level + // OAuth2ClientCredentials destination on 'provider' subaccount level // BasicDestination on 'provider' instance level. Also, the basic destination has only a path for the URL and no correlationIds property // Client Certificate Authentication destination on 'consumer' subaccount(implicitly) level // SAML Assertion destination in the 'consumer' subaccount(implicitly) on provider instance level - responseWithPlaceholders := "{\"state\":\"CONFIG_PENDING\",\"configuration\":{\"destinations\":[{\"name\":\"e2e-design-time-destination-name\",\"type\":\"HTTP\",\"description\":\"e2e-design-time-destination description\",\"proxyType\":\"Internet\",\"authentication\":\"NoAuthentication\",\"url\":\"http://e2e-design-time-url-example.com\", \"subaccountId\":\"%s\"}],\"credentials\":{\"inboundCommunication\":{\"basicAuthentication\":{\"destinations\":[{\"name\":\"e2e-basic-destination-name\",\"description\":\"e2e-basic-destination description\",\"url\":\"/e2e-basic-url-path\",\"authentication\":\"BasicAuthentication\",\"subaccountId\":\"%s\", \"instanceId\":\"%s\", \"additionalProperties\":{\"e2e-basic-testKey\":\"e2e-basic-testVal\"}}]},\"samlAssertion\":{\"correlationIds\":[\"e2e-saml-correlation-ids\"],\"destinations\":[{\"name\":\"e2e-saml-assertion-destination-name\",\"description\":\"e2e saml assertion destination description\",\"url\":\"http://e2e-saml-url-example.com\",\"instanceId\":\"%s\",\"additionalProperties\":{\"e2e-samlTestKey\":\"e2e-samlTestVal\"}}]},\"clientCertificateAuthentication\":{\"correlationIds\":[\"e2e-client-cert-auth-correlation-ids\"],\"destinations\":[{\"name\":\"e2e-client-cert-auth-destination-name\",\"description\":\"e2e client cert auth destination description\",\"url\":\"http://e2e-client-cert-auth-url-example.com\",\"additionalProperties\":{\"e2e-clientCertAuthTestKey\":\"e2e-clientCertAuthTestVal\"}}]}}},\"additionalProperties\":[{\"propertyName\":\"example-property-name\",\"propertyValue\":\"example-property-value\",\"correlationIds\":[\"correlation-ids\"]}]}}" - response := fmt.Sprintf(responseWithPlaceholders, h.config.TestProviderSubaccountID, h.config.TestProviderSubaccountID, h.config.TestDestinationInstanceID, h.config.TestDestinationInstanceID) + responseWithPlaceholders := "{\"state\":\"CONFIG_PENDING\",\"configuration\":{\"destinations\":[{\"name\":\"e2e-design-time-destination-name\",\"type\":\"HTTP\",\"description\":\"e2e-design-time-destination description\",\"proxyType\":\"Internet\",\"authentication\":\"NoAuthentication\",\"url\":\"http://e2e-design-time-url-example.com\",\"subaccountId\":\"%s\"}],\"credentials\":{\"inboundCommunication\":{\"basicAuthentication\":{\"destinations\":[{\"name\":\"e2e-basic-destination-name\",\"description\":\"e2e-basic-destination description\",\"url\":\"/e2e-basic-url-path\",\"authentication\":\"BasicAuthentication\",\"subaccountId\":\"%s\",\"instanceId\":\"%s\",\"additionalProperties\":{\"e2e-basic-testKey\":\"e2e-basic-testVal\"}}]},\"samlAssertion\":{\"correlationIds\":[\"e2e-saml-correlation-ids\"],\"destinations\":[{\"name\":\"e2e-saml-assertion-destination-name\",\"description\":\"e2e saml assertion destination description\",\"url\":\"http://e2e-saml-url-example.com\",\"instanceId\":\"%s\",\"additionalProperties\":{\"e2e-samlTestKey\":\"e2e-samlTestVal\"}}]},\"clientCertificateAuthentication\":{\"correlationIds\":[\"e2e-client-cert-auth-correlation-ids\"],\"destinations\":[{\"name\":\"e2e-client-cert-auth-destination-name\",\"description\":\"e2e client cert auth destination description\",\"url\":\"http://e2e-client-cert-auth-url-example.com\",\"additionalProperties\":{\"e2e-clientCertAuthTestKey\":\"e2e-clientCertAuthTestVal\"}}]},\"oauth2ClientCredentials\":{\"correlationIds\":[\"e2e-oauth2-client-creds-correlation-ids\"],\"destinations\":[{\"name\":\"e2e-oauth2-client-creds-destination-name\",\"subaccountId\":\"%s\",\"description\":\"e2e oauth2 client creds destination description\",\"url\":\"http://e2e-oauth2-client-creds-url-example.com\",\"additionalProperties\":{\"e2e-clientCertAuthTestKey\":\"e2e-oauth2ClientCredsTestVal\"}}]}}},\"additionalProperties\":[{\"propertyName\":\"example-property-name\",\"propertyValue\":\"example-property-value\",\"correlationIds\":[\"correlation-ids\"]}]}}" + response := fmt.Sprintf(responseWithPlaceholders, h.config.TestProviderSubaccountID, h.config.TestProviderSubaccountID, h.config.TestDestinationInstanceID, h.config.TestDestinationInstanceID, h.config.TestProviderSubaccountID) httputils.RespondWithBody(ctx, writer, http.StatusOK, json.RawMessage(response)) return } @@ -583,7 +584,7 @@ func (h *Handler) AsyncDestinationPatch(writer http.ResponseWriter, r *http.Requ } r.Body = io.NopCloser(bytes.NewReader(bodyBytes)) - config := "{\"credentials\":{\"outboundCommunication\":{\"basicAuthentication\":{\"url\":\"https://e2e-basic-destination-url.com\",\"username\":\"e2e-basic-destination-username\",\"password\":\"e2e-basic-destination-password\"},\"samlAssertion\":{\"url\":\"http://e2e-saml-url-example.com\"},\"clientCertificateAuthentication\":{\"url\":\"http://e2e-client-cert-auth-url-example.com\"}}}}" + config := "{\"credentials\":{\"outboundCommunication\":{\"basicAuthentication\":{\"url\":\"https://e2e-basic-destination-url.com\",\"username\":\"e2e-basic-destination-username\",\"password\":\"e2e-basic-destination-password\"},\"samlAssertion\":{\"url\":\"http://e2e-saml-url-example.com\"},\"clientCertificateAuthentication\":{\"url\":\"http://e2e-client-cert-auth-url-example.com\"},\"oauth2ClientCredentials\":{\"url\":\"http://e2e-oauth2-client-creds-url-example.com\",\"tokenServiceUrl\":\"https://compass-external-services-mock.local.kyma.dev/secured/oauth/token\",\"clientId\":\"client_id\",\"clientSecret\":\"client_secret\"}}}}" h.asyncFAResponse(ctx, writer, r, Assign, config, responseFunc) } diff --git a/components/external-services-mock/pkg/destinationcreator/types.go b/components/external-services-mock/pkg/destinationcreator/types.go index c198df5389..bd2d46eb29 100644 --- a/components/external-services-mock/pkg/destinationcreator/types.go +++ b/components/external-services-mock/pkg/destinationcreator/types.go @@ -7,6 +7,7 @@ const ( BasicAuthDestinationType = "basic" SAMLAssertionDestinationType = "SAML assertion" ClientCertDestinationType = "client certificate authentication" + OAuth2ClientCredentialsType = "oauth2 client credentials" ) type Destination interface { @@ -58,6 +59,18 @@ func (c *ClientCertificateAuthenticationDestination) GetType() string { return ClientCertDestinationType } +// OAuth2ClientCredentialsDestination is a structure representing a oauth2 client credentials destination entity and its data from the remote destination service +type OAuth2ClientCredentialsDestination struct { + NoAuthenticationDestination + TokenServiceURL string `json:"tokenServiceURL"` + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` +} + +func (c *OAuth2ClientCredentialsDestination) GetType() string { + return OAuth2ClientCredentialsType +} + // DestinationSvcCertificateResponse contains the response data from destination service certificate request type DestinationSvcCertificateResponse struct { Name string `json:"Name"` @@ -109,3 +122,10 @@ type DestinationSvcClientCertDestResponse struct { DestinationConfiguration ClientCertificateAuthenticationDestination `json:"destinationConfiguration"` CertificateDetails []CertificateDetails `json:"certificates"` } + +// DestinationSvcOAuth2ClientCredsDestResponse contains the response data from destination service 'find API' request for destination of type 'OAuth2ClientCredentials' +type DestinationSvcOAuth2ClientCredsDestResponse struct { + Owner OwnerDetails `json:"owner"` + DestinationConfiguration OAuth2ClientCredentialsDestination `json:"destinationConfiguration"` + AuthTokens []AuthTokensDetails `json:"authTokens"` +} diff --git a/tests/director/tests/notifications/application_subscription_notifications_test.go b/tests/director/tests/notifications/application_subscription_notifications_test.go index 4330c35726..1df4c3cf4a 100644 --- a/tests/director/tests/notifications/application_subscription_notifications_test.go +++ b/tests/director/tests/notifications/application_subscription_notifications_test.go @@ -419,23 +419,31 @@ func TestFormationNotificationsWithApplicationSubscription(stdT *testing.T) { basicDestinationName := "e2e-basic-destination-name" basicDestinationURLPath := "/e2e-basic-url-path" basicDestinationURLFromCreds := "https://e2e-basic-destination-url.com" + basicDestinationUsername := "e2e-basic-destination-username" + basicDestinationPassword := "e2e-basic-destination-password" samlAssertionDestinationName := "e2e-saml-assertion-destination-name" samlAssertionDestinationURL := "http://e2e-saml-url-example.com" clientCertAuthDestinationName := "e2e-client-cert-auth-destination-name" clientCertAuthDestinationURL := "http://e2e-client-cert-auth-url-example.com" testDestinationInstanceID := conf.TestDestinationInstanceID + oauth2ClientCredsDestinationName := "e2e-oauth2-client-creds-destination-name" + oauth2ClientCredsDestinationURL := "http://e2e-oauth2-client-creds-url-example.com" + oauth2ClientCredsDestinationTokenURL := conf.ProviderDestinationConfig.TokenURL + conf.ProviderDestinationConfig.TokenPath + oauth2ClientCredsDestinationClientID := conf.ProviderDestinationConfig.ClientID + oauth2ClientCredsDestinationClientSecret := conf.ProviderClientSecret samlAssertionDestinationCertName := fmt.Sprintf("%s-%s", directordestinationcreator.AuthTypeSAMLAssertion, assignmentWithDestDetails.ID) clientCertAuthDestinationCertName := fmt.Sprintf("%s-%s", directordestinationcreator.AuthTypeClientCertificate, assignmentWithDestDetails.ID) clientCertAuthDestinationCertName = clientCertAuthDestinationCertName[:directordestinationcreator.MaxDestinationNameLength] // due to the longer client cert auth destination prefix we exceed the maximum length of the name, that's why we truncate it // NoAuthentication destination on 'provider' subaccount level + // OAuth2ClientCredentials destination on 'provider' subaccount level // BasicDestination on 'provider' instance level. Also, the basic destination has only a path for the URL and no correlationIds property // Client Certificate Authentication destination on 'consumer' subaccount(implicitly) level // SAML Assertion destination in the 'consumer' subaccount(implicitly) on provider instance level - destinationDetailsConfigWithPlaceholders := "{\"destinations\":[{\"name\":\"%s\",\"type\":\"HTTP\",\"description\":\"e2e-design-time-destination description\",\"proxyType\":\"Internet\",\"authentication\":\"NoAuthentication\",\"url\":\"%s\", \"subaccountId\":\"%s\"}],\"credentials\":{\"inboundCommunication\":{\"basicAuthentication\":{\"destinations\":[{\"name\":\"%s\",\"description\":\"e2e-basic-destination description\",\"url\":\"%s\",\"authentication\":\"BasicAuthentication\",\"subaccountId\":\"%s\",\"instanceId\":\"%s\", \"additionalProperties\":{\"e2e-basic-testKey\":\"e2e-basic-testVal\"}}]},\"samlAssertion\":{\"correlationIds\":[\"e2e-saml-correlation-ids\"],\"destinations\":[{\"name\":\"%s\",\"description\":\"e2e saml assertion destination description\",\"url\":\"%s\",\"instanceId\":\"%s\",\"additionalProperties\":{\"e2e-samlTestKey\":\"e2e-samlTestVal\"}}]},\"clientCertificateAuthentication\":{\"correlationIds\":[\"e2e-client-cert-auth-correlation-ids\"],\"destinations\":[{\"name\":\"%s\",\"description\":\"e2e client cert auth destination description\",\"url\":\"%s\",\"additionalProperties\":{\"e2e-clientCertAuthTestKey\":\"e2e-clientCertAuthTestVal\"}}]}}},\"additionalProperties\":[{\"propertyName\":\"example-property-name\",\"propertyValue\":\"example-property-value\",\"correlationIds\":[\"correlation-ids\"]}]}" - destinationDetailsConfig := fmt.Sprintf(destinationDetailsConfigWithPlaceholders, noAuthDestinationName, noAuthDestinationURL, conf.TestProviderSubaccountID, basicDestinationName, basicDestinationURLPath, conf.TestProviderSubaccountID, testDestinationInstanceID, samlAssertionDestinationName, samlAssertionDestinationURL, testDestinationInstanceID, clientCertAuthDestinationName, clientCertAuthDestinationURL) - destinationCredentialsConfig := fmt.Sprintf("{\"credentials\":{\"outboundCommunication\":{\"basicAuthentication\":{\"url\":\"%s\",\"username\":\"e2e-basic-destination-username\",\"password\":\"e2e-basic-destination-password\"},\"samlAssertion\":{\"url\":\"http://e2e-saml-url-example.com\"},\"clientCertificateAuthentication\":{\"url\":\"http://e2e-client-cert-auth-url-example.com\"}}}}", basicDestinationURLFromCreds) + destinationDetailsConfigWithPlaceholders := "{\"destinations\":[{\"name\":\"%s\",\"type\":\"HTTP\",\"description\":\"e2e-design-time-destination description\",\"proxyType\":\"Internet\",\"authentication\":\"NoAuthentication\",\"url\":\"%s\",\"subaccountId\":\"%s\"}],\"credentials\":{\"inboundCommunication\":{\"basicAuthentication\":{\"destinations\":[{\"name\":\"%s\",\"description\":\"e2e-basic-destination description\",\"url\":\"%s\",\"authentication\":\"BasicAuthentication\",\"subaccountId\":\"%s\",\"instanceId\":\"%s\",\"additionalProperties\":{\"e2e-basic-testKey\":\"e2e-basic-testVal\"}}]},\"samlAssertion\":{\"correlationIds\":[\"e2e-saml-correlation-ids\"],\"destinations\":[{\"name\":\"%s\",\"description\":\"e2e saml assertion destination description\",\"url\":\"%s\",\"instanceId\":\"%s\",\"additionalProperties\":{\"e2e-samlTestKey\":\"e2e-samlTestVal\"}}]},\"clientCertificateAuthentication\":{\"correlationIds\":[\"e2e-client-cert-auth-correlation-ids\"],\"destinations\":[{\"name\":\"%s\",\"description\":\"e2e client cert auth destination description\",\"url\":\"%s\",\"additionalProperties\":{\"e2e-clientCertAuthTestKey\":\"e2e-clientCertAuthTestVal\"}}]},\"oauth2ClientCredentials\":{\"correlationIds\":[\"e2e-oauth2-client-creds-correlation-ids\"],\"destinations\":[{\"name\":\"%s\",\"subaccountId\":\"%s\",\"description\":\"e2e oauth2 client creds destination description\",\"url\":\"%s\",\"additionalProperties\":{\"e2e-clientCertAuthTestKey\":\"e2e-oauth2ClientCredsTestVal\"}}]}}},\"additionalProperties\":[{\"propertyName\":\"example-property-name\",\"propertyValue\":\"example-property-value\",\"correlationIds\":[\"correlation-ids\"]}]}" + destinationDetailsConfig := fmt.Sprintf(destinationDetailsConfigWithPlaceholders, noAuthDestinationName, noAuthDestinationURL, conf.TestProviderSubaccountID, basicDestinationName, basicDestinationURLPath, conf.TestProviderSubaccountID, testDestinationInstanceID, samlAssertionDestinationName, samlAssertionDestinationURL, testDestinationInstanceID, clientCertAuthDestinationName, clientCertAuthDestinationURL, oauth2ClientCredsDestinationName, conf.TestProviderSubaccountID, oauth2ClientCredsDestinationURL) + destinationCredentialsConfig := fmt.Sprintf("{\"credentials\":{\"outboundCommunication\":{\"basicAuthentication\":{\"url\":\"%s\",\"username\":\"%s\",\"password\":\"%s\"},\"samlAssertion\":{\"url\":\"%s\"},\"clientCertificateAuthentication\":{\"url\":\"%s\"},\"oauth2ClientCredentials\":{\"url\":\"%s\",\"tokenServiceUrl\":\"%s\",\"clientId\":\"%s\",\"clientSecret\":\"%s\"}}}}", basicDestinationURLFromCreds, basicDestinationUsername, basicDestinationPassword, samlAssertionDestinationURL, clientCertAuthDestinationURL, oauth2ClientCredsDestinationURL, oauth2ClientCredsDestinationTokenURL, oauth2ClientCredsDestinationClientID, oauth2ClientCredsDestinationClientSecret) expectedAssignmentsBySourceID = map[string]map[string]fixtures.AssignmentState{ app1.ID: { app2.ID: fixtures.AssignmentState{State: "READY", Config: str.Ptr(destinationCredentialsConfig)}, @@ -521,6 +529,7 @@ func TestFormationNotificationsWithApplicationSubscription(stdT *testing.T) { consumerUserTokenHeader := token.GetUserToken(t, ctx, consumerTokenURL+conf.ProviderDestinationConfig.TokenPath, conf.ProviderDestinationConfig.ClientID, conf.ProviderDestinationConfig.ClientSecret, conf.BasicUsername, conf.BasicPassword, claims.SubscriptionClaimKey) assertSAMLAssertionDestination(t, destinationClient, conf.ProviderDestinationConfig.ServiceURL, samlAssertionDestinationName, samlAssertionDestinationCertName, samlAssertionDestinationURL, app2BaseURL, testDestinationInstanceID, conf.TestConsumerSubaccountID, destinationConsumerWithInstanceToken, consumerUserTokenHeader, map[string]bool{samlAssertionDestinationCertName + directordestinationcreator.JavaKeyStoreFileExtension: true}) assertClientCertAuthDestination(t, destinationClient, conf.ProviderDestinationConfig.ServiceURL, clientCertAuthDestinationName, clientCertAuthDestinationCertName, clientCertAuthDestinationURL, "", conf.TestConsumerSubaccountID, destinationConsumerToken, map[string]bool{clientCertAuthDestinationCertName + directordestinationcreator.JavaKeyStoreFileExtension: true}) + assertOAuth2ClientCredsDestination(t, destinationClient, conf.ProviderDestinationConfig.ServiceURL, oauth2ClientCredsDestinationName, oauth2ClientCredsDestinationURL, "", conf.TestProviderSubaccountID, destinationProviderToken, 1) t.Log("Destinations and destination certificates have been successfully created") cleanupNotificationsFromExternalSvcMock(t, certSecuredHTTPClient) diff --git a/tests/director/tests/notifications/fixtures_test.go b/tests/director/tests/notifications/fixtures_test.go index 9d12eee29c..323ea47ca0 100644 --- a/tests/director/tests/notifications/fixtures_test.go +++ b/tests/director/tests/notifications/fixtures_test.go @@ -50,6 +50,7 @@ const ( deletingAssignmentState = "DELETING" basicAuthType = "Basic" samlAuthType = "SAML2.0" + oauth2AuthType = "bearer" ) var ( @@ -393,6 +394,27 @@ func assertClientCertAuthDestination(t *testing.T, client *clients.DestinationCl } } +func assertOAuth2ClientCredsDestination(t *testing.T, client *clients.DestinationClient, serviceURL, oauth2ClientCredsDestinationName, oauth2ClientCredsDestinationURL, instanceID, ownerSubaccountID, authToken string, expectedNumberOfAuthTokens int) { + oauth2ClientCredsDestBytes := client.FindDestinationByName(t, serviceURL, oauth2ClientCredsDestinationName, authToken, "", http.StatusOK) + var oauth2ClientCredsDest esmdestinationcreator.DestinationSvcOAuth2ClientCredsDestResponse + err := json.Unmarshal(oauth2ClientCredsDestBytes, &oauth2ClientCredsDest) + require.NoError(t, err) + require.Equal(t, ownerSubaccountID, oauth2ClientCredsDest.Owner.SubaccountID) + require.Equal(t, instanceID, oauth2ClientCredsDest.Owner.InstanceID) + require.Equal(t, oauth2ClientCredsDestinationName, oauth2ClientCredsDest.DestinationConfiguration.Name) + require.Equal(t, directordestinationcreator.TypeHTTP, oauth2ClientCredsDest.DestinationConfiguration.Type) + require.Equal(t, oauth2ClientCredsDestinationURL, oauth2ClientCredsDest.DestinationConfiguration.URL) + require.Equal(t, directordestinationcreator.AuthTypeOAuth2ClientCredentials, oauth2ClientCredsDest.DestinationConfiguration.Authentication) + require.Equal(t, directordestinationcreator.ProxyTypeInternet, oauth2ClientCredsDest.DestinationConfiguration.ProxyType) + + for i := 0; i < expectedNumberOfAuthTokens; i++ { + require.NotEmpty(t, oauth2ClientCredsDest.AuthTokens) + require.NotEmpty(t, oauth2ClientCredsDest.AuthTokens[i].Type) + require.Equal(t, oauth2AuthType, oauth2ClientCredsDest.AuthTokens[i].Type) + require.NotEmpty(t, oauth2ClientCredsDest.AuthTokens[i].Value) + } +} + func assertFormationAssignmentsNotificationWithItemsStructure(t *testing.T, notification gjson.Result, op, formationID, expectedAppID, expectedLocalTenantID, expectedAppNamespace, expectedAppRegion, expectedTenant, expectedCustomerID string) { assertFormationAssignmentsNotificationWithConfigContainingItemsStructure(t, notification, op, formationID, expectedAppID, expectedLocalTenantID, expectedAppNamespace, expectedAppRegion, expectedTenant, expectedCustomerID, nil) } diff --git a/tests/go.mod b/tests/go.mod index f69d2576f8..7774218a0f 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -7,8 +7,8 @@ require ( github.com/google/uuid v1.3.1 github.com/kyma-incubator/compass/components/connectivity-adapter v0.0.0-20231120121524-9ee5c58d6fb0 github.com/kyma-incubator/compass/components/connector v0.0.0-20231120121524-9ee5c58d6fb0 - github.com/kyma-incubator/compass/components/director v0.0.0-20231120121524-9ee5c58d6fb0 - github.com/kyma-incubator/compass/components/external-services-mock v0.0.0-20231120121524-9ee5c58d6fb0 + github.com/kyma-incubator/compass/components/director v0.0.0-20231124153642-13a4704c40cf + github.com/kyma-incubator/compass/components/external-services-mock v0.0.0-20231124153642-13a4704c40cf github.com/kyma-incubator/compass/components/gateway v0.0.0-20231120121524-9ee5c58d6fb0 github.com/kyma-incubator/compass/components/operations-controller v0.0.0-20231120121524-9ee5c58d6fb0 github.com/machinebox/graphql v0.2.3-0.20181106130121-3a9253180225 @@ -19,7 +19,7 @@ require ( github.com/tidwall/sjson v1.2.5 github.com/vrischmann/envconfig v1.3.0 golang.org/x/net v0.17.0 - golang.org/x/oauth2 v0.8.0 + golang.org/x/oauth2 v0.11.0 k8s.io/api v0.26.9 k8s.io/apimachinery v0.26.9 k8s.io/client-go v0.26.9 diff --git a/tests/go.sum b/tests/go.sum index 18ddd8d8f0..3042027893 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -133,13 +133,13 @@ github.com/kyma-incubator/compass/components/connectivity-adapter v0.0.0-2023112 github.com/kyma-incubator/compass/components/connectivity-adapter v0.0.0-20231120121524-9ee5c58d6fb0/go.mod h1:IDpTETbLCAl+4WtMGDtJodpiu6cdTdZqH2pzYdPtnkc= github.com/kyma-incubator/compass/components/connector v0.0.0-20231120121524-9ee5c58d6fb0 h1:H2pFrah3Ai+kzJuBm2V7Ldd2huIn9020/AANQjHcysY= github.com/kyma-incubator/compass/components/connector v0.0.0-20231120121524-9ee5c58d6fb0/go.mod h1:Rgr8R6itj7GC9WJ3naTYLAgvSGjvhLbajWPf0Lri2lU= -github.com/kyma-incubator/compass/components/director v0.0.0-20231120121524-9ee5c58d6fb0 h1:C4/i82PXIpiYhqHcV0w8JLBEIar71GKGhYlat+s92Sk= -github.com/kyma-incubator/compass/components/director v0.0.0-20231120121524-9ee5c58d6fb0/go.mod h1:e38rK7ggp6z3wztnwzYKuBIzqKeZPez6TGXKALvbW+A= -github.com/kyma-incubator/compass/components/external-services-mock v0.0.0-20231120121524-9ee5c58d6fb0 h1:HLLCdOkTvSgtq/Xl98S6jPuYwXC6m+iHXYiEI/zutG8= -github.com/kyma-incubator/compass/components/external-services-mock v0.0.0-20231120121524-9ee5c58d6fb0/go.mod h1:jU1YZIfbuwmBS/jOQLxSwRnPYdFMr+klxWnq9cM3v9g= +github.com/kyma-incubator/compass/components/director v0.0.0-20231124153642-13a4704c40cf h1:P9h2uNSO7qr2F2/04zJLGmLKimNnaSiLTgDOJiNcBIE= +github.com/kyma-incubator/compass/components/director v0.0.0-20231124153642-13a4704c40cf/go.mod h1:yRzGhOVE0t/ORiHPLLeVHN0P24cJ91NPVqnTDNFY6Ok= +github.com/kyma-incubator/compass/components/external-services-mock v0.0.0-20231124153642-13a4704c40cf h1:QY/IetHuyeDmJPdnN3K9meUNbfFf7TiRltC3hFt8udE= +github.com/kyma-incubator/compass/components/external-services-mock v0.0.0-20231124153642-13a4704c40cf/go.mod h1:SU5pM4AqESmiJD4yJW1dsjCVNpuTgTgCVSx85Q7SGIo= github.com/kyma-incubator/compass/components/gateway v0.0.0-20231120121524-9ee5c58d6fb0 h1:aWWboEJuEqEEX3wNd9vBAOZ77pQN+6zRt0c6k+o54qk= github.com/kyma-incubator/compass/components/gateway v0.0.0-20231120121524-9ee5c58d6fb0/go.mod h1:J2eSb/ghFiGt7OjWpw/POUYuuJOcvPSoGNwwKYQ/O0k= -github.com/kyma-incubator/compass/components/hydrator v0.0.0-20231117123701-9a77e393bcd8 h1:7Bcw//aKCPX42lHIJu0i7YPc7Uv6Z2yWQnZU8tey6yg= +github.com/kyma-incubator/compass/components/hydrator v0.0.0-20231120121524-9ee5c58d6fb0 h1:C8svQ+yOnY7zwfhEh5jsGZ/+wuEeomNmt/CAYIHR1fM= github.com/kyma-incubator/compass/components/operations-controller v0.0.0-20231120121524-9ee5c58d6fb0 h1:ZZyTgLUtGoIUSgURABHrbtcl72PEUAlvEC9oVPgTZfg= github.com/kyma-incubator/compass/components/operations-controller v0.0.0-20231120121524-9ee5c58d6fb0/go.mod h1:w7bQpj/DQCcTqyTBZ8hp9tM9hQ5Ppk7r/4y35ZdKpCA= github.com/kyma-incubator/compass/components/system-broker v0.0.0-20231120121524-9ee5c58d6fb0 h1:j63ak3yOKempoRVAmQZHKV3z5VZLdvyt+Nq4fL91acs= @@ -287,8 +287,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/tests/pkg/fixtures/formation_constraint_fixtures.go b/tests/pkg/fixtures/formation_constraint_fixtures.go index 2f4f1f198d..dc2e462154 100644 --- a/tests/pkg/fixtures/formation_constraint_fixtures.go +++ b/tests/pkg/fixtures/formation_constraint_fixtures.go @@ -5,7 +5,7 @@ import ( "github.com/kyma-incubator/compass/components/director/pkg/graphql" ) -func FixFormationConstraintInputContainsScenarioGroups(resourceSubtype string, targetOperation graphql.TargetOperation, inputTemplate string) graphql.FormationConstraintInput{ +func FixFormationConstraintInputContainsScenarioGroups(resourceSubtype string, targetOperation graphql.TargetOperation, inputTemplate string) graphql.FormationConstraintInput { return graphql.FormationConstraintInput{ Name: "TestContainsScenarioGroupsAssign", ConstraintType: graphql.ConstraintTypePre, @@ -16,4 +16,4 @@ func FixFormationConstraintInputContainsScenarioGroups(resourceSubtype string, t InputTemplate: inputTemplate, ConstraintScope: graphql.ConstraintScopeFormationType, } -} \ No newline at end of file +} diff --git a/tests/pkg/json/utils_json.go b/tests/pkg/json/utils_json.go index 0b860c4ebe..aaa322f49e 100644 --- a/tests/pkg/json/utils_json.go +++ b/tests/pkg/json/utils_json.go @@ -2,11 +2,12 @@ package json import ( "encoding/json" - "github.com/kyma-incubator/compass/components/director/pkg/str" - "github.com/stretchr/testify/assert" "strconv" "testing" + "github.com/kyma-incubator/compass/components/director/pkg/str" + "github.com/stretchr/testify/assert" + "github.com/kyma-incubator/compass/components/director/pkg/graphql" "github.com/stretchr/testify/require" ) @@ -60,4 +61,4 @@ func isJSONStringEmpty(json string) bool { return false } return true -} \ No newline at end of file +} diff --git a/tests/pkg/notifications/asserters/formation_assignments_async.go b/tests/pkg/notifications/asserters/formation_assignments_async.go index 5c722902f4..bb64ca567a 100644 --- a/tests/pkg/notifications/asserters/formation_assignments_async.go +++ b/tests/pkg/notifications/asserters/formation_assignments_async.go @@ -2,10 +2,11 @@ package asserters import ( "context" - "github.com/kyma-incubator/compass/tests/pkg/json" "testing" "time" + "github.com/kyma-incubator/compass/tests/pkg/json" + "github.com/kyma-incubator/compass/components/director/pkg/str" "github.com/kyma-incubator/compass/tests/pkg/fixtures" context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" @@ -16,7 +17,7 @@ import ( type FormationAssignmentsAsyncAsserter struct { FormationAssignmentsAsserter timeout time.Duration - tick time.Duration + tick time.Duration } func NewFormationAssignmentAsyncAsserter(expectations map[string]map[string]fixtures.AssignmentState, expectedAssignmentsCount int, certSecuredGraphQLClient *graphql.Client, tenantID string) *FormationAssignmentsAsyncAsserter { @@ -27,8 +28,8 @@ func NewFormationAssignmentAsyncAsserter(expectations map[string]map[string]fixt certSecuredGraphQLClient: certSecuredGraphQLClient, tenantID: tenantID, }, - timeout: time.Second*8, - tick: time.Millisecond*50, + timeout: time.Second * 8, + tick: time.Millisecond * 50, } return &f } @@ -79,4 +80,4 @@ func (a *FormationAssignmentsAsyncAsserter) assertFormationAssignmentsAsynchrono t.Logf("Successfully asserted formation asssignments asynchronously") return true }, a.timeout, a.tick) -} \ No newline at end of file +} diff --git a/tests/pkg/notifications/asserters/notifications.go b/tests/pkg/notifications/asserters/notifications.go index 0ca7aa7c0b..bd237b7f98 100644 --- a/tests/pkg/notifications/asserters/notifications.go +++ b/tests/pkg/notifications/asserters/notifications.go @@ -3,13 +3,14 @@ package asserters import ( "context" "fmt" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/tidwall/sjson" "io" "net/http" "testing" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/tidwall/sjson" + context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" "github.com/stretchr/testify/require" "github.com/tidwall/gjson"