From 3f9bf130c789d47ef31d2925520ffeff7dc8c26b Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Thu, 16 Oct 2025 11:56:08 +0300 Subject: [PATCH] IAM: Move the teambinding code to a separate dir (#112405) * move the teambinding code to a separate dir * validate pagination limit to fix codeql error * supress codeql warning * convert int to int64 --- pkg/registry/apis/iam/legacy/team.go | 294 ----------------- pkg/registry/apis/iam/legacy/team_binding.go | 306 ++++++++++++++++++ pkg/registry/apis/iam/register.go | 5 +- pkg/registry/apis/iam/team/rest_members.go | 2 +- pkg/registry/apis/iam/team/validate.go | 21 -- pkg/registry/apis/iam/team/validate_test.go | 110 ------- .../store_binding.go => teambinding/store.go} | 21 +- pkg/registry/apis/iam/teambinding/validate.go | 31 ++ .../apis/iam/teambinding/validate_test.go | 123 +++++++ 9 files changed, 470 insertions(+), 443 deletions(-) create mode 100644 pkg/registry/apis/iam/legacy/team_binding.go rename pkg/registry/apis/iam/{team/store_binding.go => teambinding/store.go} (92%) create mode 100644 pkg/registry/apis/iam/teambinding/validate.go create mode 100644 pkg/registry/apis/iam/teambinding/validate_test.go diff --git a/pkg/registry/apis/iam/legacy/team.go b/pkg/registry/apis/iam/legacy/team.go index 191cc02cbee..e1dc0909869 100644 --- a/pkg/registry/apis/iam/legacy/team.go +++ b/pkg/registry/apis/iam/legacy/team.go @@ -2,7 +2,6 @@ package legacy import ( "context" - "database/sql" "errors" "fmt" "time" @@ -412,296 +411,3 @@ func (s *legacySQLStore) DeleteTeam(ctx context.Context, ns claims.NamespaceInfo return nil }) } - -type ListTeamBindingsQuery struct { - TeamID int64 - UserID int64 - OrgID int64 - Pagination common.Pagination -} - -type ListTeamBindingsResult struct { - Bindings []TeamMember - Continue int64 - RV int64 -} - -type TeamMember struct { - ID int64 - TeamID int64 - TeamUID string - UserID int64 - UserUID string - OrgID int64 - Name string - Email string - Username string - External bool - Updated time.Time - Created time.Time - Permission team.PermissionType -} - -func (m TeamMember) MemberID() string { - return claims.NewTypeID(claims.TypeUser, m.UserUID) -} - -var sqlQueryTeamBindingsTemplate = mustTemplate("team_bindings_query.sql") - -type listTeamBindingsQuery struct { - sqltemplate.SQLTemplate - Query *ListTeamBindingsQuery - UserTable string - TeamTable string - TeamMemberTable string -} - -func (r listTeamBindingsQuery) Validate() error { - return nil // TODO -} - -func newListTeamBindings(sql *legacysql.LegacyDatabaseHelper, q *ListTeamBindingsQuery) listTeamBindingsQuery { - return listTeamBindingsQuery{ - SQLTemplate: sqltemplate.New(sql.DialectForDriver()), - UserTable: sql.Table("user"), - TeamTable: sql.Table("team"), - TeamMemberTable: sql.Table("team_member"), - Query: q, - } -} - -// ListTeamsBindings implements LegacyIdentityStore. -func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error) { - // for continue - query.Pagination.Limit += 1 - query.OrgID = ns.OrgID - if query.OrgID == 0 { - return nil, fmt.Errorf("expected non zero orgID") - } - - sql, err := s.sql(ctx) - if err != nil { - return nil, err - } - - req := newListTeamBindings(sql, &query) - q, err := sqltemplate.Execute(sqlQueryTeamBindingsTemplate, req) - if err != nil { - return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamBindingsTemplate.Name(), err) - } - - rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) - defer func() { - if rows != nil { - _ = rows.Close() - } - }() - - if err != nil { - return nil, err - } - - res := &ListTeamBindingsResult{ - Bindings: make([]TeamMember, 0, int(query.Pagination.Limit)), - } - - var lastID int64 - - for rows.Next() { - m := TeamMember{} - err = rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Created, &m.Updated, &m.Permission, &m.External) - if err != nil { - return res, err - } - - res.Bindings = append(res.Bindings, m) - - lastID = m.ID - - if len(res.Bindings) >= int(query.Pagination.Limit)-1 { - res.Continue = lastID - } - } - - return res, err -} - -type CreateTeamMemberCommand struct { - TeamID int64 - TeamUID string - UserID int64 - UserUID string - OrgID int64 - Created DBTime - Updated DBTime - External bool - Permission team.PermissionType -} - -type CreateTeamMemberResult struct { - TeamMember TeamMember -} - -var sqlCreateTeamMemberQuery = mustTemplate("create_team_member_query.sql") - -func newCreateTeamMember(sql *legacysql.LegacyDatabaseHelper, cmd *CreateTeamMemberCommand) createTeamMemberQuery { - return createTeamMemberQuery{ - SQLTemplate: sqltemplate.New(sql.DialectForDriver()), - TeamMemberTable: sql.Table("team_member"), - Command: cmd, - } -} - -type createTeamMemberQuery struct { - sqltemplate.SQLTemplate - TeamMemberTable string - Command *CreateTeamMemberCommand -} - -func (r createTeamMemberQuery) Validate() error { - return nil -} - -func (s *legacySQLStore) CreateTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd CreateTeamMemberCommand) (*CreateTeamMemberResult, error) { - now := time.Now().UTC() - cmd.Created = NewDBTime(now) - cmd.Updated = NewDBTime(now) - cmd.OrgID = ns.OrgID - - if cmd.OrgID == 0 { - return nil, fmt.Errorf("expected non zero org id") - } - - sql, err := s.sql(ctx) - if err != nil { - return nil, err - } - - req := newCreateTeamMember(sql, &cmd) - - var createdTeamMember TeamMember - err = sql.DB.GetSqlxSession().WithTransaction(ctx, func(st *session.SessionTx) error { - teamMemberQuery, err := sqltemplate.Execute(sqlCreateTeamMemberQuery, req) - if err != nil { - return fmt.Errorf("failed to execute team member template %q: %w", sqlCreateTeamMemberQuery.Name(), err) - } - - teamMemberID, err := st.ExecWithReturningId(ctx, teamMemberQuery, req.GetArgs()...) - if err != nil { - return fmt.Errorf("failed to create team member: %w", err) - } - - createdTeamMember = TeamMember{ - ID: teamMemberID, - TeamID: cmd.TeamID, - TeamUID: cmd.TeamUID, - UserID: cmd.UserID, - UserUID: cmd.UserUID, - OrgID: cmd.OrgID, - Created: cmd.Created.Time, - Updated: cmd.Updated.Time, - External: cmd.External, - Permission: cmd.Permission, - } - - return nil - }) - - if err != nil { - return nil, err - } - - return &CreateTeamMemberResult{TeamMember: createdTeamMember}, nil -} - -type ListTeamMembersQuery struct { - UID string - OrgID int64 - Pagination common.Pagination -} - -type ListTeamMembersResult struct { - Continue int64 - Members []TeamMember -} - -// Templates. -var sqlQueryTeamMembersTemplate = mustTemplate("team_members_query.sql") - -type listTeamMembersQuery struct { - sqltemplate.SQLTemplate - Query *ListTeamMembersQuery - UserTable string - TeamTable string - TeamMemberTable string -} - -func (r listTeamMembersQuery) Validate() error { - return nil // TODO -} - -func newListTeamMembers(sql *legacysql.LegacyDatabaseHelper, q *ListTeamMembersQuery) listTeamMembersQuery { - return listTeamMembersQuery{ - SQLTemplate: sqltemplate.New(sql.DialectForDriver()), - UserTable: sql.Table("user"), - TeamTable: sql.Table("team"), - TeamMemberTable: sql.Table("team_member"), - Query: q, - } -} - -// ListTeamMembers implements LegacyIdentityStore. -func (s *legacySQLStore) ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error) { - query.Pagination.Limit += 1 - query.OrgID = ns.OrgID - if query.OrgID == 0 { - return nil, fmt.Errorf("expected non zero org id") - } - - sql, err := s.sql(ctx) - if err != nil { - return nil, err - } - - req := newListTeamMembers(sql, &query) - q, err := sqltemplate.Execute(sqlQueryTeamMembersTemplate, req) - if err != nil { - return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamsTemplate.Name(), err) - } - - rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) - defer func() { - if rows != nil { - _ = rows.Close() - } - }() - - if err != nil { - return nil, err - } - - res := &ListTeamMembersResult{} - var lastID int64 - for rows.Next() { - m, err := scanMember(rows) - if err != nil { - return nil, err - } - - lastID = m.ID - res.Members = append(res.Members, m) - if len(res.Members) > int(query.Pagination.Limit)-1 { - res.Continue = lastID - res.Members = res.Members[0 : len(res.Members)-1] - break - } - } - - return res, err -} - -func scanMember(rows *sql.Rows) (TeamMember, error) { - m := TeamMember{} - err := rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Name, &m.Email, &m.Username, &m.External, &m.Created, &m.Updated, &m.Permission) - return m, err -} diff --git a/pkg/registry/apis/iam/legacy/team_binding.go b/pkg/registry/apis/iam/legacy/team_binding.go new file mode 100644 index 00000000000..3cf13e779e6 --- /dev/null +++ b/pkg/registry/apis/iam/legacy/team_binding.go @@ -0,0 +1,306 @@ +package legacy + +import ( + "context" + "database/sql" + "fmt" + "time" + + claims "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/registry/apis/iam/common" + "github.com/grafana/grafana/pkg/services/sqlstore/session" + "github.com/grafana/grafana/pkg/services/team" + "github.com/grafana/grafana/pkg/storage/legacysql" + "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" +) + +type ListTeamBindingsQuery struct { + TeamID int64 + UserID int64 + OrgID int64 + Pagination common.Pagination +} + +type ListTeamBindingsResult struct { + Bindings []TeamMember + Continue int64 + RV int64 +} + +type TeamMember struct { + ID int64 + TeamID int64 + TeamUID string + UserID int64 + UserUID string + OrgID int64 + Name string + Email string + Username string + External bool + Updated time.Time + Created time.Time + Permission team.PermissionType +} + +func (m TeamMember) MemberID() string { + return claims.NewTypeID(claims.TypeUser, m.UserUID) +} + +var sqlQueryTeamBindingsTemplate = mustTemplate("team_bindings_query.sql") + +type listTeamBindingsQuery struct { + sqltemplate.SQLTemplate + Query *ListTeamBindingsQuery + UserTable string + TeamTable string + TeamMemberTable string +} + +func (r listTeamBindingsQuery) Validate() error { + return nil // TODO +} + +func newListTeamBindings(sql *legacysql.LegacyDatabaseHelper, q *ListTeamBindingsQuery) listTeamBindingsQuery { + return listTeamBindingsQuery{ + SQLTemplate: sqltemplate.New(sql.DialectForDriver()), + UserTable: sql.Table("user"), + TeamTable: sql.Table("team"), + TeamMemberTable: sql.Table("team_member"), + Query: q, + } +} + +func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error) { + // for continue + query.Pagination.Limit += 1 + query.OrgID = ns.OrgID + if query.OrgID == 0 { + return nil, fmt.Errorf("expected non zero orgID") + } + + sql, err := s.sql(ctx) + if err != nil { + return nil, err + } + + req := newListTeamBindings(sql, &query) + q, err := sqltemplate.Execute(sqlQueryTeamBindingsTemplate, req) + if err != nil { + return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamBindingsTemplate.Name(), err) + } + + rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) + defer func() { + if rows != nil { + _ = rows.Close() + } + }() + + if err != nil { + return nil, err + } + + res := &ListTeamBindingsResult{ + Bindings: make([]TeamMember, 0, int(query.Pagination.Limit)), + } + + var lastID int64 + + for rows.Next() { + m := TeamMember{} + err = rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Created, &m.Updated, &m.Permission, &m.External) + if err != nil { + return res, err + } + + res.Bindings = append(res.Bindings, m) + + lastID = m.ID + + if len(res.Bindings) >= int(query.Pagination.Limit)-1 { + res.Continue = lastID + } + } + + return res, err +} + +type CreateTeamMemberCommand struct { + TeamID int64 + TeamUID string + UserID int64 + UserUID string + OrgID int64 + Created DBTime + Updated DBTime + External bool + Permission team.PermissionType +} + +type CreateTeamMemberResult struct { + TeamMember TeamMember +} + +var sqlCreateTeamMemberQuery = mustTemplate("create_team_member_query.sql") + +func newCreateTeamMember(sql *legacysql.LegacyDatabaseHelper, cmd *CreateTeamMemberCommand) createTeamMemberQuery { + return createTeamMemberQuery{ + SQLTemplate: sqltemplate.New(sql.DialectForDriver()), + TeamMemberTable: sql.Table("team_member"), + Command: cmd, + } +} + +type createTeamMemberQuery struct { + sqltemplate.SQLTemplate + TeamMemberTable string + Command *CreateTeamMemberCommand +} + +func (r createTeamMemberQuery) Validate() error { + return nil +} + +func (s *legacySQLStore) CreateTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd CreateTeamMemberCommand) (*CreateTeamMemberResult, error) { + now := time.Now().UTC() + cmd.Created = NewDBTime(now) + cmd.Updated = NewDBTime(now) + cmd.OrgID = ns.OrgID + + if cmd.OrgID == 0 { + return nil, fmt.Errorf("expected non zero org id") + } + + sql, err := s.sql(ctx) + if err != nil { + return nil, err + } + + req := newCreateTeamMember(sql, &cmd) + + var createdTeamMember TeamMember + err = sql.DB.GetSqlxSession().WithTransaction(ctx, func(st *session.SessionTx) error { + teamMemberQuery, err := sqltemplate.Execute(sqlCreateTeamMemberQuery, req) + if err != nil { + return fmt.Errorf("failed to execute team member template %q: %w", sqlCreateTeamMemberQuery.Name(), err) + } + + teamMemberID, err := st.ExecWithReturningId(ctx, teamMemberQuery, req.GetArgs()...) + if err != nil { + return fmt.Errorf("failed to create team member: %w", err) + } + + createdTeamMember = TeamMember{ + ID: teamMemberID, + TeamID: cmd.TeamID, + TeamUID: cmd.TeamUID, + UserID: cmd.UserID, + UserUID: cmd.UserUID, + OrgID: cmd.OrgID, + Created: cmd.Created.Time, + Updated: cmd.Updated.Time, + External: cmd.External, + Permission: cmd.Permission, + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &CreateTeamMemberResult{TeamMember: createdTeamMember}, nil +} + +type ListTeamMembersQuery struct { + UID string + OrgID int64 + Pagination common.Pagination +} + +type ListTeamMembersResult struct { + Continue int64 + Members []TeamMember +} + +var sqlQueryTeamMembersTemplate = mustTemplate("team_members_query.sql") + +type listTeamMembersQuery struct { + sqltemplate.SQLTemplate + Query *ListTeamMembersQuery + UserTable string + TeamTable string + TeamMemberTable string +} + +func (r listTeamMembersQuery) Validate() error { + return nil // TODO +} + +func newListTeamMembers(sql *legacysql.LegacyDatabaseHelper, q *ListTeamMembersQuery) listTeamMembersQuery { + return listTeamMembersQuery{ + SQLTemplate: sqltemplate.New(sql.DialectForDriver()), + UserTable: sql.Table("user"), + TeamTable: sql.Table("team"), + TeamMemberTable: sql.Table("team_member"), + Query: q, + } +} + +func (s *legacySQLStore) ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error) { + query.Pagination.Limit += 1 + query.OrgID = ns.OrgID + if query.OrgID == 0 { + return nil, fmt.Errorf("expected non zero org id") + } + + sql, err := s.sql(ctx) + if err != nil { + return nil, err + } + + req := newListTeamMembers(sql, &query) + q, err := sqltemplate.Execute(sqlQueryTeamMembersTemplate, req) + if err != nil { + return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamsTemplate.Name(), err) + } + + rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) + defer func() { + if rows != nil { + _ = rows.Close() + } + }() + + if err != nil { + return nil, err + } + + res := &ListTeamMembersResult{} + var lastID int64 + for rows.Next() { + m, err := scanMember(rows) + if err != nil { + return nil, err + } + + lastID = m.ID + res.Members = append(res.Members, m) + + if int64(len(res.Members)) > query.Pagination.Limit-1 { + res.Continue = lastID + res.Members = res.Members[0 : len(res.Members)-1] + break + } + } + + return res, err +} + +func scanMember(rows *sql.Rows) (TeamMember, error) { + m := TeamMember{} + err := rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Name, &m.Email, &m.Username, &m.External, &m.Created, &m.Updated, &m.Permission) + return m, err +} diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 9ab61ff8a1c..16c7c3fb160 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -34,6 +34,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/iam/serviceaccount" "github.com/grafana/grafana/pkg/registry/apis/iam/sso" "github.com/grafana/grafana/pkg/registry/apis/iam/team" + "github.com/grafana/grafana/pkg/registry/apis/iam/teambinding" "github.com/grafana/grafana/pkg/registry/apis/iam/user" "github.com/grafana/grafana/pkg/services/accesscontrol" gfauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" @@ -192,7 +193,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge } teamBindingResource := iamv0.TeamBindingResourceInfo - teamBindingLegacyStore := team.NewLegacyBindingStore(b.store, enableAuthnMutation) + teamBindingLegacyStore := teambinding.NewLegacyBindingStore(b.store, enableAuthnMutation) storage[teamBindingResource.StoragePath()] = teamBindingLegacyStore if b.enableDualWriter { @@ -377,7 +378,7 @@ func (b *IdentityAccessManagementAPIBuilder) Validate(ctx context.Context, a adm case *iamv0.Team: return team.ValidateOnCreate(ctx, typedObj) case *iamv0.TeamBinding: - return team.ValidateOnBindingCreate(ctx, typedObj) + return teambinding.ValidateOnCreate(ctx, typedObj) case *iamv0.ResourcePermission: return resourcepermission.ValidateCreateAndUpdateInput(ctx, typedObj) } diff --git a/pkg/registry/apis/iam/team/rest_members.go b/pkg/registry/apis/iam/team/rest_members.go index edac4cc8f59..6659823b9fa 100644 --- a/pkg/registry/apis/iam/team/rest_members.go +++ b/pkg/registry/apis/iam/team/rest_members.go @@ -107,6 +107,6 @@ func mapToTeamMember(m legacy.TeamMember) iamv0.TeamMember { InternalID: m.UserID, }, External: m.External, - Permission: mapPermisson(m.Permission), + Permission: common.MapUserTeamPermission(m.Permission), } } diff --git a/pkg/registry/apis/iam/team/validate.go b/pkg/registry/apis/iam/team/validate.go index 6859310e402..97777cb3c4e 100644 --- a/pkg/registry/apis/iam/team/validate.go +++ b/pkg/registry/apis/iam/team/validate.go @@ -55,24 +55,3 @@ func ValidateOnUpdate(ctx context.Context, obj, old *iamv0alpha1.Team) error { return nil } - -func ValidateOnBindingCreate(ctx context.Context, obj *iamv0alpha1.TeamBinding) error { - _, err := identity.GetRequester(ctx) - if err != nil { - return apierrors.NewUnauthorized("no identity found") - } - - if obj.Spec.Permission != iamv0alpha1.TeamBindingTeamPermissionAdmin && obj.Spec.Permission != iamv0alpha1.TeamBindingTeamPermissionMember { - return apierrors.NewBadRequest("invalid permission") - } - - if obj.Spec.Subject.Name == "" { - return apierrors.NewBadRequest("subject is required") - } - - if obj.Spec.TeamRef.Name == "" { - return apierrors.NewBadRequest("teamRef is required") - } - - return nil -} diff --git a/pkg/registry/apis/iam/team/validate_test.go b/pkg/registry/apis/iam/team/validate_test.go index e4a7e0bf53b..1093a01f57d 100644 --- a/pkg/registry/apis/iam/team/validate_test.go +++ b/pkg/registry/apis/iam/team/validate_test.go @@ -325,113 +325,3 @@ func TestValidateOnUpdate(t *testing.T) { }) } } - -func TestValidateOnBindingCreate(t *testing.T) { - tests := []struct { - name string - requester *identity.StaticRequester - obj *iamv0alpha1.TeamBinding - want error - }{ - { - name: "valid team binding create", - requester: &identity.StaticRequester{ - Type: types.TypeUser, - OrgRole: identity.RoleAdmin, - }, - obj: &iamv0alpha1.TeamBinding{ - Spec: iamv0alpha1.TeamBindingSpec{ - Subject: iamv0alpha1.TeamBindingspecSubject{ - Name: "test-user", - }, - TeamRef: iamv0alpha1.TeamBindingTeamRef{ - Name: "test-team", - }, - Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, - }, - }, - want: nil, - }, - { - name: "invalid team binding - invalid permission", - requester: &identity.StaticRequester{ - Type: types.TypeUser, - OrgRole: identity.RoleAdmin, - }, - obj: &iamv0alpha1.TeamBinding{ - Spec: iamv0alpha1.TeamBindingSpec{ - Subject: iamv0alpha1.TeamBindingspecSubject{ - Name: "test-user", - }, - TeamRef: iamv0alpha1.TeamBindingTeamRef{ - Name: "test-team", - }, - Permission: "invalid", - }, - }, - want: apierrors.NewBadRequest("invalid permission"), - }, - { - name: "invalid team binding - no subject", - requester: &identity.StaticRequester{ - Type: types.TypeUser, - OrgRole: identity.RoleAdmin, - }, - obj: &iamv0alpha1.TeamBinding{ - Spec: iamv0alpha1.TeamBindingSpec{ - Subject: iamv0alpha1.TeamBindingspecSubject{ - Name: "", - }, - TeamRef: iamv0alpha1.TeamBindingTeamRef{ - Name: "test-team", - }, - Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, - }, - }, - want: apierrors.NewBadRequest("subject is required"), - }, - { - name: "invalid team binding - no teamRef", - requester: &identity.StaticRequester{ - Type: types.TypeUser, - OrgRole: identity.RoleAdmin, - }, - obj: &iamv0alpha1.TeamBinding{ - Spec: iamv0alpha1.TeamBindingSpec{ - Subject: iamv0alpha1.TeamBindingspecSubject{ - Name: "test-user", - }, - TeamRef: iamv0alpha1.TeamBindingTeamRef{ - Name: "", - }, - Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, - }, - }, - want: apierrors.NewBadRequest("teamRef is required"), - }, - { - name: "invalid team binding - no requester in context", - requester: nil, - obj: &iamv0alpha1.TeamBinding{ - Spec: iamv0alpha1.TeamBindingSpec{ - Subject: iamv0alpha1.TeamBindingspecSubject{ - Name: "test-user", - }, - TeamRef: iamv0alpha1.TeamBindingTeamRef{ - Name: "test-team", - }, - Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, - }, - }, - want: apierrors.NewUnauthorized("no identity found"), - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ctx := identity.WithRequester(context.Background(), test.requester) - err := ValidateOnBindingCreate(ctx, test.obj) - assert.Equal(t, test.want, err) - }) - } -} diff --git a/pkg/registry/apis/iam/team/store_binding.go b/pkg/registry/apis/iam/teambinding/store.go similarity index 92% rename from pkg/registry/apis/iam/team/store_binding.go rename to pkg/registry/apis/iam/teambinding/store.go index 35f28692ca9..7f8f673334e 100644 --- a/pkg/registry/apis/iam/team/store_binding.go +++ b/pkg/registry/apis/iam/teambinding/store.go @@ -1,4 +1,4 @@ -package team +package teambinding import ( "context" @@ -15,7 +15,6 @@ import ( claims "github.com/grafana/authlib/types" iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" - iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/iam/common" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" @@ -74,20 +73,20 @@ func (l *LegacyBindingStore) ConvertToTable(ctx context.Context, object runtime. } func (l *LegacyBindingStore) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "update") + return nil, false, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "update") } func (l *LegacyBindingStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "delete") + return nil, false, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "delete") } func (l *LegacyBindingStore) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) { - return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "deleteCollection") + return nil, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "deleteCollection") } func (l *LegacyBindingStore) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { if !l.enableAuthnMutation { - return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "create") + return nil, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "create") } ns, err := request.NamespaceInfoFrom(ctx, true) @@ -168,7 +167,7 @@ func (l *LegacyBindingStore) Get(ctx context.Context, name string, options *meta if len(res.Bindings) != 1 { // FIXME: maybe empty result? - return nil, resource.NewNotFound(name) + return nil, bindingResource.NewNotFound(name) } obj := mapToBindingObject(ns, res.Bindings[0]) @@ -260,11 +259,3 @@ func mapFromBindingName(name string) (int64, int64) { return teamID, userID } - -func mapPermisson(p team.PermissionType) iamv0.TeamPermission { - if p == team.PermissionTypeAdmin { - return iamv0.TeamPermissionAdmin - } else { - return iamv0.TeamPermissionMember - } -} diff --git a/pkg/registry/apis/iam/teambinding/validate.go b/pkg/registry/apis/iam/teambinding/validate.go new file mode 100644 index 00000000000..27289ac19d2 --- /dev/null +++ b/pkg/registry/apis/iam/teambinding/validate.go @@ -0,0 +1,31 @@ +package teambinding + +import ( + "context" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + + iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +func ValidateOnCreate(ctx context.Context, obj *iamv0alpha1.TeamBinding) error { + _, err := identity.GetRequester(ctx) + if err != nil { + return apierrors.NewUnauthorized("no identity found") + } + + if obj.Spec.Permission != iamv0alpha1.TeamBindingTeamPermissionAdmin && obj.Spec.Permission != iamv0alpha1.TeamBindingTeamPermissionMember { + return apierrors.NewBadRequest("invalid permission") + } + + if obj.Spec.Subject.Name == "" { + return apierrors.NewBadRequest("subject is required") + } + + if obj.Spec.TeamRef.Name == "" { + return apierrors.NewBadRequest("teamRef is required") + } + + return nil +} diff --git a/pkg/registry/apis/iam/teambinding/validate_test.go b/pkg/registry/apis/iam/teambinding/validate_test.go new file mode 100644 index 00000000000..fbe4063fb43 --- /dev/null +++ b/pkg/registry/apis/iam/teambinding/validate_test.go @@ -0,0 +1,123 @@ +package teambinding + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + apierrors "k8s.io/apimachinery/pkg/api/errors" + + "github.com/grafana/authlib/types" + iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +func TestValidateOnCreate(t *testing.T) { + tests := []struct { + name string + requester *identity.StaticRequester + obj *iamv0alpha1.TeamBinding + want error + }{ + { + name: "valid team binding create", + requester: &identity.StaticRequester{ + Type: types.TypeUser, + OrgRole: identity.RoleAdmin, + }, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "test-user", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "test-team", + }, + Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, + }, + }, + want: nil, + }, + { + name: "invalid team binding - invalid permission", + requester: &identity.StaticRequester{ + Type: types.TypeUser, + OrgRole: identity.RoleAdmin, + }, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "test-user", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "test-team", + }, + Permission: "invalid", + }, + }, + want: apierrors.NewBadRequest("invalid permission"), + }, + { + name: "invalid team binding - no subject", + requester: &identity.StaticRequester{ + Type: types.TypeUser, + OrgRole: identity.RoleAdmin, + }, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "test-team", + }, + Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, + }, + }, + want: apierrors.NewBadRequest("subject is required"), + }, + { + name: "invalid team binding - no teamRef", + requester: &identity.StaticRequester{ + Type: types.TypeUser, + OrgRole: identity.RoleAdmin, + }, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "test-user", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "", + }, + Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, + }, + }, + want: apierrors.NewBadRequest("teamRef is required"), + }, + { + name: "invalid team binding - no requester in context", + requester: nil, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "test-user", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "test-team", + }, + Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, + }, + }, + want: apierrors.NewUnauthorized("no identity found"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := identity.WithRequester(context.Background(), test.requester) + err := ValidateOnCreate(ctx, test.obj) + assert.Equal(t, test.want, err) + }) + } +}