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
This commit is contained in:
Mihai Doarna
2025-10-16 11:56:08 +03:00
committed by GitHub
parent 62995971a2
commit 3f9bf130c7
9 changed files with 470 additions and 443 deletions
-294
View File
@@ -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
}
@@ -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
}
+3 -2
View File
@@ -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)
}
+1 -1
View File
@@ -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),
}
}
-21
View File
@@ -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
}
-110
View File
@@ -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)
})
}
}
@@ -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
}
}
@@ -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
}
@@ -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)
})
}
}