From b2a87e363829f6447b4f06c1aa4524608bbeb422 Mon Sep 17 00:00:00 2001 From: ychacon Date: Fri, 10 Mar 2023 17:22:27 +0100 Subject: [PATCH] Implementation for Update and revoke trustedInvokers endpoint Issue-ID: NONRTRIC-848 Signed-off-by: ychacon Change-Id: Iee3c7fbeb8032bda02f65a0ef513ce65d8359b9a --- capifcore/internal/securityapi/typevalidation.go | 21 +++++ capifcore/internal/securityservice/security.go | 80 +++++++++++++++- .../internal/securityservice/security_test.go | 102 +++++++++++++++++++++ 3 files changed, 201 insertions(+), 2 deletions(-) diff --git a/capifcore/internal/securityapi/typevalidation.go b/capifcore/internal/securityapi/typevalidation.go index 4be8aee..4a9ee28 100644 --- a/capifcore/internal/securityapi/typevalidation.go +++ b/capifcore/internal/securityapi/typevalidation.go @@ -83,6 +83,27 @@ func (si SecurityInformation) Validate() error { return nil } +func (sn SecurityNotification) Validate() error { + + if len(strings.TrimSpace(string(sn.ApiInvokerId))) == 0 { + return errors.New("SecurityNotification missing required ApiInvokerId") + } + + if len(sn.ApiIds) < 1 { + return errors.New("SecurityNotification missing required ApiIds") + } + + if len(strings.TrimSpace(string(sn.Cause))) == 0 { + return errors.New("SecurityNotification missing required Cause") + } + + if sn.Cause != CauseOVERLIMITUSAGE && sn.Cause != CauseUNEXPECTEDREASON { + return errors.New("SecurityNotification unexpected value for Cause") + } + + return nil +} + func createAccessTokenError(err AccessTokenErrError, message string) AccessTokenErr { return AccessTokenErr{ Error: err, diff --git a/capifcore/internal/securityservice/security.go b/capifcore/internal/securityservice/security.go index d3d9026..aee022e 100644 --- a/capifcore/internal/securityservice/security.go +++ b/capifcore/internal/securityservice/security.go @@ -29,6 +29,7 @@ import ( "github.com/labstack/echo/v4" copystructure "github.com/mitchellh/copystructure" + "k8s.io/utils/strings/slices" "oransc.org/nonrtric/capifcore/internal/common29122" securityapi "oransc.org/nonrtric/capifcore/internal/securityapi" @@ -224,11 +225,86 @@ func (s *Security) prepareNewSecurityContext(newContext *securityapi.ServiceSecu } func (s *Security) PostTrustedInvokersApiInvokerIdDelete(ctx echo.Context, apiInvokerId string) error { - return ctx.NoContent(http.StatusNotImplemented) + var notification securityapi.SecurityNotification + + errMsg := "Unable to revoke invoker due to %s" + + if err := ctx.Bind(¬ification); err != nil { + return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, "invalid format for security notification")) + } + + if err := notification.Validate(); err != nil { + return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err)) + } + + if ss, ok := s.trustedInvokers[apiInvokerId]; ok { + securityInfoCopy := s.revokeTrustedInvoker(&ss, notification, apiInvokerId) + + if len(securityInfoCopy) == 0 { + s.deleteTrustedInvoker(apiInvokerId) + } else { + ss.SecurityInfo = securityInfoCopy + s.updateTrustedInvoker(ss, apiInvokerId) + } + + } else { + return sendCoreError(ctx, http.StatusNotFound, "the invoker is not register as a trusted invoker") + } + + return ctx.NoContent(http.StatusNoContent) + +} + +func (s *Security) revokeTrustedInvoker(ss *securityapi.ServiceSecurity, notification securityapi.SecurityNotification, apiInvokerId string) []securityapi.SecurityInformation { + + data, _ := copystructure.Copy(ss.SecurityInfo) + securityInfoCopy, _ := data.([]securityapi.SecurityInformation) + + for i, context := range ss.SecurityInfo { + if notification.AefId == context.AefId || slices.Contains(notification.ApiIds, *context.ApiId) { + securityInfoCopy = append(securityInfoCopy[:i], securityInfoCopy[i+1:]...) + } + } + + return securityInfoCopy + } func (s *Security) PostTrustedInvokersApiInvokerIdUpdate(ctx echo.Context, apiInvokerId string) error { - return ctx.NoContent(http.StatusNotImplemented) + var serviceSecurity securityapi.ServiceSecurity + + errMsg := "Unable to update service security context due to %s" + + if err := ctx.Bind(&serviceSecurity); err != nil { + return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, "invalid format for service security context")) + } + + if err := serviceSecurity.Validate(); err != nil { + return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err)) + } + + if _, ok := s.trustedInvokers[apiInvokerId]; ok { + s.updateTrustedInvoker(serviceSecurity, apiInvokerId) + } else { + return sendCoreError(ctx, http.StatusNotFound, "the invoker is not register as a trusted invoker") + } + + uri := ctx.Request().Host + ctx.Request().URL.String() + ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, apiInvokerId)) + + err := ctx.JSON(http.StatusOK, s.trustedInvokers[apiInvokerId]) + if err != nil { + // Something really bad happened, tell Echo that our handler failed + return err + } + + return nil +} + +func (s *Security) updateTrustedInvoker(serviceSecurity securityapi.ServiceSecurity, invokerId string) { + s.lock.Lock() + defer s.lock.Unlock() + s.trustedInvokers[invokerId] = serviceSecurity } func sendAccessTokenError(ctx echo.Context, code int, err securityapi.AccessTokenErrError, message string) error { diff --git a/capifcore/internal/securityservice/security_test.go b/capifcore/internal/securityservice/security_test.go index 1abb8ae..57e9cea 100644 --- a/capifcore/internal/securityservice/security_test.go +++ b/capifcore/internal/securityservice/security_test.go @@ -488,6 +488,108 @@ func TestGetSecurityContextByInvokerId(t *testing.T) { } } +func TestUpdateTrustedInvoker(t *testing.T) { + + requestHandler, securityUnderTest := getEcho(nil, nil, nil, nil) + + aefId := "aefId" + apiId := "apiId" + invokerId := "invokerId" + serviceSecurityTest := getServiceSecurity(aefId, apiId) + serviceSecurityTest.SecurityInfo[0].ApiId = &apiId + securityUnderTest.trustedInvokers[invokerId] = serviceSecurityTest + + // Update the service security with valid invoker, should return 200 with updated service security + newNotifURL := "http://golang.org/" + serviceSecurityTest.NotificationDestination = common29122.Uri(newNotifURL) + result := testutil.NewRequest().Post("/trustedInvokers/"+invokerId+"/update").WithJsonBody(serviceSecurityTest).Go(t, requestHandler) + + var resultResponse securityapi.ServiceSecurity + assert.Equal(t, http.StatusOK, result.Code()) + err := result.UnmarshalBodyToObject(&resultResponse) + assert.NoError(t, err, "error unmarshaling response") + assert.Equal(t, newNotifURL, string(resultResponse.NotificationDestination)) + + // Update with an service security missing required NotificationDestination, should get 400 with problem details + invalidServiceSecurity := securityapi.ServiceSecurity{ + SecurityInfo: []securityapi.SecurityInformation{ + { + AefId: &aefId, + ApiId: &apiId, + PrefSecurityMethods: []publishserviceapi.SecurityMethod{ + publishserviceapi.SecurityMethodOAUTH, + }, + }, + }, + } + + result = testutil.NewRequest().Post("/trustedInvokers/"+invokerId+"/update").WithJsonBody(invalidServiceSecurity).Go(t, requestHandler) + + assert.Equal(t, http.StatusBadRequest, result.Code()) + var problemDetails common29122.ProblemDetails + err = result.UnmarshalBodyToObject(&problemDetails) + assert.NoError(t, err, "error unmarshaling response") + assert.Equal(t, http.StatusBadRequest, *problemDetails.Status) + assert.Contains(t, *problemDetails.Cause, "missing") + assert.Contains(t, *problemDetails.Cause, "notificationDestination") + + // Update a service security that has not been registered, should get 404 with problem details + missingId := "1" + result = testutil.NewRequest().Post("/trustedInvokers/"+missingId+"/update").WithJsonBody(serviceSecurityTest).Go(t, requestHandler) + + assert.Equal(t, http.StatusNotFound, result.Code()) + err = result.UnmarshalBodyToObject(&problemDetails) + assert.NoError(t, err, "error unmarshaling response") + assert.Equal(t, http.StatusNotFound, *problemDetails.Status) + assert.Contains(t, *problemDetails.Cause, "not register") + assert.Contains(t, *problemDetails.Cause, "trusted invoker") +} + +func TestRevokeAuthorizationToInvoker(t *testing.T) { + aefId := "aefId" + apiId := "apiId" + invokerId := "invokerId" + + notification := securityapi.SecurityNotification{ + AefId: &aefId, + ApiInvokerId: invokerId, + ApiIds: []string{apiId}, + Cause: securityapi.CauseUNEXPECTEDREASON, + } + + requestHandler, securityUnderTest := getEcho(nil, nil, nil, nil) + + serviceSecurityTest := getServiceSecurity(aefId, apiId) + serviceSecurityTest.SecurityInfo[0].ApiId = &apiId + + apiIdTwo := "apiIdTwo" + secInfo := securityapi.SecurityInformation{ + AefId: &aefId, + ApiId: &apiIdTwo, + PrefSecurityMethods: []publishserviceapi.SecurityMethod{ + publishserviceapi.SecurityMethodPKI, + }, + } + + serviceSecurityTest.SecurityInfo = append(serviceSecurityTest.SecurityInfo, secInfo) + + securityUnderTest.trustedInvokers[invokerId] = serviceSecurityTest + + // Revoke apiId + result := testutil.NewRequest().Post("/trustedInvokers/"+invokerId+"/delete").WithJsonBody(notification).Go(t, requestHandler) + + assert.Equal(t, http.StatusNoContent, result.Code()) + assert.Equal(t, 1, len(securityUnderTest.trustedInvokers[invokerId].SecurityInfo)) + + notification.ApiIds = []string{apiIdTwo} + // Revoke apiIdTwo + result = testutil.NewRequest().Post("/trustedInvokers/"+invokerId+"/delete").WithJsonBody(notification).Go(t, requestHandler) + + assert.Equal(t, http.StatusNoContent, result.Code()) + _, ok := securityUnderTest.trustedInvokers[invokerId] + assert.False(t, ok) +} + func getEcho(serviceRegister providermanagement.ServiceRegister, publishRegister publishservice.PublishRegister, invokerRegister invokermanagement.InvokerRegister, keycloakMgm keycloak.AccessManagement) (*echo.Echo, *Security) { swagger, err := securityapi.GetSwagger() if err != nil { -- 2.16.6