mirror of
https://github.com/grafana/grafana.git
synced 2025-01-13 01:22:05 -06:00
Users: Allow specifying user UIDs in params (#95424)
* add user ID API translation * add uid to user frontend * use users' UIDs in admin pages * fix ldapSync page * use global user search for user by UID * remove active org filtering * remove orgID params
This commit is contained in:
parent
f0391e31d2
commit
90d2f4659e
@ -71,6 +71,7 @@ func (hs *HTTPServer) AdminCreateUser(c *contextmodel.ReqContext) response.Respo
|
||||
result := user.AdminCreateUserResponse{
|
||||
Message: "User created",
|
||||
ID: usr.ID,
|
||||
UID: usr.UID,
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, result)
|
||||
|
@ -30,6 +30,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/middleware/requestmeta"
|
||||
@ -37,6 +40,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ssoutils"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/correlations"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
@ -46,6 +50,7 @@ import (
|
||||
publicdashboardsapi "github.com/grafana/grafana/pkg/services/publicdashboards/api"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
"go.opentelemetry.io/otel"
|
||||
)
|
||||
|
||||
@ -65,6 +70,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
authorize := ac.Middleware(hs.AccessControl)
|
||||
authorizeInOrg := ac.AuthorizeInOrgMiddleware(hs.AccessControl, hs.authnService)
|
||||
quota := middleware.Quota(hs.QuotaService)
|
||||
userUIDResolver := middlewareUserUIDResolver(hs.userService, ":id")
|
||||
|
||||
r := hs.RouteRegister
|
||||
|
||||
@ -282,13 +288,13 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
userIDScope := ac.Scope("global.users", "id", ac.Parameter(":id"))
|
||||
usersRoute.Get("/", authorize(ac.EvalPermission(ac.ActionUsersRead)), routing.Wrap(hs.searchUsersService.SearchUsers))
|
||||
usersRoute.Get("/search", authorize(ac.EvalPermission(ac.ActionUsersRead)), routing.Wrap(hs.searchUsersService.SearchUsersWithPaging))
|
||||
usersRoute.Get("/:id", authorize(ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(hs.GetUserByID))
|
||||
usersRoute.Get("/:id/teams", authorize(ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(hs.GetUserTeams))
|
||||
usersRoute.Get("/:id/orgs", authorize(ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(hs.GetUserOrgList))
|
||||
usersRoute.Get("/:id", userUIDResolver, authorize(ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(hs.GetUserByID))
|
||||
usersRoute.Get("/:id/teams", userUIDResolver, authorize(ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(hs.GetUserTeams))
|
||||
usersRoute.Get("/:id/orgs", userUIDResolver, authorize(ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(hs.GetUserOrgList))
|
||||
// query parameters /users/lookup?loginOrEmail=admin@example.com
|
||||
usersRoute.Get("/lookup", authorize(ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)), routing.Wrap(hs.GetUserByLoginOrEmail))
|
||||
usersRoute.Put("/:id", authorize(ac.EvalPermission(ac.ActionUsersWrite, userIDScope)), routing.Wrap(hs.UpdateUser))
|
||||
usersRoute.Post("/:id/using/:orgId", authorize(ac.EvalPermission(ac.ActionUsersWrite, userIDScope)), routing.Wrap(hs.UpdateUserActiveOrg))
|
||||
usersRoute.Put("/:id", userUIDResolver, authorize(ac.EvalPermission(ac.ActionUsersWrite, userIDScope)), routing.Wrap(hs.UpdateUser))
|
||||
usersRoute.Post("/:id/using/:orgId", userUIDResolver, authorize(ac.EvalPermission(ac.ActionUsersWrite, userIDScope)), routing.Wrap(hs.UpdateUserActiveOrg))
|
||||
}, requestmeta.SetOwner(requestmeta.TeamAuth))
|
||||
|
||||
// org information available to all users.
|
||||
@ -355,6 +361,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
// search all orgs
|
||||
apiRoute.Get("/orgs", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionOrgsRead)), routing.Wrap(hs.SearchOrgs))
|
||||
|
||||
orgUserUIDResolver := middlewareUserUIDResolver(hs.userService, ":userId")
|
||||
// orgs (admin routes)
|
||||
apiRoute.Group("/orgs/:orgId", func(orgsRoute routing.RouteRegister) {
|
||||
userIDScope := ac.Scope("users", "id", ac.Parameter(":userId"))
|
||||
@ -365,8 +372,8 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
orgsRoute.Get("/users", requestmeta.SetOwner(requestmeta.TeamAuth), authorizeInOrg(ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRead)), routing.Wrap(hs.GetOrgUsers))
|
||||
orgsRoute.Get("/users/search", requestmeta.SetOwner(requestmeta.TeamAuth), authorizeInOrg(ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRead)), routing.Wrap(hs.SearchOrgUsers))
|
||||
orgsRoute.Post("/users", requestmeta.SetOwner(requestmeta.TeamAuth), authorizeInOrg(ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), routing.Wrap(hs.AddOrgUser))
|
||||
orgsRoute.Patch("/users/:userId", requestmeta.SetOwner(requestmeta.TeamAuth), authorizeInOrg(ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersWrite, userIDScope)), routing.Wrap(hs.UpdateOrgUser))
|
||||
orgsRoute.Delete("/users/:userId", requestmeta.SetOwner(requestmeta.TeamAuth), authorizeInOrg(ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(hs.RemoveOrgUser))
|
||||
orgsRoute.Patch("/users/:userId", orgUserUIDResolver, requestmeta.SetOwner(requestmeta.TeamAuth), authorizeInOrg(ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersWrite, userIDScope)), routing.Wrap(hs.UpdateOrgUser))
|
||||
orgsRoute.Delete("/users/:userId", orgUserUIDResolver, requestmeta.SetOwner(requestmeta.TeamAuth), authorizeInOrg(ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(hs.RemoveOrgUser))
|
||||
orgsRoute.Get("/quotas", authorizeInOrg(ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgsQuotasRead)), routing.Wrap(hs.GetOrgQuotas))
|
||||
orgsRoute.Put("/quotas/:target", authorizeInOrg(ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgsQuotasWrite)), routing.Wrap(hs.UpdateOrgQuota))
|
||||
})
|
||||
@ -577,17 +584,17 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
userIDScope := ac.Scope("global.users", "id", ac.Parameter(":id"))
|
||||
|
||||
adminUserRoute.Post("/", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(hs.AdminCreateUser))
|
||||
adminUserRoute.Put("/:id/password", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersPasswordUpdate, userIDScope)), routing.Wrap(hs.AdminUpdateUserPassword))
|
||||
adminUserRoute.Put("/:id/permissions", reqGrafanaAdmin, authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersPermissionsUpdate, userIDScope)), routing.Wrap(hs.AdminUpdateUserPermissions))
|
||||
adminUserRoute.Delete("/:id", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersDelete, userIDScope)), routing.Wrap(hs.AdminDeleteUser))
|
||||
adminUserRoute.Post("/:id/disable", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersDisable, userIDScope)), routing.Wrap(hs.AdminDisableUser))
|
||||
adminUserRoute.Post("/:id/enable", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersEnable, userIDScope)), routing.Wrap(hs.AdminEnableUser))
|
||||
adminUserRoute.Get("/:id/quotas", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersQuotasList, userIDScope)), routing.Wrap(hs.GetUserQuotas))
|
||||
adminUserRoute.Put("/:id/quotas/:target", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersQuotasUpdate, userIDScope)), routing.Wrap(hs.UpdateUserQuota))
|
||||
adminUserRoute.Put("/:id/password", userUIDResolver, authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersPasswordUpdate, userIDScope)), routing.Wrap(hs.AdminUpdateUserPassword))
|
||||
adminUserRoute.Put("/:id/permissions", userUIDResolver, reqGrafanaAdmin, authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersPermissionsUpdate, userIDScope)), routing.Wrap(hs.AdminUpdateUserPermissions))
|
||||
adminUserRoute.Delete("/:id", userUIDResolver, authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersDelete, userIDScope)), routing.Wrap(hs.AdminDeleteUser))
|
||||
adminUserRoute.Post("/:id/disable", userUIDResolver, authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersDisable, userIDScope)), routing.Wrap(hs.AdminDisableUser))
|
||||
adminUserRoute.Post("/:id/enable", userUIDResolver, authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersEnable, userIDScope)), routing.Wrap(hs.AdminEnableUser))
|
||||
adminUserRoute.Get("/:id/quotas", userUIDResolver, authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersQuotasList, userIDScope)), routing.Wrap(hs.GetUserQuotas))
|
||||
adminUserRoute.Put("/:id/quotas/:target", userUIDResolver, authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersQuotasUpdate, userIDScope)), routing.Wrap(hs.UpdateUserQuota))
|
||||
|
||||
adminUserRoute.Post("/:id/logout", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersLogout, userIDScope)), routing.Wrap(hs.AdminLogoutUser))
|
||||
adminUserRoute.Get("/:id/auth-tokens", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersAuthTokenList, userIDScope)), routing.Wrap(hs.AdminGetUserAuthTokens))
|
||||
adminUserRoute.Post("/:id/revoke-auth-token", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersAuthTokenUpdate, userIDScope)), routing.Wrap(hs.AdminRevokeUserAuthToken))
|
||||
adminUserRoute.Post("/:id/logout", userUIDResolver, authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersLogout, userIDScope)), routing.Wrap(hs.AdminLogoutUser))
|
||||
adminUserRoute.Get("/:id/auth-tokens", userUIDResolver, authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersAuthTokenList, userIDScope)), routing.Wrap(hs.AdminGetUserAuthTokens))
|
||||
adminUserRoute.Post("/:id/revoke-auth-token", userUIDResolver, authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionUsersAuthTokenUpdate, userIDScope)), routing.Wrap(hs.AdminRevokeUserAuthToken))
|
||||
}, reqSignedIn)
|
||||
|
||||
// rendering
|
||||
@ -606,3 +613,23 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/api/snapshots-delete/:deleteKey", reqSnapshotPublicModeOrSignedIn, routing.Wrap(hs.DeleteDashboardSnapshotByDeleteKey))
|
||||
r.Delete("/api/snapshots/:key", reqSignedIn, routing.Wrap(hs.DeleteDashboardSnapshot))
|
||||
}
|
||||
|
||||
func middlewareUserUIDResolver(userService user.Service, paramName string) web.Handler {
|
||||
handler := user.UIDToIDHandler(userService)
|
||||
|
||||
return func(c *contextmodel.ReqContext) {
|
||||
userID := web.Params(c.Req)[paramName]
|
||||
id, err := handler(c.Req.Context(), userID)
|
||||
if err == nil {
|
||||
gotParams := web.Params(c.Req)
|
||||
gotParams[paramName] = id
|
||||
web.SetURLParams(c.Req, gotParams)
|
||||
} else {
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
c.JsonApiErr(http.StatusNotFound, "User not found", nil)
|
||||
} else {
|
||||
c.JsonApiErr(http.StatusInternalServerError, "Failed to resolve user", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -856,10 +856,10 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
|
||||
// #TODO refactor the various conversions of the folder so that we either set created by in folder.Folder or
|
||||
// we convert from unstructured to folder DTO without an intermediate conversion to folder.Folder
|
||||
if len(fDTO.CreatedBy) > 0 {
|
||||
creator = fk8s.getUserLogin(ctx, toUID(fDTO.CreatedBy), orgID)
|
||||
creator = fk8s.getUserLogin(ctx, toUID(fDTO.CreatedBy))
|
||||
}
|
||||
if len(fDTO.UpdatedBy) > 0 {
|
||||
updater = fk8s.getUserLogin(ctx, toUID(fDTO.UpdatedBy), orgID)
|
||||
updater = fk8s.getUserLogin(ctx, toUID(fDTO.UpdatedBy))
|
||||
}
|
||||
|
||||
acMetadata, _ := fk8s.getFolderACMetadata(c, fold)
|
||||
@ -930,13 +930,12 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un
|
||||
return folderDTO, nil
|
||||
}
|
||||
|
||||
func (fk8s *folderK8sHandler) getUserLogin(ctx context.Context, userUID string, orgID int64) string {
|
||||
func (fk8s *folderK8sHandler) getUserLogin(ctx context.Context, userUID string) string {
|
||||
ctx, span := tracer.Start(ctx, "api.getUserLogin")
|
||||
defer span.End()
|
||||
|
||||
query := user.GetUserByUIDQuery{
|
||||
UID: userUID,
|
||||
OrgID: orgID,
|
||||
UID: userUID,
|
||||
}
|
||||
user, err := fk8s.userService.GetByUID(ctx, &query)
|
||||
if err != nil {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package resourcepermissions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@ -14,6 +15,7 @@ import (
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
@ -44,6 +46,7 @@ func (a *api) registerEndpoints() {
|
||||
licenseMW = nopMiddleware
|
||||
}
|
||||
|
||||
userUIDResolver := middlewareUserUIDResolver(a.service.userService, ":userID")
|
||||
teamUIDResolver := team.MiddlewareTeamUIDResolver(a.service.teamService, ":teamID")
|
||||
resourceResolver := func(resTranslator ResourceTranslator) web.Handler {
|
||||
return func(c *contextmodel.ReqContext) {
|
||||
@ -72,7 +75,7 @@ func (a *api) registerEndpoints() {
|
||||
r.Get("/:resourceID", resourceResolver, auth(accesscontrol.EvalPermission(actionRead, scope)), routing.Wrap(a.getPermissions))
|
||||
r.Post("/:resourceID", resourceResolver, licenseMW, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setPermissions))
|
||||
if a.service.options.Assignments.Users {
|
||||
r.Post("/:resourceID/users/:userID", licenseMW, resourceResolver, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setUserPermission))
|
||||
r.Post("/:resourceID/users/:userID", licenseMW, resourceResolver, userUIDResolver, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setUserPermission))
|
||||
}
|
||||
if a.service.options.Assignments.Teams {
|
||||
r.Post("/:resourceID/teams/:teamID", licenseMW, resourceResolver, teamUIDResolver, auth(accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setTeamPermission))
|
||||
@ -445,3 +448,24 @@ func permissionSetResponse(cmd setPermissionCommand) response.Response {
|
||||
}
|
||||
return response.Success(message)
|
||||
}
|
||||
|
||||
// middlewareUserUIDResolver resolves the user UID to ID and sets the ID in the URL params.
|
||||
func middlewareUserUIDResolver(userService user.Service, paramName string) web.Handler {
|
||||
handler := user.UIDToIDHandler(userService)
|
||||
|
||||
return func(c *contextmodel.ReqContext) {
|
||||
userID := web.Params(c.Req)[paramName]
|
||||
id, err := handler(c.Req.Context(), userID)
|
||||
if err == nil {
|
||||
gotParams := web.Params(c.Req)
|
||||
gotParams[paramName] = id
|
||||
web.SetURLParams(c.Req, gotParams)
|
||||
} else {
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
c.JsonApiErr(http.StatusNotFound, "User not found", nil)
|
||||
} else {
|
||||
c.JsonApiErr(http.StatusInternalServerError, "Failed to resolve user", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,8 +213,7 @@ type GetUserByIDQuery struct {
|
||||
}
|
||||
|
||||
type GetUserByUIDQuery struct {
|
||||
OrgID int64
|
||||
UID string
|
||||
UID string
|
||||
}
|
||||
|
||||
type StartVerifyEmailCommand struct {
|
||||
@ -264,6 +263,7 @@ const (
|
||||
|
||||
type AdminCreateUserResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
UID string `json:"uid"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
)
|
||||
@ -13,6 +14,7 @@ type Service interface {
|
||||
CreateServiceAccount(context.Context, *CreateUserCommand) (*User, error)
|
||||
Delete(context.Context, *DeleteUserCommand) error
|
||||
GetByID(context.Context, *GetUserByIDQuery) (*User, error)
|
||||
// GetByUID returns a user by UID. This also includes service accounts (identity use only)
|
||||
GetByUID(context.Context, *GetUserByUIDQuery) (*User, error)
|
||||
GetByLogin(context.Context, *GetUserByLoginQuery) (*User, error)
|
||||
GetByEmail(context.Context, *GetUserByEmailQuery) (*User, error)
|
||||
@ -28,3 +30,16 @@ type Verifier interface {
|
||||
Start(ctx context.Context, cmd StartVerifyEmailCommand) error
|
||||
Complete(ctx context.Context, cmd CompleteEmailVerifyCommand) error
|
||||
}
|
||||
|
||||
func UIDToIDHandler(userService Service) func(ctx context.Context, userID string) (string, error) {
|
||||
return func(ctx context.Context, userID string) (string, error) {
|
||||
_, err := strconv.ParseInt(userID, 10, 64)
|
||||
if userID == "" || err == nil {
|
||||
return userID, nil
|
||||
}
|
||||
user, err := userService.GetByUID(ctx, &GetUserByUIDQuery{
|
||||
UID: userID,
|
||||
})
|
||||
return strconv.FormatInt(user.ID, 10), err
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
type store interface {
|
||||
Insert(context.Context, *user.User) (int64, error)
|
||||
GetByID(context.Context, int64) (*user.User, error)
|
||||
GetByUID(ctx context.Context, orgId int64, uid string) (*user.User, error)
|
||||
GetByUID(ctx context.Context, uid string) (*user.User, error)
|
||||
GetByLogin(context.Context, *user.GetUserByLoginQuery) (*user.User, error)
|
||||
GetByEmail(context.Context, *user.GetUserByEmailQuery) (*user.User, error)
|
||||
Delete(context.Context, int64) error
|
||||
@ -108,14 +108,11 @@ func (ss *sqlStore) GetByID(ctx context.Context, userID int64) (*user.User, erro
|
||||
return &usr, err
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetByUID(ctx context.Context, orgId int64, uid string) (*user.User, error) {
|
||||
func (ss *sqlStore) GetByUID(ctx context.Context, uid string) (*user.User, error) {
|
||||
var usr user.User
|
||||
|
||||
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
has, err := sess.Table("user").
|
||||
Where("org_id = ? AND uid = ?", orgId, uid).
|
||||
Get(&usr)
|
||||
|
||||
has, err := sess.Table("user").Where("uid = ?", uid).Get(&usr)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
|
@ -99,6 +99,12 @@ func TestIntegrationUserDataAccess(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "abcd", siu.UserUID)
|
||||
|
||||
query := user.GetUserByUIDQuery{UID: "abcd"}
|
||||
result, err := userStore.GetByUID(context.Background(), query.UID)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, result.UID, "abcd")
|
||||
require.Equal(t, result.Email, "next-test@email.com")
|
||||
})
|
||||
|
||||
t.Run("Testing DB - creates and loads user", func(t *testing.T) {
|
||||
|
@ -214,12 +214,11 @@ func (s *Service) GetByID(ctx context.Context, query *user.GetUserByIDQuery) (*u
|
||||
|
||||
func (s *Service) GetByUID(ctx context.Context, query *user.GetUserByUIDQuery) (*user.User, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "user.GetByUID", trace.WithAttributes(
|
||||
attribute.Int64("orgID", query.OrgID),
|
||||
attribute.String("userUID", query.UID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return s.store.GetByUID(ctx, query.OrgID, query.UID)
|
||||
return s.store.GetByUID(ctx, query.UID)
|
||||
}
|
||||
|
||||
func (s *Service) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) {
|
||||
|
@ -291,7 +291,7 @@ func (f *FakeUserStore) GetByID(context.Context, int64) (*user.User, error) {
|
||||
return f.ExpectedUser, f.ExpectedError
|
||||
}
|
||||
|
||||
func (f *FakeUserStore) GetByUID(context.Context, int64, string) (*user.User, error) {
|
||||
func (f *FakeUserStore) GetByUID(context.Context, string) (*user.User, error) {
|
||||
return f.ExpectedUser, f.ExpectedError
|
||||
}
|
||||
|
||||
|
@ -2717,6 +2717,9 @@
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -12439,6 +12439,9 @@
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -61,31 +61,30 @@ export const UserAdminPage = ({
|
||||
}: Props) => {
|
||||
const { id = '' } = useParams();
|
||||
useEffect(() => {
|
||||
const userId = parseInt(id, 10);
|
||||
loadAdminUserPage(userId);
|
||||
loadAdminUserPage(id);
|
||||
}, [id, loadAdminUserPage]);
|
||||
|
||||
const onPasswordChange = (password: string) => {
|
||||
if (user) {
|
||||
setUserPassword(user.id, password);
|
||||
setUserPassword(user.uid, password);
|
||||
}
|
||||
};
|
||||
|
||||
const onGrafanaAdminChange = (isGrafanaAdmin: boolean) => {
|
||||
if (user) {
|
||||
updateUserPermissions(user.id, isGrafanaAdmin);
|
||||
updateUserPermissions(user.uid, isGrafanaAdmin);
|
||||
}
|
||||
};
|
||||
|
||||
const onOrgRemove = (orgId: number) => {
|
||||
if (user) {
|
||||
deleteOrgUser(user.id, orgId);
|
||||
deleteOrgUser(user.uid, orgId);
|
||||
}
|
||||
};
|
||||
|
||||
const onOrgRoleChange = (orgId: number, newRole: string) => {
|
||||
if (user) {
|
||||
updateOrgUserRole(user.id, orgId, newRole);
|
||||
updateOrgUserRole(user.uid, orgId, newRole);
|
||||
}
|
||||
};
|
||||
|
||||
@ -97,19 +96,19 @@ export const UserAdminPage = ({
|
||||
|
||||
const onSessionRevoke = (tokenId: number) => {
|
||||
if (user) {
|
||||
revokeSession(tokenId, user.id);
|
||||
revokeSession(tokenId, user.uid);
|
||||
}
|
||||
};
|
||||
|
||||
const onAllSessionsRevoke = () => {
|
||||
if (user) {
|
||||
revokeAllSessions(user.id);
|
||||
revokeAllSessions(user.uid);
|
||||
}
|
||||
};
|
||||
|
||||
const onUserSync = () => {
|
||||
if (user) {
|
||||
syncLdapUser(user.id);
|
||||
syncLdapUser(user.id, user.uid);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -33,9 +33,9 @@ const UserCreatePage = () => {
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (data: UserDTO) => {
|
||||
const { id } = await createUser(data);
|
||||
const { uid } = await createUser(data);
|
||||
|
||||
navigate(`/admin/users/edit/${id}`);
|
||||
navigate(`/admin/users/edit/${uid}`);
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
|
@ -10,9 +10,9 @@ interface Props {
|
||||
user: UserDTO;
|
||||
|
||||
onUserUpdate: (user: UserDTO) => void;
|
||||
onUserDelete: (userId: number) => void;
|
||||
onUserDisable: (userId: number) => void;
|
||||
onUserEnable: (userId: number) => void;
|
||||
onUserDelete: (userUid: string) => void;
|
||||
onUserDisable: (userUid: string) => void;
|
||||
onUserEnable: (userUid: string) => void;
|
||||
onPasswordChange(password: string): void;
|
||||
}
|
||||
|
||||
@ -43,11 +43,11 @@ export function UserProfile({
|
||||
}
|
||||
};
|
||||
|
||||
const handleUserDelete = () => onUserDelete(user.id);
|
||||
const handleUserDelete = () => onUserDelete(user.uid);
|
||||
|
||||
const handleUserDisable = () => onUserDisable(user.id);
|
||||
const handleUserDisable = () => onUserDisable(user.uid);
|
||||
|
||||
const handleUserEnable = () => onUserEnable(user.id);
|
||||
const handleUserEnable = () => onUserEnable(user.uid);
|
||||
|
||||
const onUserNameChange = (newValue: string) => {
|
||||
onUserUpdate({
|
||||
|
@ -54,7 +54,7 @@ export const UsersTable = ({
|
||||
header: 'Login',
|
||||
cell: ({ row: { original } }: Cell<'login'>) => {
|
||||
return (
|
||||
<TextLink color="primary" inline={false} href={`/admin/users/edit/${original.id}`} title="Edit user">
|
||||
<TextLink color="primary" inline={false} href={`/admin/users/edit/${original.uid}`} title="Edit user">
|
||||
{original.login}
|
||||
</TextLink>
|
||||
);
|
||||
@ -146,7 +146,7 @@ export const UsersTable = ({
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
icon="pen"
|
||||
href={`admin/users/edit/${original.id}`}
|
||||
href={`admin/users/edit/${original.uid}`}
|
||||
aria-label={`Edit user ${original.name}`}
|
||||
tooltip={'Edit user'}
|
||||
/>
|
||||
|
@ -43,13 +43,13 @@ import {
|
||||
} from './reducers';
|
||||
// UserAdminPage
|
||||
|
||||
export function loadAdminUserPage(userId: number): ThunkResult<void> {
|
||||
export function loadAdminUserPage(userUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
try {
|
||||
dispatch(userAdminPageLoadedAction(false));
|
||||
await dispatch(loadUserProfile(userId));
|
||||
await dispatch(loadUserOrgs(userId));
|
||||
await dispatch(loadUserSessions(userId));
|
||||
await dispatch(loadUserProfile(userUid));
|
||||
await dispatch(loadUserOrgs(userUid));
|
||||
await dispatch(loadUserSessions(userUid));
|
||||
if (config.ldapEnabled && featureEnabled('ldapsync')) {
|
||||
await dispatch(loadLdapSyncStatus());
|
||||
}
|
||||
@ -69,60 +69,60 @@ export function loadAdminUserPage(userId: number): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
export function loadUserProfile(userId: number): ThunkResult<void> {
|
||||
export function loadUserProfile(userUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
const user = await getBackendSrv().get(`/api/users/${userId}`, accessControlQueryParam());
|
||||
const user = await getBackendSrv().get(`/api/users/${userUid}`, accessControlQueryParam());
|
||||
dispatch(userProfileLoadedAction(user));
|
||||
};
|
||||
}
|
||||
|
||||
export function updateUser(user: UserDTO): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await getBackendSrv().put(`/api/users/${user.id}`, user);
|
||||
dispatch(loadAdminUserPage(user.id));
|
||||
await getBackendSrv().put(`/api/users/${user.uid}`, user);
|
||||
dispatch(loadAdminUserPage(user.uid));
|
||||
};
|
||||
}
|
||||
|
||||
export function setUserPassword(userId: number, password: string): ThunkResult<void> {
|
||||
export function setUserPassword(userUid: string, password: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
const payload = { password };
|
||||
await getBackendSrv().put(`/api/admin/users/${userId}/password`, payload);
|
||||
dispatch(loadAdminUserPage(userId));
|
||||
await getBackendSrv().put(`/api/admin/users/${userUid}/password`, payload);
|
||||
dispatch(loadAdminUserPage(userUid));
|
||||
};
|
||||
}
|
||||
|
||||
export function disableUser(userId: number): ThunkResult<void> {
|
||||
export function disableUser(userUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await getBackendSrv().post(`/api/admin/users/${userId}/disable`);
|
||||
await getBackendSrv().post(`/api/admin/users/${userUid}/disable`);
|
||||
locationService.push('/admin/users');
|
||||
};
|
||||
}
|
||||
|
||||
export function enableUser(userId: number): ThunkResult<void> {
|
||||
export function enableUser(userUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await getBackendSrv().post(`/api/admin/users/${userId}/enable`);
|
||||
dispatch(loadAdminUserPage(userId));
|
||||
await getBackendSrv().post(`/api/admin/users/${userUid}/enable`);
|
||||
dispatch(loadAdminUserPage(userUid));
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteUser(userId: number): ThunkResult<void> {
|
||||
export function deleteUser(userUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await getBackendSrv().delete(`/api/admin/users/${userId}`);
|
||||
await getBackendSrv().delete(`/api/admin/users/${userUid}`);
|
||||
locationService.push('/admin/users');
|
||||
};
|
||||
}
|
||||
|
||||
export function updateUserPermissions(userId: number, isGrafanaAdmin: boolean): ThunkResult<void> {
|
||||
export function updateUserPermissions(userUid: string, isGrafanaAdmin: boolean): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
const payload = { isGrafanaAdmin };
|
||||
await getBackendSrv().put(`/api/admin/users/${userId}/permissions`, payload);
|
||||
dispatch(loadAdminUserPage(userId));
|
||||
await getBackendSrv().put(`/api/admin/users/${userUid}/permissions`, payload);
|
||||
dispatch(loadAdminUserPage(userUid));
|
||||
};
|
||||
}
|
||||
|
||||
export function loadUserOrgs(userId: number): ThunkResult<void> {
|
||||
export function loadUserOrgs(userUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
const orgs = await getBackendSrv().get(`/api/users/${userId}/orgs`);
|
||||
const orgs = await getBackendSrv().get(`/api/users/${userUid}/orgs`);
|
||||
dispatch(userOrgsLoadedAction(orgs));
|
||||
};
|
||||
}
|
||||
@ -134,32 +134,32 @@ export function addOrgUser(user: UserDTO, orgId: number, role: string): ThunkRes
|
||||
role: role,
|
||||
};
|
||||
await getBackendSrv().post(`/api/orgs/${orgId}/users/`, payload);
|
||||
dispatch(loadAdminUserPage(user.id));
|
||||
dispatch(loadAdminUserPage(user.uid));
|
||||
};
|
||||
}
|
||||
|
||||
export function updateOrgUserRole(userId: number, orgId: number, role: string): ThunkResult<void> {
|
||||
export function updateOrgUserRole(userUid: string, orgId: number, role: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
const payload = { role };
|
||||
await getBackendSrv().patch(`/api/orgs/${orgId}/users/${userId}`, payload);
|
||||
dispatch(loadAdminUserPage(userId));
|
||||
await getBackendSrv().patch(`/api/orgs/${orgId}/users/${userUid}`, payload);
|
||||
dispatch(loadAdminUserPage(userUid));
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteOrgUser(userId: number, orgId: number): ThunkResult<void> {
|
||||
export function deleteOrgUser(userUid: string, orgId: number): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await getBackendSrv().delete(`/api/orgs/${orgId}/users/${userId}`);
|
||||
dispatch(loadAdminUserPage(userId));
|
||||
await getBackendSrv().delete(`/api/orgs/${orgId}/users/${userUid}`);
|
||||
dispatch(loadAdminUserPage(userUid));
|
||||
};
|
||||
}
|
||||
|
||||
export function loadUserSessions(userId: number): ThunkResult<void> {
|
||||
export function loadUserSessions(userUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
if (!contextSrv.hasPermission(AccessControlAction.UsersAuthTokenList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tokens = await getBackendSrv().get(`/api/admin/users/${userId}/auth-tokens`);
|
||||
const tokens = await getBackendSrv().get(`/api/admin/users/${userUid}/auth-tokens`);
|
||||
tokens.reverse();
|
||||
|
||||
const sessions = tokens.map((session: UserSession) => {
|
||||
@ -182,18 +182,18 @@ export function loadUserSessions(userId: number): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
export function revokeSession(tokenId: number, userId: number): ThunkResult<void> {
|
||||
export function revokeSession(tokenId: number, userUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
const payload = { authTokenId: tokenId };
|
||||
await getBackendSrv().post(`/api/admin/users/${userId}/revoke-auth-token`, payload);
|
||||
dispatch(loadUserSessions(userId));
|
||||
await getBackendSrv().post(`/api/admin/users/${userUid}/revoke-auth-token`, payload);
|
||||
dispatch(loadUserSessions(userUid));
|
||||
};
|
||||
}
|
||||
|
||||
export function revokeAllSessions(userId: number): ThunkResult<void> {
|
||||
export function revokeAllSessions(userUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await getBackendSrv().post(`/api/admin/users/${userId}/logout`);
|
||||
dispatch(loadUserSessions(userId));
|
||||
await getBackendSrv().post(`/api/admin/users/${userUid}/logout`);
|
||||
dispatch(loadUserSessions(userUid));
|
||||
};
|
||||
}
|
||||
|
||||
@ -210,10 +210,10 @@ export function loadLdapSyncStatus(): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
export function syncLdapUser(userId: number): ThunkResult<void> {
|
||||
export function syncLdapUser(userId: number, userUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await getBackendSrv().post(`/api/admin/ldap/sync/${userId}`);
|
||||
dispatch(loadAdminUserPage(userId));
|
||||
dispatch(loadAdminUserPage(userUid));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -56,6 +56,7 @@ const getTestUserMapping = (): LdapUser => ({
|
||||
|
||||
const getTestUser = (): UserDTO => ({
|
||||
id: 1,
|
||||
uid: 'aaaaaa',
|
||||
email: 'user@localhost',
|
||||
login: 'user',
|
||||
name: 'User',
|
||||
|
@ -13,6 +13,7 @@ const defaultProps: Props = {
|
||||
...initialUserState,
|
||||
user: {
|
||||
id: 1,
|
||||
uid: 'aaaaaa',
|
||||
name: 'Test User',
|
||||
email: 'test@test.com',
|
||||
login: 'test',
|
||||
|
@ -31,6 +31,7 @@ const defaultProps: Props = {
|
||||
...initialUserState,
|
||||
user: {
|
||||
id: 1,
|
||||
uid: 'aaaaaa',
|
||||
name: 'Test User',
|
||||
email: 'test@test.com',
|
||||
login: 'test',
|
||||
|
@ -62,6 +62,7 @@ describe('userReducer', () => {
|
||||
userLoaded({
|
||||
user: {
|
||||
id: 2021,
|
||||
uid: 'aaaaaa',
|
||||
email: 'test@test.com',
|
||||
isDisabled: true,
|
||||
login: 'test',
|
||||
@ -74,6 +75,7 @@ describe('userReducer', () => {
|
||||
...initialUserState,
|
||||
user: {
|
||||
id: 2021,
|
||||
uid: 'aaaaaa',
|
||||
email: 'test@test.com',
|
||||
isDisabled: true,
|
||||
login: 'test',
|
||||
|
@ -34,6 +34,7 @@ export type Unit = { name: string; url: string };
|
||||
|
||||
export interface UserDTO extends WithAccessControlMetadata {
|
||||
id: number;
|
||||
uid: string;
|
||||
login: string;
|
||||
email: string;
|
||||
name: string;
|
||||
|
@ -2400,6 +2400,9 @@
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
Loading…
Reference in New Issue
Block a user