From e0a7c967718a26d081d3a3ba6f0766aa4b541206 Mon Sep 17 00:00:00 2001 From: Bailin He <15058035+bailinhe@users.noreply.github.com> Date: Fri, 3 May 2024 14:40:37 -0400 Subject: [PATCH] Apply suggestions from code review Co-authored-by: John Schaeffer Signed-off-by: Bailin He <15058035+bailinhe@users.noreply.github.com> --- internal/api/rolebindings.go | 57 +-- internal/api/router.go | 2 +- internal/api/types.go | 24 +- internal/query/errors.go | 11 + internal/query/rolebindings.go | 253 +++---------- internal/query/rolebindings_test.go | 50 ++- internal/query/roles_v2.go | 32 +- internal/query/roles_v2_test.go | 55 ++- internal/storage/rolebinding.go | 56 +-- internal/types/types.go | 9 +- openapi-v2.yaml | 553 ++++++++++++++-------------- 11 files changed, 464 insertions(+), 638 deletions(-) diff --git a/internal/api/rolebindings.go b/internal/api/rolebindings.go index feb67bb5b..55fc97d15 100644 --- a/internal/api/rolebindings.go +++ b/internal/api/rolebindings.go @@ -14,18 +14,6 @@ import ( "go.infratographer.com/permissions-api/internal/types" ) -func resourceToSubject(subjects []types.RoleBindingSubject) []roleBindingSubject { - resp := make([]roleBindingSubject, len(subjects)) - for i, subj := range subjects { - resp[i] = roleBindingSubject{ - ID: subj.SubjectResource.ID, - Type: subj.SubjectResource.Type, - } - } - - return resp -} - func (r *Router) roleBindingCreate(c echo.Context) error { resourceIDStr := c.Param("id") @@ -72,17 +60,16 @@ func (r *Router) roleBindingCreate(c echo.Context) error { return r.errorResponse("error creating role resource", err) } - subjects := make([]types.RoleBindingSubject, len(body.Subjects)) + subjects := make([]types.RoleBindingSubject, len(body.SubjectIDs)) - for i, s := range body.Subjects { - subj, err := r.engine.NewResourceFromID(s.ID) + for i, sid := range body.SubjectIDs { + subj, err := r.engine.NewResourceFromID(sid) if err != nil { return r.errorResponse("error creating subject resource", err) } subjects[i] = types.RoleBindingSubject{ SubjectResource: subj, - Condition: nil, } } @@ -96,11 +83,8 @@ func (r *Router) roleBindingCreate(c echo.Context) error { roleBindingResponse{ ID: rb.ID, ResourceID: rb.ResourceID, - Subjects: resourceToSubject(rb.Subjects), - Role: roleBindingResponseRole{ - ID: rb.Role.ID, - Name: rb.Role.Name, - }, + SubjectIDs: rb.SubjectIDs, + RoleID: rb.RoleID, CreatedBy: rb.CreatedBy, UpdatedBy: rb.UpdatedBy, @@ -151,11 +135,8 @@ func (r *Router) roleBindingsList(c echo.Context) error { resp.Data[i] = roleBindingResponse{ ID: rb.ID, ResourceID: rb.ResourceID, - Subjects: resourceToSubject(rb.Subjects), - Role: roleBindingResponseRole{ - ID: rb.Role.ID, - Name: rb.Role.Name, - }, + SubjectIDs: rb.SubjectIDs, + RoleID: rb.RoleID, CreatedBy: rb.CreatedBy, UpdatedBy: rb.UpdatedBy, @@ -167,7 +148,7 @@ func (r *Router) roleBindingsList(c echo.Context) error { return c.JSON(http.StatusOK, resp) } -func (r *Router) roleBindingsDelete(c echo.Context) error { +func (r *Router) roleBindingDelete(c echo.Context) error { rbID := c.Param("rb_id") ctx, span := tracer.Start( @@ -259,11 +240,8 @@ func (r *Router) roleBindingGet(c echo.Context) error { roleBindingResponse{ ID: rb.ID, ResourceID: rb.ResourceID, - Subjects: resourceToSubject(rb.Subjects), - Role: roleBindingResponseRole{ - ID: rb.Role.ID, - Name: rb.Role.Name, - }, + SubjectIDs: rb.SubjectIDs, + RoleID: rb.RoleID, CreatedBy: rb.CreatedBy, UpdatedBy: rb.UpdatedBy, @@ -318,17 +296,16 @@ func (r *Router) roleBindingUpdate(c echo.Context) error { return r.errorResponse(err.Error(), ErrParsingRequestBody) } - subjects := make([]types.RoleBindingSubject, len(body.Subjects)) + subjects := make([]types.RoleBindingSubject, len(body.SubjectIDs)) - for i, s := range body.Subjects { - subj, err := r.engine.NewResourceFromID(s.ID) + for i, sid := range body.SubjectIDs { + subj, err := r.engine.NewResourceFromID(sid) if err != nil { return r.errorResponse("error creating subject resource", err) } subjects[i] = types.RoleBindingSubject{ SubjectResource: subj, - Condition: nil, } } @@ -342,12 +319,8 @@ func (r *Router) roleBindingUpdate(c echo.Context) error { roleBindingResponse{ ID: rb.ID, ResourceID: rb.ResourceID, - Subjects: resourceToSubject(rb.Subjects), - - Role: roleBindingResponseRole{ - ID: rb.Role.ID, - Name: rb.Role.Name, - }, + SubjectIDs: rb.SubjectIDs, + RoleID: rb.RoleID, CreatedBy: rb.CreatedBy, UpdatedBy: rb.UpdatedBy, diff --git a/internal/api/router.go b/internal/api/router.go index ceaca65c8..63ed681eb 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -89,7 +89,7 @@ func (r *Router) Routes(rg *echo.Group) { v2.GET("/resources/:id/role-bindings", r.roleBindingsList) v2.POST("/resources/:id/role-bindings", r.roleBindingCreate) v2.GET("/role-bindings/:rb_id", r.roleBindingGet) - v2.DELETE("/role-bindings/:rb_id", r.roleBindingsDelete) + v2.DELETE("/role-bindings/:rb_id", r.roleBindingDelete) v2.PATCH("/role-bindings/:rb_id", r.roleBindingUpdate) v2.GET("/actions", r.listActions) diff --git a/internal/api/types.go b/internal/api/types.go index 0bf128eb0..90330e434 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -83,30 +83,20 @@ type listRolesV2Role struct { // RoleBindings -type roleBindingResponseRole struct { - ID gidx.PrefixedID `json:"id"` - Name string `json:"name"` -} - -type roleBindingSubject struct { - ID gidx.PrefixedID `json:"id" binding:"required"` - Type string `json:"type,omitempty"` -} - type roleBindingRequest struct { - RoleID string `json:"role_id" binding:"required"` - Subjects []roleBindingSubject `json:"subjects" binding:"required"` + RoleID string `json:"role_id" binding:"required"` + SubjectIDs []gidx.PrefixedID `json:"subject_ids" binding:"required"` } type rolebindingUpdateRequest struct { - Subjects []roleBindingSubject `json:"subjects" binding:"required"` + SubjectIDs []gidx.PrefixedID `json:"subject_ids" binding:"required"` } type roleBindingResponse struct { - ID gidx.PrefixedID `json:"id"` - ResourceID gidx.PrefixedID `json:"resource_id"` - Role roleBindingResponseRole `json:"role"` - Subjects []roleBindingSubject `json:"subjects"` + ID gidx.PrefixedID `json:"id"` + ResourceID gidx.PrefixedID `json:"resource_id"` + RoleID gidx.PrefixedID `json:"role_id"` + SubjectIDs []gidx.PrefixedID `json:"subject_ids"` CreatedBy gidx.PrefixedID `json:"created_by"` UpdatedBy gidx.PrefixedID `json:"updated_by"` diff --git a/internal/query/errors.go b/internal/query/errors.go index 6f21394e4..07acb3bd7 100644 --- a/internal/query/errors.go +++ b/internal/query/errors.go @@ -44,6 +44,9 @@ var ( // in the policy ErrRoleV2ResourceNotDefined = errors.New("role v2 resource not defined") + // ErrDeleteRoleInUse represents an error when a role is in use and cannot be deleted + ErrDeleteRoleInUse = fmt.Errorf("%w: role is in use", ErrInvalidArgument) + // ErrRoleAlreadyExists represents an error when a role already exists ErrRoleAlreadyExists = fmt.Errorf("%w: role already exists", ErrInvalidArgument) @@ -53,4 +56,12 @@ var ( // ErrResourceDoesNotSupportRoleBindingV2 represents an error when a role binding // request attempts to use a resource that does not support role binding v2 ErrResourceDoesNotSupportRoleBindingV2 = fmt.Errorf("%w: resource does not support role binding v2", ErrInvalidArgument) + + // ErrCreateRoleBindingWithNoSubjects represents an error when a role + // binding is created with no subjects + ErrCreateRoleBindingWithNoSubjects = fmt.Errorf("%w: role binding must have at least one subject", ErrInvalidArgument) + + // ErrRoleBindingHasNoRelationships represents an internal error when a + // role binding has no relationships + ErrRoleBindingHasNoRelationships = errors.New("role binding has no relationships") ) diff --git a/internal/query/rolebindings.go b/internal/query/rolebindings.go index 43f47f4ce..845319ef7 100644 --- a/internal/query/rolebindings.go +++ b/internal/query/rolebindings.go @@ -19,7 +19,7 @@ import ( func (e *engine) GetRoleBinding(ctx context.Context, roleBinding types.Resource) (types.RoleBinding, error) { ctx, span := e.tracer.Start( ctx, "engine.GetRoleBinding", - trace.WithAttributes(attribute.Stringer("role_binding_id", roleBinding.ID)), + trace.WithAttributes(attribute.Stringer("rolebinding_id", roleBinding.ID)), ) defer span.End() @@ -50,7 +50,7 @@ func (e *engine) GetRoleBinding(ctx context.Context, roleBinding types.Resource) } if len(rbRel) < 1 { - err := fmt.Errorf("%w: role binding: %s", ErrRoleBindingNotFound, roleBinding.ID.String()) + err := ErrRoleBindingHasNoRelationships span.RecordError(err) span.SetStatus(codes.Error, err.Error()) @@ -58,14 +58,13 @@ func (e *engine) GetRoleBinding(ctx context.Context, roleBinding types.Resource) return types.RoleBinding{}, err } - rb.Subjects = make([]types.RoleBindingSubject, 0, len(rbRel)) - - var roleID gidx.PrefixedID + rb.SubjectIDs = make([]gidx.PrefixedID, 0, len(rbRel)) for _, rel := range rbRel { + switch { // process subject relationships - if rel.Relation == iapl.RolebindingSubjectRelation { - subjectRes, err := e.NewResourceFromIDString(rel.Subject.Object.ObjectId) + case rel.Relation == iapl.RolebindingSubjectRelation: + subjID, err := gidx.Parse(rel.Subject.Object.ObjectId) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) @@ -73,34 +72,20 @@ func (e *engine) GetRoleBinding(ctx context.Context, roleBinding types.Resource) return types.RoleBinding{}, err } - rb.Subjects = append(rb.Subjects, types.RoleBindingSubject{SubjectResource: subjectRes}) - - continue - } + rb.SubjectIDs = append(rb.SubjectIDs, subjID) // process role relationships - roleID, err = gidx.Parse(rel.Subject.Object.ObjectId) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) + default: + rb.RoleID, err = gidx.Parse(rel.Subject.Object.ObjectId) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) - return types.RoleBinding{}, err + return types.RoleBinding{}, err + } } } - dbRole, err := e.store.GetRoleByID(ctx, roleID) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - - return types.RoleBinding{}, err - } - - rb.Role = types.Role{ - ID: roleID, - Name: dbRole.Name, - } - return rb, nil } @@ -118,6 +103,14 @@ func (e *engine) CreateRoleBinding( ) defer span.End() + if len(subjects) == 0 { + err := ErrCreateRoleBindingWithNoSubjects + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + + return types.RoleBinding{}, err + } + if err := e.isRoleBindable(ctx, roleResource, resource); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) @@ -137,11 +130,6 @@ func (e *engine) CreateRoleBinding( return types.RoleBinding{}, err } - role := types.Role{ - ID: roleResource.ID, - Name: dbrole.Name, - } - dbCtx, err := e.store.BeginContext(ctx) if err != nil { span.RecordError(err) @@ -150,19 +138,7 @@ func (e *engine) CreateRoleBinding( return types.RoleBinding{}, nil } - rbResourceType, ok := e.schemaTypeMap[e.rbac.RoleBindingResource.Name] - if !ok { - err := fmt.Errorf( - "%w: invalid role-binding resource type: %s", - ErrInvalidType, e.rbac.RoleBindingResource.Name, - ) - - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - logRollbackErr(e.logger, e.store.RollbackContext(dbCtx)) - - return types.RoleBinding{}, err - } + rbResourceType := e.schemaTypeMap[e.rbac.RoleBindingResource.Name] rbid, err := gidx.NewID(rbResourceType.IDPrefix) if err != nil { @@ -182,9 +158,9 @@ func (e *engine) CreateRoleBinding( return types.RoleBinding{}, err } - rb.Role = role + rb.RoleID = dbrole.ID - roleRel := e.rolebindingRoleRelationship(role.ID.String(), rb.ID.String()) + roleRel := e.rolebindingRoleRelationship(dbrole.ID.String(), rb.ID.String()) grantRel, err := e.rolebindingGrantResourceRelationship(resource, rb.ID.String()) if err != nil { @@ -207,7 +183,7 @@ func (e *engine) CreateRoleBinding( } subjUpdates := make([]*pb.RelationshipUpdate, len(subjects)) - rb.Subjects = make([]types.RoleBindingSubject, len(subjects)) + rb.SubjectIDs = make([]gidx.PrefixedID, len(subjects)) for i, subj := range subjects { rel, err := e.rolebindingSubjectRelationship(subj.SubjectResource, rb.ID.String()) @@ -219,14 +195,16 @@ func (e *engine) CreateRoleBinding( return types.RoleBinding{}, err } - rb.Subjects[i] = subj + rb.SubjectIDs[i] = subj.SubjectResource.ID subjUpdates[i] = &pb.RelationshipUpdate{ Operation: pb.RelationshipUpdate_OPERATION_TOUCH, Relationship: rel, } } - if err := e.applyUpdates(ctx, append(updates, subjUpdates...)); err != nil { + updates = append(updates, subjUpdates...) + + if err := e.applyUpdates(ctx, updates); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) logRollbackErr(e.logger, e.store.RollbackContext(dbCtx)) @@ -250,7 +228,7 @@ func (e *engine) DeleteRoleBinding(ctx context.Context, rb types.Resource) error ctx, span := e.tracer.Start( ctx, "engine.DeleteRoleBinding", trace.WithAttributes( - attribute.Stringer("role_binding_id", rb.ID), + attribute.Stringer("rolebinding_id", rb.ID), ), ) defer span.End() @@ -418,11 +396,11 @@ func (e *engine) ListRoleBindings(ctx context.Context, resource types.Resource, continue } - if optionalRole != nil && rb.Role.ID.String() != optionalRole.ID.String() { + if optionalRole != nil && rb.RoleID != optionalRole.ID { continue } - if len(rb.Subjects) == 0 { + if len(rb.SubjectIDs) == 0 { continue } @@ -476,15 +454,17 @@ func (e *engine) UpdateRoleBinding(ctx context.Context, actor, rb types.Resource } // 1. find the subjects to add or remove - current := make([]string, len(rolebinding.Subjects)) + current := make([]string, len(rolebinding.SubjectIDs)) incoming := make([]string, len(subjects)) + newSubjectIDs := make([]gidx.PrefixedID, len(subjects)) - for i, subj := range rolebinding.Subjects { - current[i] = subj.SubjectResource.ID.String() + for i, subj := range rolebinding.SubjectIDs { + current[i] = subj.String() } for i, subj := range subjects { incoming[i] = subj.SubjectResource.ID.String() + newSubjectIDs[i] = subj.SubjectResource.ID } add, remove := diff(current, incoming) @@ -495,8 +475,7 @@ func (e *engine) UpdateRoleBinding(ctx context.Context, actor, rb types.Resource } // 2. create relationship updates - updates := make([]*pb.RelationshipUpdate, len(add)+len(remove)) - i := 0 + updates := make([]*pb.RelationshipUpdate, 0, len(add)+len(remove)) for _, id := range add { update, err := e.rolebindingRelationshipUpdateForSubject(id, rb.ID.String(), pb.RelationshipUpdate_OPERATION_TOUCH) @@ -508,8 +487,7 @@ func (e *engine) UpdateRoleBinding(ctx context.Context, actor, rb types.Resource return types.RoleBinding{}, err } - updates[i] = update - i++ + updates = append(updates, update) } for _, id := range remove { @@ -522,8 +500,7 @@ func (e *engine) UpdateRoleBinding(ctx context.Context, actor, rb types.Resource return types.RoleBinding{}, err } - updates[i] = update - i++ + updates = append(updates, update) } if err := e.applyUpdates(ctx, updates); err != nil { @@ -552,7 +529,7 @@ func (e *engine) UpdateRoleBinding(ctx context.Context, actor, rb types.Resource return types.RoleBinding{}, err } - rolebinding.Subjects = subjects + rolebinding.SubjectIDs = newSubjectIDs rolebinding.UpdatedAt = rbFromDB.UpdatedAt rolebinding.UpdatedBy = rbFromDB.UpdatedBy @@ -605,152 +582,6 @@ func (e *engine) isRoleBindable(ctx context.Context, role, res types.Resource) e } } -// deleteRoleBindingsForRole deletes all role-binding relationships with a given role. -func (e *engine) deleteRoleBindingsForRole(ctx context.Context, roleResource types.Resource) error { - ctx, span := e.tracer.Start( - ctx, "engine.deleteRoleBindingsForRole", - trace.WithAttributes( - attribute.Stringer("role_id", roleResource.ID), - ), - ) - defer span.End() - - // 1. find all the bindings for the role - findBindingsFilter := &pb.RelationshipFilter{ - ResourceType: e.namespaced(e.rbac.RoleBindingResource.Name), - OptionalRelation: iapl.RolebindingRoleRelation, - OptionalSubjectFilter: &pb.SubjectFilter{ - SubjectType: e.namespaced(e.rbac.RoleResource.Name), - OptionalSubjectId: roleResource.ID.String(), - }, - } - - bindings, err := e.readRelationships(ctx, findBindingsFilter) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - - return err - } - - if len(bindings) == 0 { - return nil - } - - rbIDs := make([]gidx.PrefixedID, len(bindings)) - - for i, rel := range bindings { - id, err := gidx.Parse(rel.Resource.ObjectId) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - - return err - } - - rbIDs[i] = id - } - - dbCtx, err := e.store.BeginContext(ctx) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - - return err - } - - if err := e.store.BatchLockRoleBindingForUpdate(dbCtx, rbIDs); err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - logRollbackErr(e.logger, e.store.RollbackContext(dbCtx)) - - return err - } - - // 2. Gather all the relationships to be deleted - - // 2.1 build a list of requests to get all the subject, role and grant - // relationships for all bindable resources - relFilters := []*pb.RelationshipFilter{} - - for _, rb := range bindings { - relFilters = append(relFilters, &pb.RelationshipFilter{ - ResourceType: rb.Resource.ObjectType, - OptionalResourceId: rb.Resource.ObjectId, - }, - ) - - for _, res := range e.rbacV2ResourceTypes { - relFilters = append(relFilters, &pb.RelationshipFilter{ - ResourceType: e.namespaced(res.Name), - OptionalRelation: iapl.GrantRelationship, - OptionalSubjectFilter: &pb.SubjectFilter{ - SubjectType: rb.Resource.ObjectType, - OptionalSubjectId: rb.Resource.ObjectId, - }, - }, - ) - } - } - - // 2.2 read all the relationships - rels := []*pb.Relationship{} - - for _, filter := range relFilters { - r, err := e.readRelationships(ctx, filter) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - logRollbackErr(e.logger, e.store.RollbackContext(dbCtx)) - - return err - } - - rels = append(rels, r...) - } - - // 2.3 create delete requests - updates := make([]*pb.RelationshipUpdate, len(rels)) - - for i, rel := range rels { - updates[i] = &pb.RelationshipUpdate{ - Operation: pb.RelationshipUpdate_OPERATION_DELETE, - Relationship: rel, - } - } - - e.logger.Debugf("%d relationships will be deleted", len(updates)) - - // 3.1 delete all records in permissions-api DB - if err := e.store.BatchDeleteRoleBindings(dbCtx, rbIDs); err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - logRollbackErr(e.logger, e.store.RollbackContext(dbCtx)) - - return err - } - - // 3.2 delete all the relationships - if err := e.applyUpdates(ctx, updates); err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - logRollbackErr(e.logger, e.store.RollbackContext(dbCtx)) - - return err - } - - if err := e.store.CommitContext(dbCtx); err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - logRollbackErr(e.logger, e.store.RollbackContext(dbCtx)) - logRollbackErr(e.logger, e.rollbackUpdates(ctx, updates)) - - return err - } - - return nil -} - // rolebindingSubjectRelationship is a helper function that creates a // relationship between a role-binding and a subject. func (e *engine) rolebindingSubjectRelationship(subj types.Resource, rbID string) (*pb.Relationship, error) { diff --git a/internal/query/rolebindings_test.go b/internal/query/rolebindings_test.go index 33137c6a7..d0f294b99 100644 --- a/internal/query/rolebindings_test.go +++ b/internal/query/rolebindings_test.go @@ -102,8 +102,8 @@ func TestCreateRoleBinding(t *testing.T) { }, CheckFn: func(ctx context.Context, t *testing.T, res testingx.TestResult[types.RoleBinding]) { assert.NoError(t, res.Err) - assert.Equal(t, role.ID, res.Success.Role.ID) - assert.Len(t, res.Success.Subjects, 1) + assert.Equal(t, role.ID, res.Success.RoleID) + assert.Len(t, res.Success.SubjectIDs, 1) rb, err := e.ListRoleBindings(ctx, child, nil) assert.NoError(t, err) @@ -121,6 +121,17 @@ func TestCreateRoleBinding(t *testing.T) { assert.ErrorIs(t, res.Err, ErrRoleNotFound) }, }, + { + Name: "CreateRoleBindingWithNoSubjects", + Input: input{ + resource: root, + role: roleRes, + }, + CheckFn: func(ctx context.Context, t *testing.T, tr testingx.TestResult[types.RoleBinding]) { + assert.ErrorIs(t, tr.Err, ErrInvalidArgument) + assert.ErrorIs(t, tr.Err, ErrCreateRoleBindingWithNoSubjects) + }, + }, { Name: "CreateRoleBindingSuccess", Input: input{ @@ -131,10 +142,10 @@ func TestCreateRoleBinding(t *testing.T) { CheckFn: func(ctx context.Context, t *testing.T, res testingx.TestResult[types.RoleBinding]) { assert.NoError(t, res.Err) - assert.Len(t, res.Success.Subjects, 1) - assert.Equal(t, role.ID, res.Success.Role.ID) + assert.Len(t, res.Success.SubjectIDs, 1) + assert.Equal(t, role.ID, res.Success.RoleID) assert.Equal(t, root.ID, res.Success.ResourceID) - assert.Equal(t, subj.ID, res.Success.Subjects[0].SubjectResource.ID) + assert.Equal(t, subj.ID, res.Success.SubjectIDs[0]) assert.Equal(t, actor.ID, res.Success.CreatedBy) rbs, err := e.ListRoleBindings(ctx, root, nil) @@ -215,7 +226,7 @@ func TestListRoleBindings(t *testing.T) { }, CheckFn: func(ctx context.Context, t *testing.T, res testingx.TestResult[[]types.RoleBinding]) { assert.Len(t, res.Success, 1) - assert.Equal(t, viewer.ID, res.Success[0].Role.ID) + assert.Equal(t, viewer.ID, res.Success[0].RoleID) }, }, { @@ -226,7 +237,7 @@ func TestListRoleBindings(t *testing.T) { }, CheckFn: func(ctx context.Context, t *testing.T, res testingx.TestResult[[]types.RoleBinding]) { assert.Len(t, res.Success, 1) - assert.Equal(t, editor.ID, res.Success[0].Role.ID) + assert.Equal(t, editor.ID, res.Success[0].RoleID) }, }, { @@ -291,9 +302,9 @@ func TestGetRoleBinding(t *testing.T) { Input: rbRes, CheckFn: func(ctx context.Context, t *testing.T, res testingx.TestResult[types.RoleBinding]) { assert.NoError(t, res.Err) - assert.Equal(t, viewer.ID, res.Success.Role.ID) - assert.Len(t, res.Success.Subjects, 1) - assert.Equal(t, subj.ID, res.Success.Subjects[0].SubjectResource.ID) + assert.Equal(t, viewer.ID, res.Success.RoleID) + assert.Len(t, res.Success.SubjectIDs, 1) + assert.Equal(t, subj.ID, res.Success.SubjectIDs[0]) assert.Equal(t, actor.ID, res.Success.CreatedBy) assert.Equal(t, root.ID, res.Success.ResourceID) }, @@ -383,10 +394,10 @@ func TestUpdateRoleBinding(t *testing.T) { CheckFn: func(ctx context.Context, t *testing.T, res testingx.TestResult[types.RoleBinding]) { assert.NoError(t, res.Err) - assert.Len(t, res.Success.Subjects, 2) - assert.Contains(t, res.Success.Subjects, types.RoleBindingSubject{SubjectResource: user1}) - assert.Contains(t, res.Success.Subjects, types.RoleBindingSubject{SubjectResource: group1}) - assert.NotContains(t, res.Success.Subjects, types.RoleBindingSubject{SubjectResource: subj}) + assert.Len(t, res.Success.SubjectIDs, 2) + assert.Contains(t, res.Success.SubjectIDs, user1.ID) + assert.Contains(t, res.Success.SubjectIDs, group1.ID) + assert.NotContains(t, res.Success.SubjectIDs, subj.ID) assert.Equal(t, actor.ID, res.Success.UpdatedBy) assert.Equal(t, root.ID, res.Success.ResourceID) @@ -494,6 +505,9 @@ func TestPermissions(t *testing.T) { group1, err := e.NewResourceFromIDString("idntgrp-group1") require.NoError(t, err) + // rolebinding + var rb types.RoleBinding + err = e.CreateRelationships(ctx, []types.Relationship{{ Resource: group1, Relation: "member", @@ -633,7 +647,7 @@ func TestPermissions(t *testing.T) { }) require.Error(t, err) - _, err = e.CreateRoleBinding(ctx, user1, root, viewerRes, []types.RoleBindingSubject{{SubjectResource: group1}}) + rb, err = e.CreateRoleBinding(ctx, user1, root, viewerRes, []types.RoleBindingSubject{{SubjectResource: group1}}) require.NoError(t, err) return ctx @@ -719,7 +733,7 @@ func TestPermissions(t *testing.T) { Sync: true, }, { - Name: "RoleRemoval", + Name: "DeleteRoleBinding", SetupFn: func(ctx context.Context, t *testing.T) context.Context { err := e.checkPermission(ctx, &pb.CheckPermissionRequest{ Consistency: fullconsistency, @@ -729,7 +743,9 @@ func TestPermissions(t *testing.T) { }) require.NoError(t, err) - err = e.DeleteRoleV2(ctx, viewerRes) + rbRes, err := e.NewResourceFromID(rb.ID) + require.NoError(t, err) + err = e.DeleteRoleBinding(ctx, rbRes) require.NoError(t, err) return ctx diff --git a/internal/query/roles_v2.go b/internal/query/roles_v2.go index 83211db8f..78a58be86 100644 --- a/internal/query/roles_v2.go +++ b/internal/query/roles_v2.go @@ -352,6 +352,33 @@ func (e *engine) DeleteRoleV2(ctx context.Context, roleResource types.Resource) ctx, span := e.tracer.Start(ctx, "engine.DeleteRoleV2") defer span.End() + // find all the bindings for the role + findBindingsFilter := &pb.RelationshipFilter{ + ResourceType: e.namespaced(e.rbac.RoleBindingResource.Name), + OptionalRelation: iapl.RolebindingRoleRelation, + OptionalSubjectFilter: &pb.SubjectFilter{ + SubjectType: e.namespaced(e.rbac.RoleResource.Name), + OptionalSubjectId: roleResource.ID.String(), + }, + } + + bindings, err := e.readRelationships(ctx, findBindingsFilter) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + + return err + } + + // reject delete if role is in use + if len(bindings) > 0 { + err := fmt.Errorf("%w: cannot delete role", ErrDeleteRoleInUse) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + + return err + } + dbCtx, err := e.store.BeginContext(ctx) if err != nil { span.RecordError(err) @@ -430,11 +457,6 @@ func (e *engine) DeleteRoleV2(ctx context.Context, roleResource types.Resource) errs = append(errs, err) } - // 2.c remove all role relationships in role bindings associated with this role - if err := e.deleteRoleBindingsForRole(ctx, roleResource); err != nil { - errs = append(errs, err) - } - for _, err := range errs { if err != nil { span.RecordError(err) diff --git a/internal/query/roles_v2_test.go b/internal/query/roles_v2_test.go index f09eb0220..1aee76e6b 100644 --- a/internal/query/roles_v2_test.go +++ b/internal/query/roles_v2_test.go @@ -434,13 +434,13 @@ func TestDeleteRolesV2(t *testing.T) { require.NoError(t, err) // these bindings are expected to be deleted after the role is deleted - _, err = e.CreateRoleBinding(ctx, actor, root, roleRes, []types.RoleBindingSubject{{SubjectResource: subj}}) + rbRoot, err := e.CreateRoleBinding(ctx, actor, root, roleRes, []types.RoleBindingSubject{{SubjectResource: subj}}) require.NoError(t, err) - _, err = e.CreateRoleBinding(ctx, actor, child, roleRes, []types.RoleBindingSubject{{SubjectResource: subj}}) + rbChild, err := e.CreateRoleBinding(ctx, actor, child, roleRes, []types.RoleBindingSubject{{SubjectResource: subj}}) require.NoError(t, err) - _, err = e.CreateRoleBinding(ctx, actor, theotherchild, roleRes, []types.RoleBindingSubject{{SubjectResource: subj}}) + rbTheOtherChild, err := e.CreateRoleBinding(ctx, actor, theotherchild, roleRes, []types.RoleBindingSubject{{SubjectResource: subj}}) require.NoError(t, err) rb, err := e.ListRoleBindings(ctx, root, &roleRes) @@ -460,7 +460,7 @@ func TestDeleteRolesV2(t *testing.T) { Name: "DeleteRoleNotFound", Input: notfoundRes, CheckFn: func(ctx context.Context, t *testing.T, res testingx.TestResult[types.Role]) { - assert.ErrorContains(t, res.Err, ErrRoleNotFound.Error()) + assert.ErrorIs(t, res.Err, storage.ErrNoRoleFound) }, Sync: true, }, @@ -472,26 +472,45 @@ func TestDeleteRolesV2(t *testing.T) { }, }, { - Name: "DeleteRoleSuccess", + Name: "DeleteRoleWithExistingBindings", Input: roleRes, CheckFn: func(ctx context.Context, t *testing.T, res testingx.TestResult[types.Role]) { - assert.NoError(t, res.Err) - - _, err := e.GetRoleV2(ctx, roleRes) - assert.ErrorContains(t, err, ErrRoleNotFound.Error()) - - // make sure the role bindings are also deleted - rb, err := e.ListRoleBindings(ctx, root, &roleRes) + assert.ErrorIs(t, res.Err, ErrDeleteRoleInUse) + }, + Sync: true, + }, + { + Name: "DeleteRoleSuccess", + Input: roleRes, + SetupFn: func(ctx context.Context, t *testing.T) context.Context { + var ( + err error + rb types.Resource + ) + + // delete the role bindings first + rb, err = e.NewResourceFromID(rbRoot.ID) + require.NoError(t, err) + err = e.DeleteRoleBinding(ctx, rb) + require.NoError(t, err) + + rb, err = e.NewResourceFromID(rbChild.ID) + require.NoError(t, err) + err = e.DeleteRoleBinding(ctx, rb) assert.NoError(t, err) - assert.Len(t, rb, 0) - rb, err = e.ListRoleBindings(ctx, child, &roleRes) + rb, err = e.NewResourceFromID(rbTheOtherChild.ID) + require.NoError(t, err) + err = e.DeleteRoleBinding(ctx, rb) assert.NoError(t, err) - assert.Len(t, rb, 0) - rb, err = e.ListRoleBindings(ctx, theotherchild, &roleRes) - assert.NoError(t, err) - assert.Len(t, rb, 0) + return ctx + }, + CheckFn: func(ctx context.Context, t *testing.T, res testingx.TestResult[types.Role]) { + assert.NoError(t, res.Err) + + _, err := e.GetRoleV2(ctx, roleRes) + assert.ErrorIs(t, err, storage.ErrNoRoleFound) }, Sync: true, }, diff --git a/internal/storage/rolebinding.go b/internal/storage/rolebinding.go index 906464ce3..1fbe5dc60 100644 --- a/internal/storage/rolebinding.go +++ b/internal/storage/rolebinding.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "time" "go.infratographer.com/permissions-api/internal/types" @@ -40,17 +41,9 @@ type RoleBindingService interface { // CommitContext or RollbackContext must be called afterwards if this method returns no error. DeleteRoleBinding(ctx context.Context, id gidx.PrefixedID) error - // BatchDeleteRoleBinding deletes multiple role bindings from the database - // This method must be called with a context returned from BeginContext. - // CommitContext or RollbackContext must be called afterwards if this method returns no error. - BatchDeleteRoleBindings(ctx context.Context, ids []gidx.PrefixedID) error - // LockRoleBindingForUpdate locks a role binding record to be updated to ensure consistency. // If the role binding is not found, an ErrRoleBindingNotFound error is returned. LockRoleBindingForUpdate(ctx context.Context, id gidx.PrefixedID) error - - // BatchLockRoleBindingForUpdate locks multiple role binding records to be updated to ensure consistency. - BatchLockRoleBindingForUpdate(ctx context.Context, ids []gidx.PrefixedID) error } func (e *engine) GetRoleBindingByID(ctx context.Context, id gidx.PrefixedID) (types.RoleBinding, error) { @@ -133,9 +126,9 @@ func (e *engine) CreateRoleBinding(ctx context.Context, actorID, rbID, resourceI err = tx.QueryRowContext(ctx, ` INSERT INTO rolebindings (id, resource_id, created_by, updated_by, created_at, updated_at) - VALUES ($1, $2, $3, $3, now(), now()) + VALUES ($1, $2, $3, $3, $4, $4) RETURNING id, resource_id, created_by, updated_by, created_at, updated_at - `, rbID.String(), resourceID.String(), actorID.String(), + `, rbID.String(), resourceID.String(), actorID.String(), time.Now(), ).Scan( &rb.ID, &rb.ResourceID, @@ -211,23 +204,6 @@ func (e *engine) DeleteRoleBinding(ctx context.Context, id gidx.PrefixedID) erro return nil } -func (e *engine) BatchDeleteRoleBindings(ctx context.Context, ids []gidx.PrefixedID) error { - tx, err := getContextTx(ctx) - if err != nil { - return err - } - - inClause, args := e.buildBatchInClauseWithIDs(ids) - q := fmt.Sprintf("DELETE FROM rolebindings WHERE id IN (%s)", inClause) - - _, err = tx.ExecContext(ctx, q, args...) - if err != nil { - return err - } - - return nil -} - func (e *engine) LockRoleBindingForUpdate(ctx context.Context, id gidx.PrefixedID) error { db, err := getContextDBQuery(ctx, e) if err != nil { @@ -251,32 +227,6 @@ func (e *engine) LockRoleBindingForUpdate(ctx context.Context, id gidx.PrefixedI return nil } -func (e *engine) BatchLockRoleBindingForUpdate(ctx context.Context, ids []gidx.PrefixedID) error { - db, err := getContextDBQuery(ctx, e) - if err != nil { - return err - } - - inClause, args := e.buildBatchInClauseWithIDs(ids) - q := fmt.Sprintf("SELECT 1 FROM rolebindings WHERE id IN (%s) FOR UPDATE", inClause) - - result, err := db.ExecContext(ctx, q, args...) - if err != nil { - return err - } - - rowsAffected, err := result.RowsAffected() - if err != nil { - return err - } - - if int(rowsAffected) != len(ids) { - return fmt.Errorf("%w: %d of role-bindings not found", ErrRoleBindingNotFound, len(ids)-int(rowsAffected)) - } - - return nil -} - // buildBatchInClauseWithIDs is a helper function that builds an IN clause for // a batch query with the provided prefixed IDs. func (e *engine) buildBatchInClauseWithIDs(ids []gidx.PrefixedID) (clause string, args []any) { diff --git a/internal/types/types.go b/internal/types/types.go index b22ca92ee..ff7d0509a 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -81,14 +81,9 @@ type Resource struct { ID gidx.PrefixedID } -// RoleBindingSubjectCondition is the object that represents the condition of a -// role binding subject. -type RoleBindingSubjectCondition struct{} - // RoleBindingSubject is the object that represents the subject of a role binding. type RoleBindingSubject struct { SubjectResource Resource - Condition *RoleBindingSubjectCondition } // Relationship represents a named association between a resource and a subject. @@ -102,8 +97,8 @@ type Relationship struct { type RoleBinding struct { ID gidx.PrefixedID ResourceID gidx.PrefixedID - Role Role - Subjects []RoleBindingSubject + RoleID gidx.PrefixedID + SubjectIDs []gidx.PrefixedID CreatedBy gidx.PrefixedID UpdatedBy gidx.PrefixedID diff --git a/openapi-v2.yaml b/openapi-v2.yaml index 69e22ea82..46027e385 100644 --- a/openapi-v2.yaml +++ b/openapi-v2.yaml @@ -498,7 +498,33 @@ paths: operationId: deleteRole responses: "200": - description: "" + description: delete-role + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + examples: + delete-role: + value: + success: true + "400": + description: delete-role-with-existing-bindings + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: 'error deleting role: invalid argument: role is in use: cannot delete role' + examples: + delete-role-with-existing-bindings: + value: + message: 'error deleting role: invalid argument: role is in use: cannot delete role' patch: tags: - roles @@ -662,7 +688,7 @@ paths: required: true schema: type: string - example: permrv2-zNYOpdL1RGyiSp0hgaIYB + example: permrv2-l9XgxtA6EUkwNSCTGlmgF /resources/{id}/role-bindings: get: tags: @@ -691,92 +717,108 @@ paths: items: type: object properties: + created_at: + type: string + example: "2024-05-03T19:35:23Z" + created_by: + type: string + example: idntusr-bailin id: type: string - example: permrbn-r7WU4juT0p-76JW-sJwzQ - role: - type: object - properties: - id: - type: string - example: permrv2-nDw3bVXYwHysvZDFyxh2C - name: - type: string - example: super_user - subjects: + example: permrbn-IYH19GIbDGZ9n2xR0yHvW + resource_id: + type: string + example: tnntten-root + role_id: + type: string + example: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: type: array items: - type: object - properties: - id: - type: string - example: idntusr-bailin - type: - type: string - example: user + type: string + example: idntusr-bailin example: - - id: idntusr-bailin - type: user + - idntusr-bailin + updated_at: + type: string + example: "2024-05-03T19:35:23Z" + updated_by: + type: string + example: idntusr-bailin example: - - id: permrbn-r7WU4juT0p-76JW-sJwzQ - role: - id: permrv2-nDw3bVXYwHysvZDFyxh2C - name: super_user - subjects: - - id: idntusr-bailin - type: user - - id: permrbn-wNpgDB-ajBTyR6nB-WBcY - role: - id: permrv2-nDw3bVXYwHysvZDFyxh2C - name: super_user - subjects: - - id: idntusr-bailin - type: user - - id: idntusr-bailin-1 - type: user - - id: idntusr-bailin-2 - type: user - - id: idntusr-bailin-3 - type: user - - id: idntusr-bailin-4 - type: user - - id: idntusr-bailin-5 - type: user + - created_at: "2024-05-03T19:35:23Z" + created_by: idntusr-bailin + id: permrbn-IYH19GIbDGZ9n2xR0yHvW + resource_id: tnntten-root + role_id: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: + - idntusr-bailin + updated_at: "2024-05-03T19:35:23Z" + updated_by: idntusr-bailin + - created_at: "2024-05-06T16:04:08Z" + created_by: idntusr-bailin + id: permrbn-K652x2XPJO1mGJFUE4hEu + resource_id: tnntten-root + role_id: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: + - idntgrp-my-subgroup + updated_at: "2024-05-06T16:04:08Z" + updated_by: idntusr-bailin + - created_at: "2024-05-06T16:00:46Z" + created_by: idntusr-bailin + id: permrbn-lr5s4g6g1shmDL_htEAR_ + resource_id: tnntten-root + role_id: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: + - idntusr-bailin + - idntusr-bailin-1 + - idntusr-bailin-2 + - idntusr-bailin-3 + - idntusr-bailin-4 + - idntusr-bailin-5 + updated_at: "2024-05-06T16:00:46Z" + updated_by: idntusr-bailin examples: list-role-bindings: value: data: - - id: permrbn-r7WU4juT0p-76JW-sJwzQ - role: - id: permrv2-nDw3bVXYwHysvZDFyxh2C - name: super_user - subjects: - - id: idntusr-bailin - type: user - - id: permrbn-wNpgDB-ajBTyR6nB-WBcY - role: - id: permrv2-nDw3bVXYwHysvZDFyxh2C - name: super_user - subjects: - - id: idntusr-bailin - type: user - - id: idntusr-bailin-1 - type: user - - id: idntusr-bailin-2 - type: user - - id: idntusr-bailin-3 - type: user - - id: idntusr-bailin-4 - type: user - - id: idntusr-bailin-5 - type: user + - created_at: "2024-05-03T19:35:23Z" + created_by: idntusr-bailin + id: permrbn-IYH19GIbDGZ9n2xR0yHvW + resource_id: tnntten-root + role_id: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: + - idntusr-bailin + updated_at: "2024-05-03T19:35:23Z" + updated_by: idntusr-bailin + - created_at: "2024-05-06T16:04:08Z" + created_by: idntusr-bailin + id: permrbn-K652x2XPJO1mGJFUE4hEu + resource_id: tnntten-root + role_id: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: + - idntgrp-my-subgroup + updated_at: "2024-05-06T16:04:08Z" + updated_by: idntusr-bailin + - created_at: "2024-05-06T16:00:46Z" + created_by: idntusr-bailin + id: permrbn-lr5s4g6g1shmDL_htEAR_ + resource_id: tnntten-root + role_id: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: + - idntusr-bailin + - idntusr-bailin-1 + - idntusr-bailin-2 + - idntusr-bailin-3 + - idntusr-bailin-4 + - idntusr-bailin-5 + updated_at: "2024-05-06T16:00:46Z" + updated_by: idntusr-bailin post: tags: - role-bindings summary: create-role-binding - description: | - create a role-binding for a resource. The role-binding will grant the - specified role to the specified subjects. + description: create-role-binding operationId: createRoleBinding requestBody: content: @@ -786,28 +828,20 @@ paths: properties: role_id: type: string - example: permrv2-hoN9DD27g1tfKzjF8kj42 - subjects: + example: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: type: array items: - type: object - properties: - conditions: - type: object - properties: {} - id: - type: string - example: idntgrp-my-subgroup + type: string + example: idntgrp-my-subgroup example: - - conditions: {} - id: idntgrp-my-subgroup + - idntgrp-my-subgroup examples: create-role-binding: value: - role_id: permrv2-hoN9DD27g1tfKzjF8kj42 - subjects: - - conditions: {} - id: idntgrp-my-subgroup + role_id: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: + - idntgrp-my-subgroup responses: "200": description: root-super-user / create-role-binding-group @@ -816,71 +850,81 @@ paths: schema: type: object properties: + created_at: + type: string + example: "2024-05-06T16:00:46Z" + created_by: + type: string + example: idntusr-bailin id: type: string - example: permrbn-wNpgDB-ajBTyR6nB-WBcY - role: - type: object - properties: - id: - type: string - example: permrv2-nDw3bVXYwHysvZDFyxh2C - name: - type: string - example: super_user - subjects: + example: permrbn-lr5s4g6g1shmDL_htEAR_ + resource_id: + type: string + example: tnntten-root + role_id: + type: string + example: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: type: array items: - type: object - properties: - id: - type: string - example: idntusr-bailin - type: - type: string - example: user + type: string + example: idntusr-bailin example: - - id: idntusr-bailin - type: user - - id: idntusr-bailin-1 - type: user - - id: idntusr-bailin-2 - type: user - - id: idntusr-bailin-3 - type: user - - id: idntusr-bailin-4 - type: user - - id: idntusr-bailin-5 - type: user + - idntusr-bailin + - idntusr-bailin-1 + - idntusr-bailin-2 + - idntusr-bailin-3 + - idntusr-bailin-4 + - idntusr-bailin-5 + updated_at: + type: string + example: "2024-05-06T16:00:46Z" + updated_by: + type: string + example: idntusr-bailin examples: create-role-binding-group: value: - id: permrbn-96Cy2kTlt3jtZwjEF9gaO - role: - id: permrv2-hoN9DD27g1tfKzjF8kj42 - name: my_proj_doc_viewer - subjects: - - id: idntgrp-my-subgroup - type: group + created_at: "2024-05-06T16:04:08Z" + created_by: idntusr-bailin + id: permrbn-K652x2XPJO1mGJFUE4hEu + resource_id: tnntten-root + role_id: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: + - idntgrp-my-subgroup + updated_at: "2024-05-06T16:04:08Z" + updated_by: idntusr-bailin root-super-user: value: - id: permrbn-wNpgDB-ajBTyR6nB-WBcY - role: - id: permrv2-nDw3bVXYwHysvZDFyxh2C - name: super_user - subjects: - - id: idntusr-bailin - type: user - - id: idntusr-bailin-1 - type: user - - id: idntusr-bailin-2 - type: user - - id: idntusr-bailin-3 - type: user - - id: idntusr-bailin-4 - type: user - - id: idntusr-bailin-5 - type: user + created_at: "2024-05-06T16:00:46Z" + created_by: idntusr-bailin + id: permrbn-lr5s4g6g1shmDL_htEAR_ + resource_id: tnntten-root + role_id: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: + - idntusr-bailin + - idntusr-bailin-1 + - idntusr-bailin-2 + - idntusr-bailin-3 + - idntusr-bailin-4 + - idntusr-bailin-5 + updated_at: "2024-05-06T16:00:46Z" + updated_by: idntusr-bailin + "400": + description: create-role-binding-empty-subject + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: 'error creating role-binding: invalid argument: role binding must have at least one subject' + examples: + create-role-binding-empty-subject: + value: + message: 'error creating role-binding: invalid argument: role binding must have at least one subject' parameters: - name: id in: path @@ -903,58 +947,56 @@ paths: schema: type: object properties: + created_at: + type: string + example: "2024-05-06T16:00:46Z" + created_by: + type: string + example: idntusr-bailin id: type: string - example: permrbn-pQByAAcMGUfS-ZvXPlrxp - role: - type: object - properties: - id: - type: string - example: permrv2-pUuZKw25TbCjoPnOYkA4w - name: - type: string - example: super-admin - subjects: + example: permrbn-lr5s4g6g1shmDL_htEAR_ + resource_id: + type: string + example: tnntten-root + role_id: + type: string + example: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: type: array items: - type: object - properties: - id: - type: string - example: idntclt-xT33dia6DUHdcDXsGRUes - type: - type: string - example: client + type: string + example: idntusr-bailin example: - - id: idntclt-xT33dia6DUHdcDXsGRUes - type: client - - id: idntgrp-root-admins - type: group - - id: idntusr-DUrM80Cg2VzLVEhpmQb7ovmW1EG1isBnmIFpLTA5N2k - type: user - - id: idntusr-FYfXJww0qhW1XrF-WKupOQCQ9Q84d-ieEamjxz1Hzhs - type: user - - id: idntusr-bailin - type: user + - idntusr-bailin + - idntusr-bailin-1 + - idntusr-bailin-2 + - idntusr-bailin-3 + - idntusr-bailin-4 + - idntusr-bailin-5 + updated_at: + type: string + example: "2024-05-06T16:00:46Z" + updated_by: + type: string + example: idntusr-bailin examples: get-role-binding: value: - id: permrbn-pQByAAcMGUfS-ZvXPlrxp - role: - id: permrv2-pUuZKw25TbCjoPnOYkA4w - name: super-admin - subjects: - - id: idntclt-xT33dia6DUHdcDXsGRUes - type: client - - id: idntgrp-root-admins - type: group - - id: idntusr-DUrM80Cg2VzLVEhpmQb7ovmW1EG1isBnmIFpLTA5N2k - type: user - - id: idntusr-FYfXJww0qhW1XrF-WKupOQCQ9Q84d-ieEamjxz1Hzhs - type: user - - id: idntusr-bailin - type: user + created_at: "2024-05-06T16:00:46Z" + created_by: idntusr-bailin + id: permrbn-lr5s4g6g1shmDL_htEAR_ + resource_id: tnntten-root + role_id: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: + - idntusr-bailin + - idntusr-bailin-1 + - idntusr-bailin-2 + - idntusr-bailin-3 + - idntusr-bailin-4 + - idntusr-bailin-5 + updated_at: "2024-05-06T16:00:46Z" + updated_by: idntusr-bailin delete: tags: - role-bindings @@ -972,118 +1014,95 @@ paths: update a role-binding, this will replace the subjects with the new subjects. note that role_id is immutable operationId: updateRoleBinding - parameters: - - name: id - in: query - schema: - type: string - example: loadbal-test-1 requestBody: content: application/json: schema: type: object properties: - subjects: + subject_ids: type: array items: - type: object - properties: - conditions: - type: object - properties: {} - id: - type: string - example: idntclt-xT33dia6DUHdcDXsGRUes + type: string + example: idntclt-xT33dia6DUHdcDXsGRUes example: - - id: idntclt-xT33dia6DUHdcDXsGRUes - - id: idntusr-DUrM80Cg2VzLVEhpmQb7ovmW1EG1isBnmIFpLTA5N2k - - id: idntusr-FYfXJww0qhW1XrF-WKupOQCQ9Q84d-ieEamjxz1Hzhs - - id: idntgrp-root-admins + - idntclt-xT33dia6DUHdcDXsGRUes + - idntusr-DUrM80Cg2VzLVEhpmQb7ovmW1EG1isBnmIFpLTA5N2k + - idntusr-FYfXJww0qhW1XrF-WKupOQCQ9Q84d-ieEamjxz1Hzhs + - idntgrp-root-admins + - idntusr-bailin examples: update-role-binding: value: - subjects: - - id: idntclt-xT33dia6DUHdcDXsGRUes - - id: idntusr-DUrM80Cg2VzLVEhpmQb7ovmW1EG1isBnmIFpLTA5N2k - - id: idntusr-FYfXJww0qhW1XrF-WKupOQCQ9Q84d-ieEamjxz1Hzhs - - id: idntgrp-root-admins + subject_ids: + - idntclt-xT33dia6DUHdcDXsGRUes + - idntusr-DUrM80Cg2VzLVEhpmQb7ovmW1EG1isBnmIFpLTA5N2k + - idntusr-FYfXJww0qhW1XrF-WKupOQCQ9Q84d-ieEamjxz1Hzhs + - idntgrp-root-admins + - idntusr-bailin responses: "200": description: update-role-binding - headers: - Content-Length: - schema: - type: string - example: "368" - Date: - schema: - type: string - example: Mon, 22 Apr 2024 21:52:22 GMT - X-Request-Id: - schema: - type: string - example: NzmFDigPmfVANuPeLMlMFtpykGKhGwvj content: application/json: schema: type: object properties: + created_at: + type: string + example: "2024-05-06T16:00:46Z" + created_by: + type: string + example: idntusr-bailin id: type: string - example: permrbn-pQByAAcMGUfS-ZvXPlrxp - role: - type: object - properties: - id: - type: string - example: permrv2-pUuZKw25TbCjoPnOYkA4w - name: - type: string - example: super-admin - subjects: + example: permrbn-lr5s4g6g1shmDL_htEAR_ + resource_id: + type: string + example: tnntten-root + role_id: + type: string + example: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: type: array items: - type: object - properties: - id: - type: string - example: idntclt-xT33dia6DUHdcDXsGRUes - type: - type: string - example: client + type: string + example: idntclt-xT33dia6DUHdcDXsGRUes example: - - id: idntclt-xT33dia6DUHdcDXsGRUes - type: client - - id: idntusr-DUrM80Cg2VzLVEhpmQb7ovmW1EG1isBnmIFpLTA5N2k - type: user - - id: idntusr-FYfXJww0qhW1XrF-WKupOQCQ9Q84d-ieEamjxz1Hzhs - type: user - - id: idntgrp-root-admins - type: group + - idntclt-xT33dia6DUHdcDXsGRUes + - idntgrp-root-admins + - idntusr-DUrM80Cg2VzLVEhpmQb7ovmW1EG1isBnmIFpLTA5N2k + - idntusr-FYfXJww0qhW1XrF-WKupOQCQ9Q84d-ieEamjxz1Hzhs + - idntusr-bailin + updated_at: + type: string + example: "2024-05-06T16:09:14Z" + updated_by: + type: string + example: idntusr-bailin examples: update-role-binding: value: - id: permrbn-pQByAAcMGUfS-ZvXPlrxp - role: - id: permrv2-pUuZKw25TbCjoPnOYkA4w - name: super-admin - subjects: - - id: idntclt-xT33dia6DUHdcDXsGRUes - type: client - - id: idntusr-DUrM80Cg2VzLVEhpmQb7ovmW1EG1isBnmIFpLTA5N2k - type: user - - id: idntusr-FYfXJww0qhW1XrF-WKupOQCQ9Q84d-ieEamjxz1Hzhs - type: user - - id: idntgrp-root-admins - type: group + created_at: "2024-05-06T16:00:46Z" + created_by: idntusr-bailin + id: permrbn-lr5s4g6g1shmDL_htEAR_ + resource_id: tnntten-root + role_id: permrv2-PLjILDwe8kG_t42tMCDiB + subject_ids: + - idntclt-xT33dia6DUHdcDXsGRUes + - idntgrp-root-admins + - idntusr-DUrM80Cg2VzLVEhpmQb7ovmW1EG1isBnmIFpLTA5N2k + - idntusr-FYfXJww0qhW1XrF-WKupOQCQ9Q84d-ieEamjxz1Hzhs + - idntusr-bailin + updated_at: "2024-05-06T16:09:14Z" + updated_by: idntusr-bailin parameters: - name: rb-id in: path required: true schema: type: string - example: permrbn-uRNlQEo6yh9DinKhSxL2z + example: permrbn-lr5s4g6g1shmDL_htEAR_ /actions: get: summary: list-actions