mirror of https://github.com/grafana/grafana.git
Compare commits
2 Commits
e9c7a1244b
...
e189d8547f
Author | SHA1 | Date |
---|---|---|
|
e189d8547f | |
|
9e07387de2 |
|
@ -24,6 +24,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth
|
|||
// Identity specific resources
|
||||
legacyAuthorizer := gfauthorizer.NewResourceAuthorizer(legacyAccessClient)
|
||||
resourceAuthorizer[iamv0.TeamResourceInfo.GetName()] = legacyAuthorizer
|
||||
resourceAuthorizer[iamv0.TeamBindingResourceInfo.GetName()] = legacyAuthorizer
|
||||
resourceAuthorizer["display"] = legacyAuthorizer
|
||||
|
||||
// Access specific resources
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
INSERT INTO {{ .Ident .TeamMemberTable }}
|
||||
(team_id, user_id, created, updated, external, permission)
|
||||
(team_id, user_id, org_id, created, updated, external, permission)
|
||||
VALUES
|
||||
({{ .Arg .Command.TeamID }}, {{ .Arg .Command.UserID }}, {{ .Arg .Command.Created }},
|
||||
({{ .Arg .Command.TeamID }}, {{ .Arg .Command.UserID }}, {{ .Arg .Command.OrgID }}, {{ .Arg .Command.Created }},
|
||||
{{ .Arg .Command.Updated }}, {{ .Arg .Command.External }}, {{ .Arg .Command.Permission }})
|
||||
|
|
|
@ -226,7 +226,8 @@ func TestIdentityQueries(t *testing.T) {
|
|||
Name: "team_1_bindings",
|
||||
Data: listTeamBindings(&ListTeamBindingsQuery{
|
||||
OrgID: 1,
|
||||
UID: "team-1",
|
||||
TeamID: 1,
|
||||
UserID: 1,
|
||||
Pagination: common.Pagination{Limit: 1},
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -414,8 +414,8 @@ func (s *legacySQLStore) DeleteTeam(ctx context.Context, ns claims.NamespaceInfo
|
|||
}
|
||||
|
||||
type ListTeamBindingsQuery struct {
|
||||
// UID is team uid to list bindings for. If not set store should list bindings for all teams
|
||||
UID string
|
||||
TeamID int64
|
||||
UserID int64
|
||||
OrgID int64
|
||||
Pagination common.Pagination
|
||||
}
|
||||
|
@ -432,6 +432,7 @@ type TeamMember struct {
|
|||
TeamUID string
|
||||
UserID int64
|
||||
UserUID string
|
||||
OrgID int64
|
||||
Name string
|
||||
Email string
|
||||
Username string
|
||||
|
@ -486,7 +487,7 @@ func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.Namespa
|
|||
req := newListTeamBindings(sql, &query)
|
||||
q, err := sqltemplate.Execute(sqlQueryTeamBindingsTemplate, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamsTemplate.Name(), err)
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamBindingsTemplate.Name(), err)
|
||||
}
|
||||
|
||||
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
|
||||
|
@ -508,7 +509,7 @@ func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.Namespa
|
|||
|
||||
for rows.Next() {
|
||||
m := TeamMember{}
|
||||
err = rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.Created, &m.Updated, &m.Permission)
|
||||
err = rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Created, &m.Updated, &m.Permission)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
@ -522,16 +523,15 @@ func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.Namespa
|
|||
}
|
||||
}
|
||||
|
||||
if query.UID == "" {
|
||||
res.RV, err = sql.GetResourceVersion(ctx, "team_member", "updated")
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
type CreateTeamMemberCommand struct {
|
||||
TeamID int64
|
||||
TeamUID string
|
||||
UserID int64
|
||||
UserUID string
|
||||
OrgID int64
|
||||
Created DBTime
|
||||
Updated DBTime
|
||||
External bool
|
||||
|
@ -566,6 +566,11 @@ func (s *legacySQLStore) CreateTeamMember(ctx context.Context, ns claims.Namespa
|
|||
now := time.Now().UTC().Truncate(time.Second)
|
||||
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 {
|
||||
|
@ -589,7 +594,10 @@ func (s *legacySQLStore) CreateTeamMember(ctx context.Context, ns claims.Namespa
|
|||
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,
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
|
||||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission
|
||||
FROM {{ .Ident .TeamMemberTable }} tm
|
||||
INNER JOIN {{ .Ident .TeamTable }} t ON tm.team_id = t.id
|
||||
INNER JOIN {{ .Ident .UserTable }} u ON tm.user_id = u.id
|
||||
WHERE
|
||||
tm.org_id = {{ .Arg .Query.OrgID}}
|
||||
{{ if .Query.UID }}
|
||||
AND t.uid = {{ .Arg .Query.UID }}
|
||||
{{ if .Query.TeamID }}
|
||||
AND tm.team_id = {{ .Arg .Query.TeamID }}
|
||||
{{ end }}
|
||||
{{ if .Query.UserID }}
|
||||
AND tm.user_id = {{ .Arg .Query.UserID }}
|
||||
{{ end }}
|
||||
{{- if .Query.Pagination.Continue }}
|
||||
AND tm.id >= {{ .Arg .Query.Pagination.Continue }}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
INSERT INTO `grafana`.`team_member`
|
||||
(team_id, user_id, created, updated, external, permission)
|
||||
(team_id, user_id, org_id, created, updated, external, permission)
|
||||
VALUES
|
||||
(1, 1, '2023-01-01 12:00:00',
|
||||
(1, 1, 0, '2023-01-01 12:00:00',
|
||||
'2023-01-01 12:00:00', FALSE, 'Member')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
INSERT INTO `grafana`.`team_member`
|
||||
(team_id, user_id, created, updated, external, permission)
|
||||
(team_id, user_id, org_id, created, updated, external, permission)
|
||||
VALUES
|
||||
(1, 1, '2023-01-01 12:00:00',
|
||||
(1, 1, 0, '2023-01-01 12:00:00',
|
||||
'2023-01-01 12:00:00', FALSE, 'Admin')
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
|
||||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission
|
||||
FROM `grafana`.`team_member` tm
|
||||
INNER JOIN `grafana`.`team` t ON tm.team_id = t.id
|
||||
INNER JOIN `grafana`.`user` u ON tm.user_id = u.id
|
||||
WHERE
|
||||
tm.org_id = 1
|
||||
AND t.uid = 'team-1'
|
||||
AND tm.team_id = 1
|
||||
AND tm.user_id = 1
|
||||
AND NOT tm.external
|
||||
ORDER BY t.id ASC
|
||||
LIMIT 1;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
|
||||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission
|
||||
FROM `grafana`.`team_member` tm
|
||||
INNER JOIN `grafana`.`team` t ON tm.team_id = t.id
|
||||
INNER JOIN `grafana`.`user` u ON tm.user_id = u.id
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
|
||||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission
|
||||
FROM `grafana`.`team_member` tm
|
||||
INNER JOIN `grafana`.`team` t ON tm.team_id = t.id
|
||||
INNER JOIN `grafana`.`user` u ON tm.user_id = u.id
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
INSERT INTO "grafana"."team_member"
|
||||
(team_id, user_id, created, updated, external, permission)
|
||||
(team_id, user_id, org_id, created, updated, external, permission)
|
||||
VALUES
|
||||
(1, 1, '2023-01-01 12:00:00',
|
||||
(1, 1, 0, '2023-01-01 12:00:00',
|
||||
'2023-01-01 12:00:00', FALSE, 'Member')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
INSERT INTO "grafana"."team_member"
|
||||
(team_id, user_id, created, updated, external, permission)
|
||||
(team_id, user_id, org_id, created, updated, external, permission)
|
||||
VALUES
|
||||
(1, 1, '2023-01-01 12:00:00',
|
||||
(1, 1, 0, '2023-01-01 12:00:00',
|
||||
'2023-01-01 12:00:00', FALSE, 'Admin')
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
|
||||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission
|
||||
FROM "grafana"."team_member" tm
|
||||
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
|
||||
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
|
||||
WHERE
|
||||
tm.org_id = 1
|
||||
AND t.uid = 'team-1'
|
||||
AND tm.team_id = 1
|
||||
AND tm.user_id = 1
|
||||
AND NOT tm.external
|
||||
ORDER BY t.id ASC
|
||||
LIMIT 1;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
|
||||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission
|
||||
FROM "grafana"."team_member" tm
|
||||
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
|
||||
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
|
||||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission
|
||||
FROM "grafana"."team_member" tm
|
||||
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
|
||||
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
INSERT INTO "grafana"."team_member"
|
||||
(team_id, user_id, created, updated, external, permission)
|
||||
(team_id, user_id, org_id, created, updated, external, permission)
|
||||
VALUES
|
||||
(1, 1, '2023-01-01 12:00:00',
|
||||
(1, 1, 0, '2023-01-01 12:00:00',
|
||||
'2023-01-01 12:00:00', FALSE, 'Member')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
INSERT INTO "grafana"."team_member"
|
||||
(team_id, user_id, created, updated, external, permission)
|
||||
(team_id, user_id, org_id, created, updated, external, permission)
|
||||
VALUES
|
||||
(1, 1, '2023-01-01 12:00:00',
|
||||
(1, 1, 0, '2023-01-01 12:00:00',
|
||||
'2023-01-01 12:00:00', FALSE, 'Admin')
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
|
||||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission
|
||||
FROM "grafana"."team_member" tm
|
||||
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
|
||||
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
|
||||
WHERE
|
||||
tm.org_id = 1
|
||||
AND t.uid = 'team-1'
|
||||
AND tm.team_id = 1
|
||||
AND tm.user_id = 1
|
||||
AND NOT tm.external
|
||||
ORDER BY t.id ASC
|
||||
LIMIT 1;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
|
||||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission
|
||||
FROM "grafana"."team_member" tm
|
||||
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
|
||||
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
|
||||
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission
|
||||
FROM "grafana"."team_member" tm
|
||||
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
|
||||
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
@ -99,6 +100,12 @@ func (l *LegacyBindingStore) Create(ctx context.Context, obj runtime.Object, cre
|
|||
return nil, fmt.Errorf("expected TeamBinding object, got %T", obj)
|
||||
}
|
||||
|
||||
if createValidation != nil {
|
||||
if err := createValidation(ctx, teamMemberObj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the user by ID
|
||||
userObj, err := l.store.GetUserInternalID(ctx, ns, legacy.GetUserInternalIDQuery{
|
||||
UID: teamMemberObj.Spec.Subject.Name,
|
||||
|
@ -115,12 +122,6 @@ func (l *LegacyBindingStore) Create(ctx context.Context, obj runtime.Object, cre
|
|||
return nil, fmt.Errorf("failed to fetch team by id %s: %w", teamMemberObj.Spec.TeamRef.Name, err)
|
||||
}
|
||||
|
||||
if createValidation != nil {
|
||||
if err := createValidation(ctx, obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var permission team.PermissionType
|
||||
switch teamMemberObj.Spec.Permission {
|
||||
case iamv0alpha1.TeamBindingTeamPermissionAdmin:
|
||||
|
@ -131,7 +132,9 @@ func (l *LegacyBindingStore) Create(ctx context.Context, obj runtime.Object, cre
|
|||
|
||||
createCmd := legacy.CreateTeamMemberCommand{
|
||||
TeamID: teamObj.ID,
|
||||
TeamUID: teamMemberObj.Spec.TeamRef.Name,
|
||||
UserID: userObj.ID,
|
||||
UserUID: teamMemberObj.Spec.Subject.Name,
|
||||
Permission: permission,
|
||||
External: false,
|
||||
}
|
||||
|
@ -152,8 +155,11 @@ func (l *LegacyBindingStore) Get(ctx context.Context, name string, options *meta
|
|||
return nil, err
|
||||
}
|
||||
|
||||
teamID, userID := mapFromBindingName(name)
|
||||
|
||||
res, err := l.store.ListTeamBindings(ctx, ns, legacy.ListTeamBindingsQuery{
|
||||
UID: name,
|
||||
TeamID: teamID,
|
||||
UserID: userID,
|
||||
Pagination: common.Pagination{Limit: 1},
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -210,7 +216,7 @@ func mapToBindingObject(ns claims.NamespaceInfo, tm legacy.TeamMember) iamv0alph
|
|||
|
||||
return iamv0alpha1.TeamBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: tm.TeamUID,
|
||||
Name: mapToBindingName(tm.TeamID, tm.UserID),
|
||||
Namespace: ns.Value,
|
||||
ResourceVersion: strconv.FormatInt(rv.UnixMilli(), 10),
|
||||
CreationTimestamp: metav1.NewTime(ct),
|
||||
|
@ -227,6 +233,33 @@ func mapToBindingObject(ns claims.NamespaceInfo, tm legacy.TeamMember) iamv0alph
|
|||
}
|
||||
}
|
||||
|
||||
func mapToBindingName(teamID int64, userID int64) string {
|
||||
return fmt.Sprintf("binding-%d-%d", teamID, userID)
|
||||
}
|
||||
|
||||
func mapFromBindingName(name string) (int64, int64) {
|
||||
parts := strings.Split(name, "-")
|
||||
if len(parts) != 3 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
if parts[0] != "binding" {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
teamID, err := strconv.ParseInt(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
userID, err := strconv.ParseInt(parts[2], 10, 64)
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
return teamID, userID
|
||||
}
|
||||
|
||||
func mapPermisson(p team.PermissionType) iamv0.TeamPermission {
|
||||
if p == team.PermissionTypeAdmin {
|
||||
return iamv0.TeamPermissionAdmin
|
||||
|
|
|
@ -57,9 +57,22 @@ func ValidateOnUpdate(ctx context.Context, obj, old *iamv0alpha1.Team) error {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -371,6 +371,60 @@ func TestValidateOnBindingCreate(t *testing.T) {
|
|||
},
|
||||
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 {
|
||||
|
|
|
@ -27,6 +27,12 @@ var gvrUsers = schema.GroupVersionResource{
|
|||
Resource: "users",
|
||||
}
|
||||
|
||||
var gvrTeamBindings = schema.GroupVersionResource{
|
||||
Group: "iam.grafana.app",
|
||||
Version: "v0alpha1",
|
||||
Resource: "teambindings",
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testsuite.Run(m)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,295 @@
|
|||
package identity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tests/apis"
|
||||
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||
"github.com/grafana/grafana/pkg/util/testutil"
|
||||
)
|
||||
|
||||
func TestIntegrationTeamBindings(t *testing.T) {
|
||||
testutil.SkipIntegrationTestInShortMode(t)
|
||||
|
||||
// TODO: Add rest.Mode4 when it's supported
|
||||
modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3}
|
||||
for _, mode := range modes {
|
||||
t.Run(fmt.Sprintf("Team binding CRUD operations with dual writer mode %d", mode), func(t *testing.T) {
|
||||
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
AppModeProduction: false,
|
||||
DisableAnonymous: true,
|
||||
APIServerStorageType: "unified",
|
||||
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
|
||||
"teambindings.iam.grafana.app": {
|
||||
DualWriterMode: mode,
|
||||
},
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
|
||||
featuremgmt.FlagKubernetesAuthnMutation,
|
||||
},
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a team
|
||||
teamClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: helper.Org1.Admin,
|
||||
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
|
||||
GVR: gvrTeams,
|
||||
})
|
||||
|
||||
team, err := teamClient.Resource.Create(ctx, helper.LoadYAMLOrJSONFile("testdata/team-test-create-v0.yaml"), metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, team)
|
||||
|
||||
// Create a user
|
||||
userClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: helper.Org1.Admin,
|
||||
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
|
||||
GVR: gvrUsers,
|
||||
})
|
||||
|
||||
user, err := userClient.Resource.Create(ctx, helper.LoadYAMLOrJSONFile("testdata/user-test-create-v0.yaml"), metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, user)
|
||||
|
||||
doTeamBindingCRUDTestsUsingTheNewAPIs(t, helper, team, user)
|
||||
|
||||
if mode < 3 {
|
||||
doTeamBindingCRUDTestsUsingTheLegacyAPIs(t, helper, mode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHelper, team *unstructured.Unstructured, user *unstructured.Unstructured) {
|
||||
t.Run("should create/get team binding using the new APIs", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: helper.Org1.Admin,
|
||||
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
// Create the team binding
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
created, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, created)
|
||||
|
||||
createdSpec := created.Object["spec"].(map[string]interface{})
|
||||
require.Equal(t, user.GetName(), createdSpec["subject"].(map[string]interface{})["name"])
|
||||
require.Equal(t, team.GetName(), createdSpec["teamRef"].(map[string]interface{})["name"])
|
||||
require.Equal(t, "admin", createdSpec["permission"])
|
||||
|
||||
createdUID := created.GetName()
|
||||
require.NotEmpty(t, createdUID)
|
||||
|
||||
// Get the team binding
|
||||
fetched, err := teamBindingClient.Resource.Get(ctx, createdUID, metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, fetched)
|
||||
|
||||
fetchedSpec := fetched.Object["spec"].(map[string]interface{})
|
||||
require.Equal(t, user.GetName(), fetchedSpec["subject"].(map[string]interface{})["name"])
|
||||
require.Equal(t, team.GetName(), fetchedSpec["teamRef"].(map[string]interface{})["name"])
|
||||
require.Equal(t, "admin", fetchedSpec["permission"])
|
||||
|
||||
require.Equal(t, createdUID, fetched.GetName())
|
||||
require.Equal(t, "default", fetched.GetNamespace())
|
||||
})
|
||||
|
||||
t.Run("should not be able to create team binding when using a user with insufficient permissions", func(t *testing.T) {
|
||||
for _, u := range []apis.User{
|
||||
helper.Org1.Editor,
|
||||
helper.Org1.Viewer,
|
||||
} {
|
||||
t.Run(fmt.Sprintf("with basic role_%s", u.Identity.GetOrgRole()), func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: u,
|
||||
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
_, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.Error(t, err)
|
||||
|
||||
var statusErr *errors.StatusError
|
||||
require.ErrorAs(t, err, &statusErr)
|
||||
require.Equal(t, int32(403), statusErr.ErrStatus.Code)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should not be able to create team binding without a subject", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: helper.Org1.Admin,
|
||||
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = ""
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
|
||||
_, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.Error(t, err)
|
||||
var statusErr *errors.StatusError
|
||||
require.ErrorAs(t, err, &statusErr)
|
||||
require.Equal(t, int32(400), statusErr.ErrStatus.Code)
|
||||
require.Contains(t, statusErr.ErrStatus.Message, "subject is required")
|
||||
})
|
||||
|
||||
t.Run("should not be able to create team binding without a teamRef", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: helper.Org1.Admin,
|
||||
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = ""
|
||||
|
||||
_, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.Error(t, err)
|
||||
var statusErr *errors.StatusError
|
||||
require.ErrorAs(t, err, &statusErr)
|
||||
require.Equal(t, int32(400), statusErr.ErrStatus.Code)
|
||||
require.Contains(t, statusErr.ErrStatus.Message, "teamRef is required")
|
||||
})
|
||||
|
||||
t.Run("should not be able to create team binding with invalid permission", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: helper.Org1.Admin,
|
||||
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["permission"] = "invalid"
|
||||
|
||||
_, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.Error(t, err)
|
||||
var statusErr *errors.StatusError
|
||||
require.ErrorAs(t, err, &statusErr)
|
||||
require.Equal(t, int32(400), statusErr.ErrStatus.Code)
|
||||
require.Contains(t, statusErr.ErrStatus.Message, "invalid permission")
|
||||
})
|
||||
}
|
||||
|
||||
func doTeamBindingCRUDTestsUsingTheLegacyAPIs(t *testing.T, helper *apis.K8sTestHelper, mode rest.DualWriterMode) {
|
||||
t.Run("should create team binding using legacy APIs and get it using the new APIs", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a team using legacy API
|
||||
legacyTeamPayload := `{
|
||||
"name": "Test Team Legacy",
|
||||
"email": "testteamlegacy@example.com"
|
||||
}`
|
||||
|
||||
type legacyTeamResponse struct {
|
||||
UID string `json:"uid"`
|
||||
ID int64 `json:"teamId"`
|
||||
}
|
||||
|
||||
teamRsp := apis.DoRequest(helper, apis.RequestParams{
|
||||
User: helper.Org1.Admin,
|
||||
Method: "POST",
|
||||
Path: "/api/teams",
|
||||
Body: []byte(legacyTeamPayload),
|
||||
}, &legacyTeamResponse{})
|
||||
|
||||
require.NotNil(t, teamRsp)
|
||||
require.Equal(t, 200, teamRsp.Response.StatusCode)
|
||||
require.NotEmpty(t, teamRsp.Result.UID)
|
||||
|
||||
// Create a user using legacy API
|
||||
legacyUserPayload := `{
|
||||
"name": "Test User 2",
|
||||
"email": "testuser2@example.com",
|
||||
"login": "testuser2",
|
||||
"password": "password123"
|
||||
}`
|
||||
|
||||
type legacyUserResponse struct {
|
||||
UID string `json:"uid"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
userRsp := apis.DoRequest(helper, apis.RequestParams{
|
||||
User: helper.Org1.Admin,
|
||||
Method: "POST",
|
||||
Path: "/api/admin/users",
|
||||
Body: []byte(legacyUserPayload),
|
||||
}, &legacyUserResponse{})
|
||||
|
||||
require.NotNil(t, userRsp)
|
||||
require.Equal(t, 200, userRsp.Response.StatusCode)
|
||||
require.NotEmpty(t, userRsp.Result.UID)
|
||||
|
||||
// Create team binding using legacy API
|
||||
legacyTeamBindingPayload := `{
|
||||
"userId": ` + fmt.Sprintf("%d", userRsp.Result.ID) + `,
|
||||
"teamId": ` + fmt.Sprintf("%d", teamRsp.Result.ID) + `,
|
||||
"permission": "member"
|
||||
}`
|
||||
|
||||
type legacyTeamBindingResponse struct {
|
||||
UserID int64 `json:"userId"`
|
||||
TeamID int64 `json:"teamId"`
|
||||
Permission string `json:"permission"`
|
||||
}
|
||||
|
||||
teamBindingRsp := apis.DoRequest(helper, apis.RequestParams{
|
||||
User: helper.Org1.Admin,
|
||||
Method: "POST",
|
||||
Path: "/api/teams/" + teamRsp.Result.UID + "/members",
|
||||
Body: []byte(legacyTeamBindingPayload),
|
||||
}, &legacyTeamBindingResponse{})
|
||||
|
||||
require.NotNil(t, teamBindingRsp)
|
||||
require.Equal(t, 200, teamBindingRsp.Response.StatusCode)
|
||||
|
||||
// Get team binding using new API
|
||||
teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: helper.Org1.Admin,
|
||||
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
teamBindingName := fmt.Sprintf("binding-%d-%d", teamRsp.Result.ID, userRsp.Result.ID)
|
||||
teamBinding, err := teamBindingClient.Resource.Get(ctx, teamBindingName, metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, teamBinding)
|
||||
|
||||
teamBindingSpec := teamBinding.Object["spec"].(map[string]interface{})
|
||||
require.Equal(t, "member", teamBindingSpec["permission"])
|
||||
require.Equal(t, userRsp.Result.UID, teamBindingSpec["subject"].(map[string]interface{})["name"])
|
||||
require.Equal(t, teamRsp.Result.UID, teamBindingSpec["teamRef"].(map[string]interface{})["name"])
|
||||
require.Equal(t, teamBindingName, teamBinding.GetName())
|
||||
})
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: iam.grafana.app/v0alpha1
|
||||
kind: TeamBinding
|
||||
metadata:
|
||||
name: test-team-binding-1
|
||||
spec:
|
||||
subject:
|
||||
name: ""
|
||||
teamRef:
|
||||
name: ""
|
||||
permission: "admin"
|
Loading…
Reference in New Issue