From 6f91b6ac28e733561200c5faf12029cafed39d3f Mon Sep 17 00:00:00 2001 From: ychacon Date: Fri, 12 May 2023 11:14:17 +0200 Subject: [PATCH] Moving add client in keycloak from security to invoker api Issue-ID: NONRTRIC-861 Signed-off-by: ychacon Change-Id: I0d9ceedc2f23f2d5fa5233f6075f3ecd56d3fc08 --- .../invokermanagement/invokermanagement.go | 20 +++++- .../invokermanagement/invokermanagement_test.go | 28 +++++--- .../internal/invokermanagementapi/typeupdate.go | 2 - .../invokermanagementapi/typeupdate_test.go | 2 - capifcore/internal/keycloak/keycloak.go | 76 +++++++++++++++++----- .../internal/keycloak/mocks/AccessManagement.go | 26 ++++++++ .../providermanagement/mocks/ServiceRegister.go | 16 ++++- .../providermanagement/providermanagement.go | 10 +++ .../internal/providermanagementapi/typeaccess.go | 11 +++- .../internal/publishservice/publishservice.go | 4 ++ .../internal/publishservice/publishservice_test.go | 12 +++- .../internal/publishserviceapi/typevalidation.go | 1 - capifcore/internal/restclient/HTTPClient.go | 35 +++++++--- capifcore/internal/restclient/HTTPClient_test.go | 33 +++++++++- capifcore/internal/restclient/mocks/HTTPClient.go | 33 +++++++++- capifcore/internal/securityapi/typeupdate.go | 7 +- capifcore/internal/securityservice/security.go | 5 -- .../internal/securityservice/security_test.go | 13 +--- capifcore/main.go | 2 +- 19 files changed, 268 insertions(+), 68 deletions(-) diff --git a/capifcore/internal/invokermanagement/invokermanagement.go b/capifcore/internal/invokermanagement/invokermanagement.go index 43bdc02..ee7030a 100644 --- a/capifcore/internal/invokermanagement/invokermanagement.go +++ b/capifcore/internal/invokermanagement/invokermanagement.go @@ -27,6 +27,7 @@ import ( "sync" "oransc.org/nonrtric/capifcore/internal/eventsapi" + "oransc.org/nonrtric/capifcore/internal/keycloak" "oransc.org/nonrtric/capifcore/internal/common29122" invokerapi "oransc.org/nonrtric/capifcore/internal/invokermanagementapi" @@ -53,16 +54,18 @@ type InvokerManager struct { onboardedInvokers map[string]invokerapi.APIInvokerEnrolmentDetails publishRegister publishservice.PublishRegister nextId int64 + keycloak keycloak.AccessManagement eventChannel chan<- eventsapi.EventNotification lock sync.Mutex } // Creates a manager that implements both the InvokerRegister and the invokermanagementapi.ServerInterface interfaces. -func NewInvokerManager(publishRegister publishservice.PublishRegister, eventChannel chan<- eventsapi.EventNotification) *InvokerManager { +func NewInvokerManager(publishRegister publishservice.PublishRegister, km keycloak.AccessManagement, eventChannel chan<- eventsapi.EventNotification) *InvokerManager { return &InvokerManager{ onboardedInvokers: make(map[string]invokerapi.APIInvokerEnrolmentDetails), publishRegister: publishRegister, nextId: 1000, + keycloak: km, eventChannel: eventChannel, } } @@ -147,9 +150,24 @@ func (im *InvokerManager) prepareNewInvoker(newInvoker *invokerapi.APIInvokerEnr newInvoker.PrepareNewInvoker() + im.addClientInKeycloak(newInvoker) + im.onboardedInvokers[*newInvoker.ApiInvokerId] = *newInvoker } +func (im *InvokerManager) addClientInKeycloak(newInvoker *invokerapi.APIInvokerEnrolmentDetails) error { + if err := im.keycloak.AddClient(*newInvoker.ApiInvokerId, "invokerrealm"); err != nil { + return err + } + + if body, err := im.keycloak.GetClientRepresentation(*newInvoker.ApiInvokerId, "invokerrealm"); err != nil { + return err + } else { + newInvoker.OnboardingInformation.OnboardingSecret = body.Secret + } + return nil +} + // Deletes an individual API Invoker. func (im *InvokerManager) DeleteOnboardedInvokersOnboardingId(ctx echo.Context, onboardingId string) error { if _, ok := im.onboardedInvokers[onboardingId]; ok { diff --git a/capifcore/internal/invokermanagement/invokermanagement_test.go b/capifcore/internal/invokermanagement/invokermanagement_test.go index 4365eb3..7b92972 100644 --- a/capifcore/internal/invokermanagement/invokermanagement_test.go +++ b/capifcore/internal/invokermanagement/invokermanagement_test.go @@ -29,12 +29,14 @@ import ( "oransc.org/nonrtric/capifcore/internal/eventsapi" "oransc.org/nonrtric/capifcore/internal/invokermanagementapi" + "oransc.org/nonrtric/capifcore/internal/keycloak" "github.com/labstack/echo/v4" "oransc.org/nonrtric/capifcore/internal/common29122" "oransc.org/nonrtric/capifcore/internal/publishserviceapi" + keycloackmocks "oransc.org/nonrtric/capifcore/internal/keycloak/mocks" "oransc.org/nonrtric/capifcore/internal/publishservice" publishmocks "oransc.org/nonrtric/capifcore/internal/publishservice/mocks" @@ -42,6 +44,7 @@ import ( "github.com/deepmap/oapi-codegen/pkg/testutil" echomiddleware "github.com/labstack/echo/v4/middleware" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) func TestOnboardInvoker(t *testing.T) { @@ -55,11 +58,20 @@ func TestOnboardInvoker(t *testing.T) { AefProfiles: &aefProfiles, }, } + + invokerInfo := "invoker a" + wantedInvokerSecret := "onboarding_secret_" + strings.Replace(invokerInfo, " ", "_", 1) + var client keycloak.Client + client.Secret = &wantedInvokerSecret publishRegisterMock := publishmocks.PublishRegister{} publishRegisterMock.On("GetAllPublishedServices").Return(publishedServices) - invokerUnderTest, eventChannel, requestHandler := getEcho(&publishRegisterMock) - invokerInfo := "invoker a" + accessMgmMock := keycloackmocks.AccessManagement{} + accessMgmMock.On("AddClient", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(nil) + accessMgmMock.On("GetClientRepresentation", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(&client, nil) + + invokerUnderTest, eventChannel, requestHandler := getEcho(&publishRegisterMock, &accessMgmMock) + newInvoker := getInvoker(invokerInfo) // Onboard a valid invoker @@ -73,7 +85,7 @@ func TestOnboardInvoker(t *testing.T) { assert.Equal(t, wantedInvokerId, *resultInvoker.ApiInvokerId) assert.Equal(t, newInvoker.NotificationDestination, resultInvoker.NotificationDestination) assert.Equal(t, newInvoker.OnboardingInformation.ApiInvokerPublicKey, resultInvoker.OnboardingInformation.ApiInvokerPublicKey) - wantedInvokerSecret := "onboarding_secret_" + strings.Replace(invokerInfo, " ", "_", 1) + assert.Equal(t, wantedInvokerSecret, *resultInvoker.OnboardingInformation.OnboardingSecret) assert.Equal(t, "http://example.com/onboardedInvokers/"+*resultInvoker.ApiInvokerId, result.Recorder.Header().Get(echo.HeaderLocation)) assert.True(t, invokerUnderTest.IsInvokerRegistered(wantedInvokerId)) @@ -128,7 +140,7 @@ func TestOnboardInvoker(t *testing.T) { } func TestDeleteInvoker(t *testing.T) { - invokerUnderTest, eventChannel, requestHandler := getEcho(nil) + invokerUnderTest, eventChannel, requestHandler := getEcho(nil, nil) invokerId := "invokerId" newInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{ @@ -157,7 +169,7 @@ func TestDeleteInvoker(t *testing.T) { func TestUpdateInvoker(t *testing.T) { publishRegisterMock := publishmocks.PublishRegister{} publishRegisterMock.On("GetAllPublishedServices").Return([]publishserviceapi.ServiceAPIDescription{}) - serviceUnderTest, _, requestHandler := getEcho(&publishRegisterMock) + serviceUnderTest, _, requestHandler := getEcho(&publishRegisterMock, nil) invokerId := "invokerId" invoker := invokermanagementapi.APIInvokerEnrolmentDetails{ @@ -261,7 +273,7 @@ func TestGetInvokerApiList(t *testing.T) { }) publishRegisterMock := publishmocks.PublishRegister{} publishRegisterMock.On("GetAllPublishedServices").Return(apiList) - invokerUnderTest, _, _ := getEcho(&publishRegisterMock) + invokerUnderTest, _, _ := getEcho(&publishRegisterMock, nil) invokerInfo := "invoker a" newInvoker := getInvoker(invokerInfo) @@ -280,7 +292,7 @@ func TestGetInvokerApiList(t *testing.T) { assert.Equal(t, apiId, *(*wantedApiList)[0].ApiId) } -func getEcho(publishRegister publishservice.PublishRegister) (*InvokerManager, chan eventsapi.EventNotification, *echo.Echo) { +func getEcho(publishRegister publishservice.PublishRegister, keycloakMgm keycloak.AccessManagement) (*InvokerManager, chan eventsapi.EventNotification, *echo.Echo) { swagger, err := invokermanagementapi.GetSwagger() if err != nil { fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err) @@ -290,7 +302,7 @@ func getEcho(publishRegister publishservice.PublishRegister) (*InvokerManager, c swagger.Servers = nil eventChannel := make(chan eventsapi.EventNotification) - im := NewInvokerManager(publishRegister, eventChannel) + im := NewInvokerManager(publishRegister, keycloakMgm, eventChannel) e := echo.New() e.Use(echomiddleware.Logger()) diff --git a/capifcore/internal/invokermanagementapi/typeupdate.go b/capifcore/internal/invokermanagementapi/typeupdate.go index 1de6280..abd399e 100644 --- a/capifcore/internal/invokermanagementapi/typeupdate.go +++ b/capifcore/internal/invokermanagementapi/typeupdate.go @@ -30,8 +30,6 @@ var uuidFunc = getUUID func (ied *APIInvokerEnrolmentDetails) PrepareNewInvoker() { ied.createId() - ied.getOnboardingSecret() - } func (ied *APIInvokerEnrolmentDetails) createId() { diff --git a/capifcore/internal/invokermanagementapi/typeupdate_test.go b/capifcore/internal/invokermanagementapi/typeupdate_test.go index 5128d5f..f04a13d 100644 --- a/capifcore/internal/invokermanagementapi/typeupdate_test.go +++ b/capifcore/internal/invokermanagementapi/typeupdate_test.go @@ -34,11 +34,9 @@ func TestPrepareNewInvoker(t *testing.T) { invokerUnderTest.PrepareNewInvoker() assert.Equal(t, "api_invoker_id_1", *invokerUnderTest.ApiInvokerId) - assert.Equal(t, "onboarding_secret_api_invoker_id_1", *invokerUnderTest.OnboardingInformation.OnboardingSecret) invokerInfo := "invoker info" invokerUnderTest.ApiInvokerInformation = &invokerInfo invokerUnderTest.PrepareNewInvoker() assert.Equal(t, "api_invoker_id_invoker_info", *invokerUnderTest.ApiInvokerId) - assert.Equal(t, "onboarding_secret_invoker_info", *invokerUnderTest.OnboardingInformation.OnboardingSecret) } diff --git a/capifcore/internal/keycloak/keycloak.go b/capifcore/internal/keycloak/keycloak.go index 3646516..200f8d4 100644 --- a/capifcore/internal/keycloak/keycloak.go +++ b/capifcore/internal/keycloak/keycloak.go @@ -39,6 +39,8 @@ type AccessManagement interface { GetToken(realm string, data map[string][]string) (Jwttoken, error) // Add new client in keycloak AddClient(clientId string, realm string) error + // Returns information about client including secret + GetClientRepresentation(clientId string, realm string) (*Client, error) } type AdminUser struct { @@ -83,7 +85,6 @@ type Jwttoken struct { func (km *KeycloakManager) GetToken(realm string, data map[string][]string) (Jwttoken, error) { var jwt Jwttoken getTokenUrl := km.keycloakServerUrl + "/realms/" + realm + "/protocol/openid-connect/token" - resp, err := http.PostForm(getTokenUrl, data) if err != nil { @@ -96,6 +97,7 @@ func (km *KeycloakManager) GetToken(realm string, data map[string][]string) (Jwt if err != nil { return jwt, err } + if resp.StatusCode != http.StatusOK { return jwt, errors.New(string(body)) } @@ -105,16 +107,20 @@ func (km *KeycloakManager) GetToken(realm string, data map[string][]string) (Jwt } type Client struct { - AdminURL string `json:"adminUrl,omitempty"` - BearerOnly bool `json:"bearerOnly,omitempty"` - ClientID string `json:"clientId,omitempty"` - Enabled bool `json:"enabled,omitempty"` - PublicClient bool `json:"publicClient,omitempty"` - RootURL string `json:"rootUrl,omitempty"` - ServiceAccountsEnabled bool `json:"serviceAccountsEnabled,omitempty"` + AdminURL string `json:"adminUrl,omitempty"` + AuthorizationServicesEnabled *bool `json:"authorizationServicesEnabled,omitempty"` + BearerOnly bool `json:"bearerOnly,omitempty"` + ClientID string `json:"clientId,omitempty"` + Enabled bool `json:"enabled,omitempty"` + ID *string `json:"id,omitempty"` + PublicClient bool `json:"publicClient,omitempty"` + RootURL string `json:"rootUrl,omitempty"` + Secret *string `json:"secret,omitempty"` + ServiceAccountsEnabled bool `json:"serviceAccountsEnabled,omitempty"` } func (km *KeycloakManager) AddClient(clientId string, realm string) error { + data := url.Values{"grant_type": {"password"}, "username": {km.admin.User}, "password": {km.admin.Password}, "client_id": {"admin-cli"}} token, err := km.GetToken("master", data) if err != nil { @@ -123,22 +129,56 @@ func (km *KeycloakManager) AddClient(clientId string, realm string) error { } createClientUrl := km.keycloakServerUrl + "/admin/realms/" + realm + "/clients" - newClient := Client{ - ClientID: clientId, - Enabled: true, - ServiceAccountsEnabled: true, - BearerOnly: false, - PublicClient: false, + newClient := map[string]interface{}{"clientId": clientId, "serviceAccountsEnabled": true} + + body, err := json.Marshal(newClient) + if err != nil { + return err } - body, _ := json.Marshal(newClient) var headers = map[string]string{"Content-Type": "application/json", "Authorization": "Bearer " + token.AccessToken} - if error := restclient.Post(createClientUrl, body, headers, km.client); error != nil { - log.Errorf("error with http request: %+v\n", err) + if err := restclient.Post(createClientUrl, body, headers, km.client); err != nil { + log.Errorf("addClient - error with http request: %+v\n", err) return err } - log.Info("Created new client") + log.Debug("Created new client") return nil } + +func (km *KeycloakManager) GetClientRepresentation(clientId string, realm string) (*Client, error) { + + data := url.Values{"grant_type": {"password"}, "username": {km.admin.User}, "password": {km.admin.Password}, "client_id": {"admin-cli"}} + token, err := km.GetToken("master", data) + if err != nil { + log.Errorf("error wrong credentials or url %v\n", err) + return nil, err + } + + createClientUrl, _ := url.Parse(km.keycloakServerUrl + "/admin/realms/" + realm + "/clients") + q := createClientUrl.Query() + q.Add("clientId", clientId) + createClientUrl.RawQuery = q.Encode() + + var headers = map[string]string{"Content-Type": "application/json", "Authorization": "Bearer " + token.AccessToken} + + if resp, err := restclient.Get(createClientUrl.String(), headers, km.client); err == nil { + var client []Client + + if err = json.Unmarshal(resp, &client); err != nil { + log.Errorf("error unmarshal keycloak client object: %+v\n", err) + return nil, err + } + + if len(client) > 0 { + return &client[0], nil + } + return nil, nil + + } else { + log.Errorf("error with http request: %+v\n", err) + return nil, err + } + +} diff --git a/capifcore/internal/keycloak/mocks/AccessManagement.go b/capifcore/internal/keycloak/mocks/AccessManagement.go index 59b914a..35086d4 100644 --- a/capifcore/internal/keycloak/mocks/AccessManagement.go +++ b/capifcore/internal/keycloak/mocks/AccessManagement.go @@ -26,6 +26,32 @@ func (_m *AccessManagement) AddClient(clientId string, realm string) error { return r0 } +// GetClientRepresentation provides a mock function with given fields: clientId, realm +func (_m *AccessManagement) GetClientRepresentation(clientId string, realm string) (*keycloak.Client, error) { + ret := _m.Called(clientId, realm) + + var r0 *keycloak.Client + var r1 error + if rf, ok := ret.Get(0).(func(string, string) (*keycloak.Client, error)); ok { + return rf(clientId, realm) + } + if rf, ok := ret.Get(0).(func(string, string) *keycloak.Client); ok { + r0 = rf(clientId, realm) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*keycloak.Client) + } + } + + if rf, ok := ret.Get(1).(func(string, string) error); ok { + r1 = rf(clientId, realm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetToken provides a mock function with given fields: realm, data func (_m *AccessManagement) GetToken(realm string, data map[string][]string) (keycloak.Jwttoken, error) { ret := _m.Called(realm, data) diff --git a/capifcore/internal/providermanagement/mocks/ServiceRegister.go b/capifcore/internal/providermanagement/mocks/ServiceRegister.go index c795ed1..a1f9c41 100644 --- a/capifcore/internal/providermanagement/mocks/ServiceRegister.go +++ b/capifcore/internal/providermanagement/mocks/ServiceRegister.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -39,6 +39,20 @@ func (_m *ServiceRegister) IsFunctionRegistered(functionId string) bool { return r0 } +// IsPublishingFunctionRegistered provides a mock function with given fields: apiProvFuncId +func (_m *ServiceRegister) IsPublishingFunctionRegistered(apiProvFuncId string) bool { + ret := _m.Called(apiProvFuncId) + + var r0 bool + if rf, ok := ret.Get(0).(func(string) bool); ok { + r0 = rf(apiProvFuncId) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + type mockConstructorTestingTNewServiceRegister interface { mock.TestingT Cleanup(func()) diff --git a/capifcore/internal/providermanagement/providermanagement.go b/capifcore/internal/providermanagement/providermanagement.go index d5e7a63..2d6bd61 100644 --- a/capifcore/internal/providermanagement/providermanagement.go +++ b/capifcore/internal/providermanagement/providermanagement.go @@ -38,6 +38,7 @@ import ( type ServiceRegister interface { IsFunctionRegistered(functionId string) bool GetAefsForPublisher(apfId string) []string + IsPublishingFunctionRegistered(apiProvFuncId string) bool } type ProviderManager struct { @@ -69,6 +70,15 @@ func (pm *ProviderManager) GetAefsForPublisher(apfId string) []string { return nil } +func (pm *ProviderManager) IsPublishingFunctionRegistered(apiProvFuncId string) bool { + for _, provider := range pm.registeredProviders { + if provider.IsPublishingFunctionRegistered(apiProvFuncId) { + return true + } + } + return false +} + func (pm *ProviderManager) PostRegistrations(ctx echo.Context) error { var newProvider provapi.APIProviderEnrolmentDetails errMsg := "Unable to register provider due to %s" diff --git a/capifcore/internal/providermanagementapi/typeaccess.go b/capifcore/internal/providermanagementapi/typeaccess.go index 5257701..b1894d4 100644 --- a/capifcore/internal/providermanagementapi/typeaccess.go +++ b/capifcore/internal/providermanagementapi/typeaccess.go @@ -48,7 +48,16 @@ func (ed APIProviderEnrolmentDetails) IsFunctionRegistered(functionId string) bo return false } -func (fd APIProviderFunctionDetails) isProvidingFunction() bool { +func (ed APIProviderEnrolmentDetails) IsPublishingFunctionRegistered(functionId string) bool { + for _, registeredFunc := range *ed.ApiProvFuncs { + if *registeredFunc.ApiProvFuncId == functionId && registeredFunc.isPublishingFunction() { + return true + } + } + return false +} + +func (fd APIProviderFunctionDetails) isPublishingFunction() bool { return fd.ApiProvFuncRole == ApiProviderFuncRoleAPF } diff --git a/capifcore/internal/publishservice/publishservice.go b/capifcore/internal/publishservice/publishservice.go index 7960f12..2fe5b2e 100644 --- a/capifcore/internal/publishservice/publishservice.go +++ b/capifcore/internal/publishservice/publishservice.go @@ -118,6 +118,10 @@ func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) e return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errorMsg, "invalid format for service "+apfId)) } + if !ps.serviceRegister.IsPublishingFunctionRegistered(apfId) { + return sendCoreError(ctx, http.StatusForbidden, fmt.Sprintf(errorMsg, "api is only available for publishers "+apfId)) + } + if err := ps.isServicePublished(newServiceAPIDescription); err != nil { return sendCoreError(ctx, http.StatusForbidden, fmt.Sprintf(errorMsg, err)) } diff --git a/capifcore/internal/publishservice/publishservice_test.go b/capifcore/internal/publishservice/publishservice_test.go index b69b956..8de1e9f 100644 --- a/capifcore/internal/publishservice/publishservice_test.go +++ b/capifcore/internal/publishservice/publishservice_test.go @@ -52,6 +52,7 @@ func TestPublishUnpublishService(t *testing.T) { aefId := "aefId" serviceRegisterMock := serviceMocks.ServiceRegister{} serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId"}) + serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true) helmManagerMock := helmMocks.HelmManager{} helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) serviceUnderTest, eventChannel, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock) @@ -71,7 +72,6 @@ func TestPublishUnpublishService(t *testing.T) { // Publish a service for provider result = testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler) - assert.Equal(t, http.StatusCreated, result.Code()) var resultService publishapi.ServiceAPIDescription err := result.UnmarshalBodyToObject(&resultService) @@ -136,6 +136,7 @@ func TestPostUnpublishedServiceWithUnregisteredFunction(t *testing.T) { aefId := "aefId" serviceRegisterMock := serviceMocks.ServiceRegister{} serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{"otherAefId"}) + serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true) _, _, requestHandler := getEcho(&serviceRegisterMock, nil) newServiceDescription := getServiceAPIDescription(aefId, "apiName", "description") @@ -157,6 +158,7 @@ func TestGetServices(t *testing.T) { aefId := "aefId" serviceRegisterMock := serviceMocks.ServiceRegister{} serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId}) + serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true) _, _, requestHandler := getEcho(&serviceRegisterMock, nil) // Check no services published for provider @@ -212,6 +214,7 @@ func TestUpdateDescription(t *testing.T) { serviceRegisterMock := serviceMocks.ServiceRegister{} serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"}) + serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true) helmManagerMock := helmMocks.HelmManager{} helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) serviceUnderTest, eventChannel, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock) @@ -285,6 +288,7 @@ func TestUpdateValidServiceWithDeletedFunction(t *testing.T) { description := "description" serviceRegisterMock := serviceMocks.ServiceRegister{} + serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true) serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"}) helmManagerMock := helmMocks.HelmManager{} helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) @@ -367,7 +371,11 @@ func TestUpdateValidServiceWithDeletedFunction(t *testing.T) { } func TestPublishInvalidService(t *testing.T) { - _, _, requestHandler := getEcho(nil, nil) + apfId := "apfId" + serviceRegisterMock := serviceMocks.ServiceRegister{} + serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true) + + _, _, requestHandler := getEcho(&serviceRegisterMock, nil) newServiceDescription := getServiceAPIDescription("aefId", " ", "description") // Publish a service diff --git a/capifcore/internal/publishserviceapi/typevalidation.go b/capifcore/internal/publishserviceapi/typevalidation.go index 287a07d..5e8a8fc 100644 --- a/capifcore/internal/publishserviceapi/typevalidation.go +++ b/capifcore/internal/publishserviceapi/typevalidation.go @@ -21,7 +21,6 @@ package publishserviceapi import ( "errors" - //"fmt" "strings" ) diff --git a/capifcore/internal/restclient/HTTPClient.go b/capifcore/internal/restclient/HTTPClient.go index c771a54..e9d3469 100644 --- a/capifcore/internal/restclient/HTTPClient.go +++ b/capifcore/internal/restclient/HTTPClient.go @@ -33,6 +33,7 @@ const ContentTypePlain = "text/plain" //go:generate mockery --name HTTPClient type HTTPClient interface { Do(*http.Request) (*http.Response, error) + Get(url string) (*http.Response, error) } type RequestError struct { @@ -44,31 +45,49 @@ func (pe RequestError) Error() string { return fmt.Sprintf("Request failed due to error response with status: %v and body: %v", pe.StatusCode, string(pe.Body)) } +func Get(url string, header map[string]string, client HTTPClient) ([]byte, error) { + return do(http.MethodGet, url, nil, header, client) +} + func Put(url string, body []byte, client HTTPClient) error { var header = map[string]string{"Content-Type": ContentTypeJSON} - return do(http.MethodPut, url, body, header, client) + _, err := do(http.MethodPut, url, body, header, client) + return err } func Post(url string, body []byte, header map[string]string, client HTTPClient) error { - return do(http.MethodPost, url, body, header, client) + _, err := do(http.MethodPost, url, body, header, client) + return err } -func do(method string, url string, body []byte, header map[string]string, client HTTPClient) error { - if req, reqErr := http.NewRequest(method, url, bytes.NewBuffer(body)); reqErr == nil { +func do(method string, url string, body []byte, header map[string]string, client HTTPClient) ([]byte, error) { + if req, reqErr := http.NewRequest(method, url, nil); reqErr == nil { if len(header) > 0 { setHeader(req, header) } + if body != nil { + req.Body = io.NopCloser(bytes.NewReader(body)) + } + if response, respErr := client.Do(req); respErr == nil { if isResponseSuccess(response.StatusCode) { - return nil + fmt.Printf("HTTP client:: response statuscode:: %v body:: %v\n", response.StatusCode, response.Body) + defer response.Body.Close() + + // Read the response body + respBody, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + return respBody, nil } else { - return getRequestError(response) + return nil, getRequestError(response) } } else { - return respErr + return nil, respErr } } else { - return reqErr + return nil, reqErr } } diff --git a/capifcore/internal/restclient/HTTPClient_test.go b/capifcore/internal/restclient/HTTPClient_test.go index e390686..47cf8b7 100644 --- a/capifcore/internal/restclient/HTTPClient_test.go +++ b/capifcore/internal/restclient/HTTPClient_test.go @@ -47,6 +47,7 @@ func TestPutOk(t *testing.T) { clientMock.On("Do", mock.Anything).Return(&http.Response{ StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte("body"))), }, nil) if err := Put("http://localhost:9990", []byte("body"), &clientMock); err != nil { @@ -67,6 +68,36 @@ func TestPutOk(t *testing.T) { clientMock.AssertNumberOfCalls(t, "Do", 1) } +func TestPostOk(t *testing.T) { + assertions := require.New(t) + clientMock := mocks.HTTPClient{} + + clientMock.On("Do", mock.Anything).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte("body"))), + }, nil) + + headers := map[string]string{ + "Content-Type": "application/json", + } + if err := Post("http://localhost:9990", []byte("body"), headers, &clientMock); err != nil { + t.Errorf("Post() error = %v, did not want error", err) + } + var actualRequest *http.Request + clientMock.AssertCalled(t, "Do", mock.MatchedBy(func(req *http.Request) bool { + actualRequest = req + return true + })) + assertions.Equal(http.MethodPost, actualRequest.Method) + assertions.Equal("http", actualRequest.URL.Scheme) + assertions.Equal("localhost:9990", actualRequest.URL.Host) + assertions.Equal("application/json", actualRequest.Header.Get("Content-Type")) + body, _ := io.ReadAll(actualRequest.Body) + expectedBody := []byte("body") + assertions.Equal(expectedBody, body) + clientMock.AssertNumberOfCalls(t, "Do", 1) +} + func Test_doErrorCases(t *testing.T) { assertions := require.New(t) type args struct { @@ -109,7 +140,7 @@ func Test_doErrorCases(t *testing.T) { StatusCode: tt.args.mockReturnStatus, Body: io.NopCloser(bytes.NewReader(tt.args.mockReturnBody)), }, tt.args.mockReturnError) - err := do("PUT", tt.args.url, nil, map[string]string{}, &clientMock) + _, err := do("PUT", tt.args.url, nil, map[string]string{}, &clientMock) assertions.Equal(tt.wantErr, err, tt.name) }) } diff --git a/capifcore/internal/restclient/mocks/HTTPClient.go b/capifcore/internal/restclient/mocks/HTTPClient.go index 7627c4b..7d0064a 100644 --- a/capifcore/internal/restclient/mocks/HTTPClient.go +++ b/capifcore/internal/restclient/mocks/HTTPClient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -18,6 +18,10 @@ func (_m *HTTPClient) Do(_a0 *http.Request) (*http.Response, error) { ret := _m.Called(_a0) var r0 *http.Response + var r1 error + if rf, ok := ret.Get(0).(func(*http.Request) (*http.Response, error)); ok { + return rf(_a0) + } if rf, ok := ret.Get(0).(func(*http.Request) *http.Response); ok { r0 = rf(_a0) } else { @@ -26,7 +30,6 @@ func (_m *HTTPClient) Do(_a0 *http.Request) (*http.Response, error) { } } - var r1 error if rf, ok := ret.Get(1).(func(*http.Request) error); ok { r1 = rf(_a0) } else { @@ -36,6 +39,32 @@ func (_m *HTTPClient) Do(_a0 *http.Request) (*http.Response, error) { return r0, r1 } +// Get provides a mock function with given fields: url +func (_m *HTTPClient) Get(url string) (*http.Response, error) { + ret := _m.Called(url) + + var r0 *http.Response + var r1 error + if rf, ok := ret.Get(0).(func(string) (*http.Response, error)); ok { + return rf(url) + } + if rf, ok := ret.Get(0).(func(string) *http.Response); ok { + r0 = rf(url) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*http.Response) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(url) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + type mockConstructorTestingTNewHTTPClient interface { mock.TestingT Cleanup(func()) diff --git a/capifcore/internal/securityapi/typeupdate.go b/capifcore/internal/securityapi/typeupdate.go index 3402b8e..3adb836 100644 --- a/capifcore/internal/securityapi/typeupdate.go +++ b/capifcore/internal/securityapi/typeupdate.go @@ -59,7 +59,6 @@ func isSecuryMethodsEmpty() bool { } func addSecurityMethodsFromInterfaceDetails(methodsFromInterface *[]publishserviceapi.SecurityMethod, prefMethods *[]publishserviceapi.SecurityMethod) { - if methodsFromInterface != nil { securityMethods = append(securityMethods, *methodsFromInterface...) } @@ -68,9 +67,9 @@ func addSecurityMethodsFromInterfaceDetails(methodsFromInterface *[]publishservi } } -func addSecurityMethodsFromAefProfile(afpProfile *publishserviceapi.AefProfile) { - if afpProfile.SecurityMethods != nil { - securityMethods = append(securityMethods, *afpProfile.SecurityMethods...) +func addSecurityMethodsFromAefProfile(aefProfile *publishserviceapi.AefProfile) { + if aefProfile.SecurityMethods != nil { + securityMethods = append(securityMethods, *aefProfile.SecurityMethods...) } } diff --git a/capifcore/internal/securityservice/security.go b/capifcore/internal/securityservice/security.go index e211f67..ddedc85 100644 --- a/capifcore/internal/securityservice/security.go +++ b/capifcore/internal/securityservice/security.go @@ -192,11 +192,6 @@ func (s *Security) PutTrustedInvokersApiInvokerId(ctx echo.Context, apiInvokerId return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err)) } - err = s.keycloak.AddClient(apiInvokerId, "invokerrealm") - if err != nil { - return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err)) - } - uri := ctx.Request().Host + ctx.Request().URL.String() ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, apiInvokerId)) diff --git a/capifcore/internal/securityservice/security_test.go b/capifcore/internal/securityservice/security_test.go index 1dda127..ea0fbe6 100644 --- a/capifcore/internal/securityservice/security_test.go +++ b/capifcore/internal/securityservice/security_test.go @@ -263,10 +263,7 @@ func TestPutTrustedInvokerSuccessfully(t *testing.T) { publishRegisterMock := publishmocks.PublishRegister{} publishRegisterMock.On("GetAllPublishedServices").Return(publishedServices) - accessMgmMock := keycloackmocks.AccessManagement{} - accessMgmMock.On("AddClient", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(nil) - - requestHandler, _ := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, &accessMgmMock) + requestHandler, _ := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, nil) invokerId := "invokerId" serviceSecurityUnderTest := getServiceSecurity(aefId, apiId) @@ -285,8 +282,6 @@ func TestPutTrustedInvokerSuccessfully(t *testing.T) { assert.Equal(t, *security.SelSecurityMethod, publishserviceapi.SecurityMethodPKI) } invokerRegisterMock.AssertCalled(t, "IsInvokerRegistered", invokerId) - accessMgmMock.AssertCalled(t, "AddClient", invokerId, "invokerrealm") - } func TestPutTrustedInkoverNotRegistered(t *testing.T) { @@ -354,10 +349,7 @@ func TestPutTrustedInvokerInterfaceDetailsNotNil(t *testing.T) { publishRegisterMock := publishmocks.PublishRegister{} publishRegisterMock.On("GetAllPublishedServices").Return(publishedServices) - accessMgmMock := keycloackmocks.AccessManagement{} - accessMgmMock.On("AddClient", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(nil) - - requestHandler, _ := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, &accessMgmMock) + requestHandler, _ := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, nil) invokerId := "invokerId" serviceSecurityUnderTest := getServiceSecurity(aefId, apiId) @@ -386,7 +378,6 @@ func TestPutTrustedInvokerInterfaceDetailsNotNil(t *testing.T) { assert.Equal(t, publishserviceapi.SecurityMethodPSK, *security.SelSecurityMethod) } invokerRegisterMock.AssertCalled(t, "IsInvokerRegistered", invokerId) - accessMgmMock.AssertCalled(t, "AddClient", invokerId, "invokerrealm") } func TestPutTrustedInvokerNotFoundSecurityMethod(t *testing.T) { diff --git a/capifcore/main.go b/capifcore/main.go index 5ba3923..93c0626 100644 --- a/capifcore/main.go +++ b/capifcore/main.go @@ -137,7 +137,7 @@ func getEcho() *echo.Echo { log.Fatalf("Error loading InvokerManagement swagger spec\n: %s", err) } invokerManagerSwagger.Servers = nil - invokerManager := invokermanagement.NewInvokerManager(publishService, eventChannel) + invokerManager := invokermanagement.NewInvokerManager(publishService, km, eventChannel) group = e.Group("/api-invoker-management/v1") group.Use(middleware.OapiRequestValidator(invokerManagerSwagger)) invokermanagementapi.RegisterHandlersWithBaseURL(e, invokerManager, "/api-invoker-management/v1") -- 2.16.6