From 49f1deb9b086589af0d3bbbdf20fde9d7c3c2dd3 Mon Sep 17 00:00:00 2001 From: ochom Date: Thu, 29 Jul 2021 14:37:02 +0300 Subject: [PATCH] feat: add revoking role permissions and deleting roles --- go.sum | 6 + .../infrastructure/database/fb/firebase.go | 48 ++- .../presentation/graph/generated/generated.go | 216 ++++++++++++ .../presentation/graph/profile.graphql | 4 + .../presentation/graph/profile.resolvers.go | 20 ++ pkg/onboarding/repository/mock/onboarding.go | 6 + pkg/onboarding/repository/onboarding.go | 8 +- pkg/onboarding/usecases/roles.go | 122 ++++++- pkg/onboarding/usecases/roles_unit_test.go | 318 ++++++++++++++++++ 9 files changed, 730 insertions(+), 18 deletions(-) diff --git a/go.sum b/go.sum index 6398e6ff..125c9dd4 100644 --- a/go.sum +++ b/go.sum @@ -118,8 +118,10 @@ github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcju github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -363,7 +365,9 @@ github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/savannahghi/converterandformatter v0.0.3/go.mod h1:0o7yieYU10WabPqKuqj+5QL52eTL1eGElxjb+A68bbA= @@ -410,6 +414,7 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= @@ -443,6 +448,7 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= diff --git a/pkg/onboarding/infrastructure/database/fb/firebase.go b/pkg/onboarding/infrastructure/database/fb/firebase.go index 3c47c12f..cb676848 100644 --- a/pkg/onboarding/infrastructure/database/fb/firebase.go +++ b/pkg/onboarding/infrastructure/database/fb/firebase.go @@ -3821,7 +3821,7 @@ func (fr *Repository) GetRoleByID(ctx context.Context, roleID string) (*profileu return nil, err } - role := &profileutils.Role{} + role := profileutils.Role{} err = docs[0].DataTo(role) if err != nil { @@ -3830,7 +3830,7 @@ func (fr *Repository) GetRoleByID(ctx context.Context, roleID string) (*profileu return nil, exceptions.InternalServerError(err) } - return role, nil + return &role, nil } // GetRolesByIDs gets all roles matching provided roleIDs if specified otherwise all roles @@ -3853,6 +3853,50 @@ func (fr *Repository) GetRolesByIDs( return &roles, nil } +// DeleteRole removes a role permanently from the database +func (fr *Repository) DeleteRole( + ctx context.Context, + roleID string, +) (bool, error) { + ctx, span := tracer.Start(ctx, "DeleteRole") + defer span.End() + + query := &GetAllQuery{ + CollectionName: fr.GetRolesCollectionName(), + FieldName: "id", + Value: roleID, + Operator: "==", + } + + docs, err := fr.FirestoreClient.GetAll(ctx, query) + if err != nil { + utils.RecordSpanError(span, err) + return false, exceptions.InternalServerError(err) + } + + // means the document was removed or does not exist + if len(docs) == 0 { + return true, nil + } + deleteCommand := &DeleteCommand{ + CollectionName: fr.GetExperimentParticipantCollectionName(), + ID: docs[0].Ref.ID, + } + err = fr.FirestoreClient.Delete(ctx, deleteCommand) + if err != nil { + utils.RecordSpanError(span, err) + return false, exceptions.InternalServerError( + fmt.Errorf( + "unable to remove role of ID %v, error: %v", + roleID, + err, + ), + ) + } + + return true, nil +} + // CheckIfRoleNameExists checks if a role with a similar name exists // Ensures unique name for each role during creation func (fr *Repository) CheckIfRoleNameExists(ctx context.Context, name string) (bool, error) { diff --git a/pkg/onboarding/presentation/graph/generated/generated.go b/pkg/onboarding/presentation/graph/generated/generated.go index f757ec29..acab4f0f 100644 --- a/pkg/onboarding/presentation/graph/generated/generated.go +++ b/pkg/onboarding/presentation/graph/generated/generated.go @@ -273,6 +273,7 @@ type ComplexityRoot struct { CreateRole func(childComplexity int, input dto.RoleInput) int DeactivateAgent func(childComplexity int, agentID string) int DeleteFavoriteNavAction func(childComplexity int, title string) int + DeleteRole func(childComplexity int, roleID string) int DeregisterAllMicroservices func(childComplexity int) int DeregisterMicroservice func(childComplexity int, id string) int ProcessKYCRequest func(childComplexity int, id string, status domain.KYCProcessStatus, rejectionReason *string) int @@ -284,6 +285,7 @@ type ComplexityRoot struct { RetireKYCProcessingRequest func(childComplexity int) int RetireSecondaryEmailAddresses func(childComplexity int, emails []string) int RetireSecondaryPhoneNumbers func(childComplexity int, phones []string) int + RevokeRolePermission func(childComplexity int, roleID string, permissionScope string) int SaveFavoriteNavAction func(childComplexity int, title string) int SetPrimaryEmailAddress func(childComplexity int, email string, otp string) int SetPrimaryPhoneNumber func(childComplexity int, phone string, otp string) int @@ -610,7 +612,9 @@ type MutationResolver interface { DeregisterMicroservice(ctx context.Context, id string) (bool, error) DeregisterAllMicroservices(ctx context.Context) (bool, error) CreateRole(ctx context.Context, input dto.RoleInput) (*dto.RoleOutput, error) + DeleteRole(ctx context.Context, roleID string) (bool, error) AddPermissionsToRole(ctx context.Context, input dto.RolePermissionInput) (*dto.RoleOutput, error) + RevokeRolePermission(ctx context.Context, roleID string, permissionScope string) (*dto.RoleOutput, error) } type QueryResolver interface { DummyQuery(ctx context.Context) (*bool, error) @@ -1792,6 +1796,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.DeleteFavoriteNavAction(childComplexity, args["title"].(string)), true + case "Mutation.deleteRole": + if e.complexity.Mutation.DeleteRole == nil { + break + } + + args, err := ec.field_Mutation_deleteRole_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.DeleteRole(childComplexity, args["roleID"].(string)), true + case "Mutation.deregisterAllMicroservices": if e.complexity.Mutation.DeregisterAllMicroservices == nil { break @@ -1914,6 +1930,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.RetireSecondaryPhoneNumbers(childComplexity, args["phones"].([]string)), true + case "Mutation.revokeRolePermission": + if e.complexity.Mutation.RevokeRolePermission == nil { + break + } + + args, err := ec.field_Mutation_revokeRolePermission_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.RevokeRolePermission(childComplexity, args["roleID"].(string), args["permissionScope"].(string)), true + case "Mutation.saveFavoriteNavAction": if e.complexity.Mutation.SaveFavoriteNavAction == nil { break @@ -4102,7 +4130,11 @@ input RolePermissionInput { getAllRoles: [RoleOutput] +<<<<<<< HEAD getAllPermissions: [Permission!]! +======= + getAllPermissions: [Permission] +>>>>>>> ebe8aa8... feat: add list roles and permissions } extend type Mutation { @@ -4220,7 +4252,11 @@ extend type Mutation { createRole(input: RoleInput!): RoleOutput! + deleteRole(roleID: String!): Boolean! + addPermissionsToRole(input: RolePermissionInput!): RoleOutput! + + revokeRolePermission(roleID: String!, permissionScope: String!): RoleOutput! } `, BuiltIn: false}, {Name: "pkg/onboarding/presentation/graph/types.graphql", Input: `scalar Date @@ -5154,6 +5190,21 @@ func (ec *executionContext) field_Mutation_deleteFavoriteNavAction_args(ctx cont return args, nil } +func (ec *executionContext) field_Mutation_deleteRole_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["roleID"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roleID")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["roleID"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_deregisterMicroservice_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -5307,6 +5358,30 @@ func (ec *executionContext) field_Mutation_retireSecondaryPhoneNumbers_args(ctx return args, nil } +func (ec *executionContext) field_Mutation_revokeRolePermission_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["roleID"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roleID")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["roleID"] = arg0 + var arg1 string + if tmp, ok := rawArgs["permissionScope"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("permissionScope")) + arg1, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["permissionScope"] = arg1 + return args, nil +} + func (ec *executionContext) field_Mutation_saveFavoriteNavAction_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -11718,6 +11793,48 @@ func (ec *executionContext) _Mutation_createRole(ctx context.Context, field grap return ec.marshalNRoleOutput2ᚖgithubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋapplicationᚋdtoᚐRoleOutput(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_deleteRole(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_deleteRole_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().DeleteRole(rctx, args["roleID"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_addPermissionsToRole(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -11760,6 +11877,48 @@ func (ec *executionContext) _Mutation_addPermissionsToRole(ctx context.Context, return ec.marshalNRoleOutput2ᚖgithubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋapplicationᚋdtoᚐRoleOutput(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_revokeRolePermission(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_revokeRolePermission_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().RevokeRolePermission(rctx, args["roleID"].(string), args["permissionScope"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*dto.RoleOutput) + fc.Result = res + return ec.marshalNRoleOutput2ᚖgithubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋapplicationᚋdtoᚐRoleOutput(ctx, field.Selections, res) +} + func (ec *executionContext) _NHIFDetails_id(ctx context.Context, field graphql.CollectedField, obj *domain.NHIFDetails) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -22301,11 +22460,21 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "deleteRole": + out.Values[i] = ec._Mutation_deleteRole(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "addPermissionsToRole": out.Values[i] = ec._Mutation_addPermissionsToRole(ctx, field) if out.Values[i] == graphql.Null { invalids++ } + case "revokeRolePermission": + out.Values[i] = ec._Mutation_revokeRolePermission(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -26174,6 +26343,53 @@ func (ec *executionContext) marshalOPermission2ᚕgithubᚗcomᚋsavannahghiᚋp return ret } +func (ec *executionContext) marshalOPermission2ᚕᚖgithubᚗcomᚋsavannahghiᚋprofileutilsᚐPermission(ctx context.Context, sel ast.SelectionSet, v []*profileutils.Permission) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalOPermission2ᚖgithubᚗcomᚋsavannahghiᚋprofileutilsᚐPermission(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + +func (ec *executionContext) marshalOPermission2ᚖgithubᚗcomᚋsavannahghiᚋprofileutilsᚐPermission(ctx context.Context, sel ast.SelectionSet, v *profileutils.Permission) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Permission(ctx, sel, v) +} + func (ec *executionContext) unmarshalOPermissionType2ᚕgithubᚗcomᚋsavannahghiᚋprofileutilsᚐPermissionTypeᚄ(ctx context.Context, v interface{}) ([]profileutils.PermissionType, error) { if v == nil { return nil, nil diff --git a/pkg/onboarding/presentation/graph/profile.graphql b/pkg/onboarding/presentation/graph/profile.graphql index 42d122b7..df2ff520 100644 --- a/pkg/onboarding/presentation/graph/profile.graphql +++ b/pkg/onboarding/presentation/graph/profile.graphql @@ -161,5 +161,9 @@ extend type Mutation { createRole(input: RoleInput!): RoleOutput! + deleteRole(roleID: String!): Boolean! + addPermissionsToRole(input: RolePermissionInput!): RoleOutput! + + revokeRolePermission(roleID: String!, permissionScope: String!): RoleOutput! } diff --git a/pkg/onboarding/presentation/graph/profile.resolvers.go b/pkg/onboarding/presentation/graph/profile.resolvers.go index 671df7a2..d0cf57b1 100644 --- a/pkg/onboarding/presentation/graph/profile.resolvers.go +++ b/pkg/onboarding/presentation/graph/profile.resolvers.go @@ -586,15 +586,35 @@ func (r *mutationResolver) CreateRole(ctx context.Context, input dto.RoleInput) return role, err } +func (r *mutationResolver) DeleteRole(ctx context.Context, roleID string) (bool, error) { + startTime := time.Now() + + success, err := r.interactor.Role.DeleteRole(ctx, roleID) + defer serverutils.RecordGraphqlResolverMetrics(ctx, startTime, "deleteRole", err) + + return success, err +} + func (r *mutationResolver) AddPermissionsToRole(ctx context.Context, input dto.RolePermissionInput) (*dto.RoleOutput, error) { startTime := time.Now() role, err := r.interactor.Role.AddPermissionsToRole(ctx, input) + defer serverutils.RecordGraphqlResolverMetrics(ctx, startTime, "addPermissionsToRole", err) return role, err } +func (r *mutationResolver) RevokeRolePermission(ctx context.Context, roleID string, permissionScope string) (*dto.RoleOutput, error) { + startTime := time.Now() + + role, err := r.interactor.Role.RevokeRolePermission(ctx, roleID, permissionScope) + + defer serverutils.RecordGraphqlResolverMetrics(ctx, startTime, "revokeRolePermission", err) + + return role, err +} + func (r *queryResolver) DummyQuery(ctx context.Context) (*bool, error) { dummy := true return &dummy, nil diff --git a/pkg/onboarding/repository/mock/onboarding.go b/pkg/onboarding/repository/mock/onboarding.go index 779786cc..06c1d33b 100644 --- a/pkg/onboarding/repository/mock/onboarding.go +++ b/pkg/onboarding/repository/mock/onboarding.go @@ -190,6 +190,7 @@ type FakeOnboardingRepository struct { GetRolesByIDsFn func(ctx context.Context, roleIDs []string) (*[]profileutils.Role, error) GetRoleByIDFn func(ctx context.Context, roleID string) (*profileutils.Role, error) CheckIfRoleNameExistsFn func(ctx context.Context, name string) (bool, error) + DeleteRoleFn func(ctx context.Context, roleID string) (bool, error) CheckIfUserHasPermissionFn func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) } @@ -934,3 +935,8 @@ func (f *FakeOnboardingRepository) CheckIfRoleNameExists(ctx context.Context, na func (f *FakeOnboardingRepository) CheckIfUserHasPermission(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { return f.CheckIfUserHasPermissionFn(ctx, UID, requiredPermission) } + +// DeleteRole ... +func (f *FakeOnboardingRepository) DeleteRole(ctx context.Context, roleID string) (bool, error) { + return f.DeleteRoleFn(ctx, roleID) +} diff --git a/pkg/onboarding/repository/onboarding.go b/pkg/onboarding/repository/onboarding.go index 0cbc7835..02576e6d 100644 --- a/pkg/onboarding/repository/onboarding.go +++ b/pkg/onboarding/repository/onboarding.go @@ -320,11 +320,9 @@ type RolesRepository interface { CheckIfRoleNameExists(ctx context.Context, name string) (bool, error) - UpdateRoleDetails( - ctx context.Context, - profileID string, - role profileutils.Role, - ) (*profileutils.Role, error) + UpdateRoleDetails(ctx context.Context, profileID string, role profileutils.Role) (*profileutils.Role, error) + + DeleteRole(ctx context.Context, roleID string) (bool, error) CheckIfUserHasPermission(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) } diff --git a/pkg/onboarding/usecases/roles.go b/pkg/onboarding/usecases/roles.go index 462360da..a3c613f9 100644 --- a/pkg/onboarding/usecases/roles.go +++ b/pkg/onboarding/usecases/roles.go @@ -16,12 +16,17 @@ import ( type RoleUseCase interface { CreateRole(ctx context.Context, input dto.RoleInput) (*dto.RoleOutput, error) GetAllRoles(ctx context.Context) ([]*dto.RoleOutput, error) + DeleteRole(ctx context.Context, roleID string) (bool, error) GetAllPermissions(ctx context.Context) ([]*profileutils.Permission, error) AddPermissionsToRole( ctx context.Context, input dto.RolePermissionInput, ) (*dto.RoleOutput, error) + RevokeRolePermission( + ctx context.Context, + roleID string, permissionScope string, + ) (*dto.RoleOutput, error) GetUserRoles(ctx context.Context, UID string) (*[]profileutils.Role, error) @@ -152,10 +157,38 @@ func (r *RoleUseCaseImpl) GetAllRoles(ctx context.Context) ([]*dto.RoleOutput, e return roleOutput, nil } +//DeleteRole removes a role from the database permanently +func (r *RoleUseCaseImpl) DeleteRole(ctx context.Context, roleID string) (bool, error) { + ctx, span := tracer.Start(ctx, "DeleteRole") + defer span.End() + + user, err := r.baseExt.GetLoggedInUser(ctx) + if err != nil { + utils.RecordSpanError(span, err) + return false, err + } + // Check logged in user has the right permissions + allowed, err := r.repo.CheckIfUserHasPermission(ctx, user.UID, profileutils.CanEditRole) + if err != nil { + utils.RecordSpanError(span, err) + return false, err + } + if !allowed { + return false, exceptions.RoleNotValid( + fmt.Errorf("error: logged in user does not have permissions to delete roles"), + ) + } + + success, err := r.repo.DeleteRole(ctx, roleID) + if err != nil { + utils.RecordSpanError(span, err) + return false, err + } + return success, nil +} + //GetAllPermissions returns a list of all permissions declared in the system -func (r *RoleUseCaseImpl) GetAllPermissions( - ctx context.Context, -) ([]*profileutils.Permission, error) { +func (r *RoleUseCaseImpl) GetAllPermissions(ctx context.Context) ([]*profileutils.Permission, error) { ctx, span := tracer.Start(ctx, "GetAllPermissions") defer span.End() @@ -164,16 +197,9 @@ func (r *RoleUseCaseImpl) GetAllPermissions( utils.RecordSpanError(span, err) return nil, err } - output := []*profileutils.Permission{} for _, perm := range perms { - - p := &profileutils.Permission{ - Scope: perm.Scope, - Group: perm.Group, - Description: perm.Description, - } - output = append(output, p) + output = append(output, &perm) } return output, nil @@ -255,6 +281,80 @@ func (r *RoleUseCaseImpl) AddPermissionsToRole( return output, nil } +// RevokeRolePermission removes a permission from a role +func (r *RoleUseCaseImpl) RevokeRolePermission( + ctx context.Context, + roleID string, permissionScope string, +) (*dto.RoleOutput, error) { + ctx, span := tracer.Start(ctx, "RevokeRolePermission") + defer span.End() + + user, err := r.baseExt.GetLoggedInUser(ctx) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + // Check logged in user has the right permissions + allowed, err := r.repo.CheckIfUserHasPermission(ctx, user.UID, profileutils.CanEditRole) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + if !allowed { + return nil, exceptions.RoleNotValid( + fmt.Errorf("error: logged in user does not have permissions to edit role"), + ) + } + + role, err := r.repo.GetRoleByID(ctx, roleID) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + newScopes := []string{} + + for _, roleScope := range role.Scopes { + if roleScope != permissionScope { + newScopes = append(newScopes, roleScope) + } + } + + role.Scopes = newScopes + + userProfile, err := r.repo.GetUserProfileByUID(ctx, user.UID, false) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + // save role to database + updatedRole, err := r.repo.UpdateRoleDetails(ctx, userProfile.ID, *role) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + // get permissions + perms, err := updatedRole.Permissions(ctx) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + output := &dto.RoleOutput{ + ID: updatedRole.ID, + Name: updatedRole.Name, + Description: updatedRole.Description, + Active: updatedRole.Active, + Scopes: updatedRole.Scopes, + Permissions: perms, + } + + return output, nil +} + // GetRole gets a specific role and its permissions func (r *RoleUseCaseImpl) GetRole(ctx context.Context, ID string) (*dto.RoleOutput, error) { ctx, span := tracer.Start(ctx, "GetRole") diff --git a/pkg/onboarding/usecases/roles_unit_test.go b/pkg/onboarding/usecases/roles_unit_test.go index 1bbcd3ff..42b04f53 100644 --- a/pkg/onboarding/usecases/roles_unit_test.go +++ b/pkg/onboarding/usecases/roles_unit_test.go @@ -330,6 +330,123 @@ func TestRoleUseCaseImpl_GetAllRoles(t *testing.T) { } } +func TestRoleUseCaseImpl_DeleteRole(t *testing.T) { + ctx := context.Background() + i, err := InitializeFakeOnboardingInteractor() + + if err != nil { + t.Errorf("failed to fake initialize onboarding interactor: %v", + err, + ) + return + } + + type args struct { + ctx context.Context + roleID string + } + + input := args{ + ctx: ctx, + roleID: "123", + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "sad: did not get logged in user", + args: input, + want: false, + wantErr: true, + }, + { + name: "sad: unable to check if user has permission", + args: input, + want: false, + wantErr: true, + }, + { + name: "sad: user do not have required permission", + args: input, + want: false, + wantErr: true, + }, + { + name: "sad: unable to delete role", + args: input, + want: false, + wantErr: true, + }, + { + name: "happy: deleted role", + args: input, + want: true, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "sad: did not get logged in user" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return nil, fmt.Errorf("error, did not get logged in user") + } + } + + if tt.name == "sad: unable to check if user has permission" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return false, fmt.Errorf("error unable to check is user has permission") + } + } + + if tt.name == "sad: user do not have required permission" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return false, nil + } + } + if tt.name == "sad: unable to delete role" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + fakeRepo.DeleteRoleFn = func(ctx context.Context, roleID string) (bool, error) { + return false, fmt.Errorf("error, unable to delete roles") + } + } + + if tt.name == "happy: deleted role" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + fakeRepo.DeleteRoleFn = func(ctx context.Context, roleID string) (bool, error) { + return true, nil + } + } + got, err := i.Role.DeleteRole(tt.args.ctx, tt.args.roleID) + if (err != nil) != tt.wantErr { + t.Errorf("RoleUseCaseImpl.DeleteRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("RoleUseCaseImpl.DeleteRole() = %v, want %v", got, tt.want) + } + }) + } +} + func TestRoleUseCaseImpl_GetAllPermissions(t *testing.T) { ctx := context.Background() @@ -602,6 +719,207 @@ func TestRoleUseCaseImpl_AddPermissionsToRole(t *testing.T) { } } +func TestRoleUseCaseImpl_RevokeRolePermissions(t *testing.T) { + ctx := context.Background() + i, err := InitializeFakeOnboardingInteractor() + + if err != nil { + t.Errorf("failed to fake initialize onboarding interactor: %v", + err, + ) + return + } + + allPerms, err := profileutils.AllPermissions(ctx) + if err != nil { + t.Error("error did not get all permissions") + return + } + + perms := []profileutils.Permission{} + for _, perm := range allPerms { + if perm.Scope == "role.create" { + perm.Allowed = true + } + perms = append(perms, perm) + } + + expectedOutput := dto.RoleOutput{ + ID: "123", + Scopes: []string{"role.create"}, + Permissions: perms, + } + + type args struct { + ctx context.Context + roleID string + scope string + } + + input := args{ + ctx: ctx, + roleID: "123", + scope: "agent.create", + } + + tests := []struct { + name string + args args + want *dto.RoleOutput + wantErr bool + }{ + { + name: "sad unable to get logged in user", + args: input, + want: nil, + wantErr: true, + }, + { + name: "sad unable to check if user has permissions", + args: input, + want: nil, + wantErr: true, + }, + { + name: "sad user do not have required permission", + args: input, + want: nil, + wantErr: true, + }, + { + name: "sad unable to get role by id", + args: input, + want: nil, + wantErr: true, + }, + { + name: "sad unable to get user profile", + args: input, + want: nil, + wantErr: true, + }, + { + name: "sad unable to update role details", + args: input, + want: nil, + wantErr: true, + }, + { + name: "happy_added_permission_to_roles", + args: input, + want: &expectedOutput, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "sad unable to get logged in user" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return nil, fmt.Errorf("unable to get logged in user") + } + } + + if tt.name == "sad unable to check if user has permissions" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: "123"}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return false, fmt.Errorf("unable to check permissions") + } + } + + if tt.name == "sad user do not have required permission" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: "123"}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return false, nil + } + } + + if tt.name == "sad unable to get role by id" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: "123"}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return nil, fmt.Errorf("error unable to get role to edit") + } + } + + if tt.name == "sad unable to get user profile" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: "123"}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{}, nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("unable to get user profile") + } + } + + if tt.name == "sad unable to update role details" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: "123"}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{}, nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{}, nil + } + fakeRepo.UpdateRoleDetailsFn = func(ctx context.Context, profileID string, role profileutils.Role) (*profileutils.Role, error) { + return nil, fmt.Errorf("error unable to update role") + } + } + + if tt.name == "happy_added_permission_to_roles" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: "123"}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{}, nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{}, nil + } + fakeRepo.UpdateRoleDetailsFn = func(ctx context.Context, profileID string, role profileutils.Role) (*profileutils.Role, error) { + return &profileutils.Role{ + ID: "123", + Scopes: []string{"role.create"}, + }, nil + } + } + + got, err := i.Role.RevokeRolePermission(tt.args.ctx, tt.args.roleID, tt.args.scope) + + if (err != nil) != tt.wantErr { + t.Errorf( + "RoleUseCaseImpl.AddPermissionsToRole() error = %v, wantErr %v", + err, + tt.wantErr, + ) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RoleUseCaseImpl.AddPermissionsToRole() = %v, want %v", got, tt.want) + } + }) + } +} + func TestRoleUseCaseImpl_GetRole(t *testing.T) { ctx := context.Background() i, err := InitializeFakeOnboardingInteractor()