IAM: Create and delete user from the legacy store (#107694)
* Add Create for User + DualWriter setup * Add delete User * Fix delete + access check * Add tests for delete user * Add tests for create user * Fixes * Use sqlx session to fix database locked issues * wip authz checks * legacyAccessClient * Update legacyAccessClient, add tests for create user * Close rows before running other queries * Use ExecWithReturningId * Verify deletion in the tests * Add Validate and Mutate * Other changes * Address feedback * Update tests --------- Co-authored-by: Gabriel Mabille <gabriel.mabille@grafana.com>
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
||||
"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"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
|
||||
const AnnoKeyLastSeenAt = "iam.grafana.app/lastSeenAt"
|
||||
@@ -28,6 +29,10 @@ var (
|
||||
_ rest.Getter = (*LegacyStore)(nil)
|
||||
_ rest.Lister = (*LegacyStore)(nil)
|
||||
_ rest.Storage = (*LegacyStore)(nil)
|
||||
_ rest.CreaterUpdater = (*LegacyStore)(nil)
|
||||
_ rest.GracefulDeleter = (*LegacyStore)(nil)
|
||||
_ rest.CollectionDeleter = (*LegacyStore)(nil)
|
||||
_ rest.TableConvertor = (*LegacyStore)(nil)
|
||||
)
|
||||
|
||||
var resource = iamv0.UserResourceInfo
|
||||
@@ -41,6 +46,57 @@ type LegacyStore struct {
|
||||
ac claims.AccessClient
|
||||
}
|
||||
|
||||
// Update implements rest.Updater.
|
||||
func (s *LegacyStore) 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, fmt.Errorf("method not yet implemented")
|
||||
}
|
||||
|
||||
// DeleteCollection implements rest.CollectionDeleter.
|
||||
func (s *LegacyStore) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) {
|
||||
return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "deletecollection")
|
||||
}
|
||||
|
||||
// Delete implements rest.GracefulDeleter.
|
||||
func (s *LegacyStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
ns, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
found, err := s.store.ListUsers(ctx, ns, legacy.ListUserQuery{
|
||||
OrgID: ns.OrgID,
|
||||
UID: name,
|
||||
Pagination: common.Pagination{Limit: 1},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if found == nil || len(found.Users) < 1 {
|
||||
return nil, false, resource.NewNotFound(name)
|
||||
}
|
||||
|
||||
userToDelete := &found.Users[0]
|
||||
|
||||
if deleteValidation != nil {
|
||||
userObj := toUserItem(userToDelete, ns.Value)
|
||||
if err := deleteValidation(ctx, &userObj); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
deleteCmd := legacy.DeleteUserCommand{
|
||||
UID: name,
|
||||
}
|
||||
|
||||
_, err = s.store.DeleteUser(ctx, ns, deleteCmd)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to delete user: %w", err)
|
||||
}
|
||||
|
||||
deletedUser := toUserItem(userToDelete, ns.Value)
|
||||
return &deletedUser, true, nil
|
||||
}
|
||||
|
||||
func (s *LegacyStore) New() runtime.Object {
|
||||
return resource.NewFunc()
|
||||
}
|
||||
@@ -65,7 +121,7 @@ func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object,
|
||||
|
||||
func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
res, err := common.List(
|
||||
ctx, resource.GetName(), s.ac, common.PaginationFromListOptions(options),
|
||||
ctx, resource, s.ac, common.PaginationFromListOptions(options),
|
||||
func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0alpha.User], error) {
|
||||
found, err := s.store.ListUsers(ctx, ns, legacy.ListUserQuery{
|
||||
Pagination: p,
|
||||
@@ -120,6 +176,48 @@ func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetO
|
||||
return &obj, nil
|
||||
}
|
||||
|
||||
// Create implements rest.Creater.
|
||||
func (s *LegacyStore) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
||||
ns, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userObj, ok := obj.(*iamv0alpha.User)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected User object, got %T", obj)
|
||||
}
|
||||
|
||||
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,
|
||||
Email: userObj.Spec.Email,
|
||||
Name: userObj.Spec.Name,
|
||||
IsAdmin: userObj.Spec.GrafanaAdmin,
|
||||
IsDisabled: userObj.Spec.Disabled,
|
||||
EmailVerified: userObj.Spec.EmailVerified,
|
||||
IsProvisioned: userObj.Spec.Provisioned,
|
||||
}
|
||||
|
||||
result, err := s.store.CreateUser(ctx, ns, createCmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iamUser := toUserItem(&result.User, ns.Value)
|
||||
return &iamUser, nil
|
||||
}
|
||||
|
||||
func toUserItem(u *user.User, ns string) iamv0alpha.User {
|
||||
item := &iamv0alpha.User{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
||||
Reference in New Issue
Block a user