Implementation for Update and revoke trustedInvokers endpoint 25/10725/1
authorychacon <yennifer.chacon@est.tech>
Fri, 10 Mar 2023 16:22:27 +0000 (17:22 +0100)
committerychacon <yennifer.chacon@est.tech>
Fri, 10 Mar 2023 16:22:27 +0000 (17:22 +0100)
Issue-ID: NONRTRIC-848
Signed-off-by: ychacon <yennifer.chacon@est.tech>
Change-Id: Iee3c7fbeb8032bda02f65a0ef513ce65d8359b9a

capifcore/internal/securityapi/typevalidation.go
capifcore/internal/securityservice/security.go
capifcore/internal/securityservice/security_test.go

index 4be8aee..4a9ee28 100644 (file)
@@ -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,
index d3d9026..aee022e 100644 (file)
@@ -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(&notification); 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 {
index 1abb8ae..57e9cea 100644 (file)
@@ -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 {