IAM: Add role to User (#111256)
* codegen fix * Return user role from the legacy store * Lint * Add tests, gen openapi * make generate * revert go.mod, go.sum, go.work.sum changes * Update go.mod and go.sum
This commit is contained in:
@@ -14,9 +14,6 @@ func MutateOnCreate(ctx context.Context, obj *iamv0alpha1.User) error {
|
||||
if obj.Spec.Login == "" {
|
||||
obj.Spec.Login = obj.Spec.Email
|
||||
}
|
||||
if obj.Spec.Email == "" {
|
||||
obj.Spec.Email = obj.Spec.Login
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ func TestMutateOnCreate_LoginEmail(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expectedLogin: "only.login",
|
||||
expectedEmail: "only.login",
|
||||
},
|
||||
{
|
||||
name: "login and email already lowercase",
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"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"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
|
||||
@@ -196,16 +196,17 @@ func (s *LegacyStore) Create(ctx context.Context, obj runtime.Object, createVali
|
||||
return nil, fmt.Errorf("expected User object, got %T", obj)
|
||||
}
|
||||
|
||||
if userObj.GenerateName != "" {
|
||||
userObj.Name = userObj.GenerateName + util.GenerateShortUID()
|
||||
userObj.GenerateName = ""
|
||||
}
|
||||
|
||||
if createValidation != nil {
|
||||
if err := createValidation(ctx, obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if userObj.Spec.Login == "" && userObj.Spec.Email == "" {
|
||||
return nil, fmt.Errorf("user must have either login or email")
|
||||
}
|
||||
|
||||
createCmd := legacy.CreateUserCommand{
|
||||
UID: userObj.Name,
|
||||
Login: userObj.Spec.Login,
|
||||
@@ -215,6 +216,7 @@ func (s *LegacyStore) Create(ctx context.Context, obj runtime.Object, createVali
|
||||
IsDisabled: userObj.Spec.Disabled,
|
||||
EmailVerified: userObj.Spec.EmailVerified,
|
||||
IsProvisioned: userObj.Spec.Provisioned,
|
||||
Role: userObj.Spec.Role,
|
||||
}
|
||||
|
||||
result, err := s.store.CreateUser(ctx, ns, createCmd)
|
||||
@@ -226,7 +228,7 @@ func (s *LegacyStore) Create(ctx context.Context, obj runtime.Object, createVali
|
||||
return &iamUser, nil
|
||||
}
|
||||
|
||||
func toUserItem(u *user.User, ns string) iamv0alpha1.User {
|
||||
func toUserItem(u *common.UserWithRole, ns string) iamv0alpha1.User {
|
||||
item := &iamv0alpha1.User{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: u.UID,
|
||||
@@ -242,6 +244,7 @@ func toUserItem(u *user.User, ns string) iamv0alpha1.User {
|
||||
Disabled: u.IsDisabled,
|
||||
GrafanaAdmin: u.IsAdmin,
|
||||
Provisioned: u.IsProvisioned,
|
||||
Role: u.Role,
|
||||
},
|
||||
}
|
||||
obj, _ := utils.MetaAccessor(item)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
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.User) error {
|
||||
requester, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return apierrors.NewUnauthorized("no identity found")
|
||||
}
|
||||
|
||||
// Temporary validation that the user is not trying to create a Grafana Admin without being a Grafana Admin.
|
||||
if obj.Spec.GrafanaAdmin && !requester.GetIsGrafanaAdmin() {
|
||||
return apierrors.NewForbidden(iamv0alpha1.UserResourceInfo.GroupResource(),
|
||||
obj.Name,
|
||||
fmt.Errorf("only grafana admins can create grafana admins"))
|
||||
}
|
||||
|
||||
if obj.Spec.Login == "" && obj.Spec.Email == "" {
|
||||
return apierrors.NewBadRequest("user must have either login or email")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"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
|
||||
user *iamv0alpha1.User
|
||||
requester *identity.StaticRequester
|
||||
expectError bool
|
||||
errorContains string
|
||||
}{
|
||||
{
|
||||
name: "valid user creation by grafana admin",
|
||||
user: &iamv0alpha1.User{
|
||||
Spec: iamv0alpha1.UserSpec{
|
||||
Login: "testuser",
|
||||
},
|
||||
},
|
||||
requester: &identity.StaticRequester{
|
||||
Type: types.TypeUser,
|
||||
IsGrafanaAdmin: true,
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "grafana admin creating another grafana admin",
|
||||
user: &iamv0alpha1.User{
|
||||
Spec: iamv0alpha1.UserSpec{
|
||||
Login: "newadmin",
|
||||
GrafanaAdmin: true,
|
||||
},
|
||||
},
|
||||
requester: &identity.StaticRequester{
|
||||
Type: types.TypeUser,
|
||||
IsGrafanaAdmin: true,
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "non-admin trying to create a grafana admin",
|
||||
user: &iamv0alpha1.User{
|
||||
Spec: iamv0alpha1.UserSpec{
|
||||
Login: "newadmin",
|
||||
GrafanaAdmin: true,
|
||||
},
|
||||
},
|
||||
requester: &identity.StaticRequester{
|
||||
Type: types.TypeUser,
|
||||
IsGrafanaAdmin: false,
|
||||
},
|
||||
expectError: true,
|
||||
errorContains: "only grafana admins can create grafana admins",
|
||||
},
|
||||
{
|
||||
name: "user with empty login and email",
|
||||
user: &iamv0alpha1.User{
|
||||
Spec: iamv0alpha1.UserSpec{},
|
||||
},
|
||||
requester: &identity.StaticRequester{
|
||||
Type: types.TypeUser,
|
||||
IsGrafanaAdmin: false,
|
||||
},
|
||||
expectError: true,
|
||||
errorContains: "user must have either login or email",
|
||||
},
|
||||
{
|
||||
name: "user with only login",
|
||||
user: &iamv0alpha1.User{
|
||||
Spec: iamv0alpha1.UserSpec{
|
||||
Login: "testuser",
|
||||
},
|
||||
},
|
||||
requester: &identity.StaticRequester{
|
||||
Type: types.TypeUser,
|
||||
IsGrafanaAdmin: false,
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "user with only email",
|
||||
user: &iamv0alpha1.User{
|
||||
Spec: iamv0alpha1.UserSpec{
|
||||
Email: "test@test.com",
|
||||
},
|
||||
},
|
||||
requester: &identity.StaticRequester{
|
||||
Type: types.TypeUser,
|
||||
IsGrafanaAdmin: false,
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := identity.WithRequester(
|
||||
context.Background(),
|
||||
tt.requester,
|
||||
)
|
||||
|
||||
err := ValidateOnCreate(ctx, tt.user)
|
||||
|
||||
if tt.expectError {
|
||||
require.Error(t, err)
|
||||
if tt.errorContains != "" {
|
||||
require.Contains(t, err.Error(), tt.errorContains)
|
||||
}
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user