mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
API: get list of users with additional auth info (#17305)
* batch disable users * batch revoke users tokens * split batch disable user and revoke token * API: get users with auth info and isExternal flag * fix tests for batch disable users * Users: refactor /api/users/search endpoint * Users: use alias for "user" table * Chore: add BatchDisableUsers() to the bus * Users: order user list by id explicitly * Users: return AuthModule from /api/users/:id endpoint * Users: do not return unused fields * Users: fix SearchUsers method after last changes * User: return auth module as array for future purposes * User: tests for SearchUsers() * User: return only latest auth module in SearchUsers() * User: fix JOIN, get only most recent auth module
This commit is contained in:
parent
40708bef10
commit
dad894f1cc
@ -28,6 +28,11 @@ func getUserUserProfile(userID int64) Response {
|
|||||||
return Error(500, "Failed to get user", err)
|
return Error(500, "Failed to get user", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAuthQuery := m.GetAuthInfoQuery{UserId: userID}
|
||||||
|
if err := bus.Dispatch(&getAuthQuery); err == nil {
|
||||||
|
query.Result.AuthModule = []string{getAuthQuery.Result.AuthModule}
|
||||||
|
}
|
||||||
|
|
||||||
return JSON(200, query.Result)
|
return JSON(200, query.Result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,29 +208,45 @@ func (user *SignedInUser) HasRole(role RoleType) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserProfileDTO struct {
|
type UserProfileDTO struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Login string `json:"login"`
|
Login string `json:"login"`
|
||||||
Theme string `json:"theme"`
|
Theme string `json:"theme"`
|
||||||
OrgId int64 `json:"orgId"`
|
OrgId int64 `json:"orgId"`
|
||||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||||
IsDisabled bool `json:"isDisabled"`
|
IsDisabled bool `json:"isDisabled"`
|
||||||
|
AuthModule []string `json:"authModule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserSearchHitDTO struct {
|
type UserSearchHitDTO struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Login string `json:"login"`
|
Login string `json:"login"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
AvatarUrl string `json:"avatarUrl"`
|
AvatarUrl string `json:"avatarUrl"`
|
||||||
IsAdmin bool `json:"isAdmin"`
|
IsAdmin bool `json:"isAdmin"`
|
||||||
IsDisabled bool `json:"isDisabled"`
|
IsDisabled bool `json:"isDisabled"`
|
||||||
LastSeenAt time.Time `json:"lastSeenAt"`
|
LastSeenAt time.Time `json:"lastSeenAt"`
|
||||||
LastSeenAtAge string `json:"lastSeenAtAge"`
|
LastSeenAtAge string `json:"lastSeenAtAge"`
|
||||||
|
AuthModule AuthModuleConversion `json:"authModule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserIdDTO struct {
|
type UserIdDTO struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// implement Conversion interface to define custom field mapping (xorm feature)
|
||||||
|
type AuthModuleConversion []string
|
||||||
|
|
||||||
|
func (auth *AuthModuleConversion) FromDB(data []byte) error {
|
||||||
|
auth_module := string(data)
|
||||||
|
*auth = []string{auth_module}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just a stub, we don't wanna write to database
|
||||||
|
func (auth *AuthModuleConversion) ToDB() ([]byte, error) {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
@ -435,7 +435,15 @@ func SearchUsers(query *models.SearchUsersQuery) error {
|
|||||||
|
|
||||||
whereConditions := make([]string, 0)
|
whereConditions := make([]string, 0)
|
||||||
whereParams := make([]interface{}, 0)
|
whereParams := make([]interface{}, 0)
|
||||||
sess := x.Table("user")
|
sess := x.Table("user").Alias("u")
|
||||||
|
|
||||||
|
// Join with only most recent auth module
|
||||||
|
joinCondition := `(
|
||||||
|
SELECT id from user_auth
|
||||||
|
WHERE user_auth.user_id = u.id
|
||||||
|
ORDER BY user_auth.created DESC `
|
||||||
|
joinCondition = "user_auth.id=" + joinCondition + dialect.Limit(1) + ")"
|
||||||
|
sess.Join("LEFT", "user_auth", joinCondition)
|
||||||
|
|
||||||
if query.OrgId > 0 {
|
if query.OrgId > 0 {
|
||||||
whereConditions = append(whereConditions, "org_id = ?")
|
whereConditions = append(whereConditions, "org_id = ?")
|
||||||
@ -450,7 +458,7 @@ func SearchUsers(query *models.SearchUsersQuery) error {
|
|||||||
if query.AuthModule != "" {
|
if query.AuthModule != "" {
|
||||||
whereConditions = append(
|
whereConditions = append(
|
||||||
whereConditions,
|
whereConditions,
|
||||||
`id IN (SELECT user_id
|
`u.id IN (SELECT user_id
|
||||||
FROM user_auth
|
FROM user_auth
|
||||||
WHERE auth_module=?)`,
|
WHERE auth_module=?)`,
|
||||||
)
|
)
|
||||||
@ -464,14 +472,15 @@ func SearchUsers(query *models.SearchUsersQuery) error {
|
|||||||
|
|
||||||
offset := query.Limit * (query.Page - 1)
|
offset := query.Limit * (query.Page - 1)
|
||||||
sess.Limit(query.Limit, offset)
|
sess.Limit(query.Limit, offset)
|
||||||
sess.Cols("id", "email", "name", "login", "is_admin", "is_disabled", "last_seen_at")
|
sess.Cols("u.id", "u.email", "u.name", "u.login", "u.is_admin", "u.is_disabled", "u.last_seen_at", "user_auth.auth_module")
|
||||||
|
sess.OrderBy("u.id")
|
||||||
if err := sess.Find(&query.Result.Users); err != nil {
|
if err := sess.Find(&query.Result.Users); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// get total
|
// get total
|
||||||
user := models.User{}
|
user := models.User{}
|
||||||
countSess := x.Table("user")
|
countSess := x.Table("user").Alias("u")
|
||||||
|
|
||||||
if len(whereConditions) > 0 {
|
if len(whereConditions) > 0 {
|
||||||
countSess.Where(strings.Join(whereConditions, " AND "), whereParams...)
|
countSess.Where(strings.Join(whereConditions, " AND "), whereParams...)
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
@ -253,6 +254,61 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("When searching users", func() {
|
||||||
|
// Find a user to set tokens on
|
||||||
|
login := "loginuser0"
|
||||||
|
|
||||||
|
// Calling GetUserByAuthInfoQuery on an existing user will populate an entry in the user_auth table
|
||||||
|
// Make the first log-in during the past
|
||||||
|
getTime = func() time.Time { return time.Now().AddDate(0, 0, -2) }
|
||||||
|
query := &models.GetUserByAuthInfoQuery{Login: login, AuthModule: "test1", AuthId: "test1"}
|
||||||
|
err = GetUserByAuthInfo(query)
|
||||||
|
getTime = time.Now
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(query.Result.Login, ShouldEqual, login)
|
||||||
|
|
||||||
|
// Add a second auth module for this user
|
||||||
|
// Have this module's last log-in be more recent
|
||||||
|
getTime = func() time.Time { return time.Now().AddDate(0, 0, -1) }
|
||||||
|
query = &models.GetUserByAuthInfoQuery{Login: login, AuthModule: "test2", AuthId: "test2"}
|
||||||
|
err = GetUserByAuthInfo(query)
|
||||||
|
getTime = time.Now
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(query.Result.Login, ShouldEqual, login)
|
||||||
|
|
||||||
|
Convey("Should return the only most recently used auth_module", func() {
|
||||||
|
searchUserQuery := &models.SearchUsersQuery{}
|
||||||
|
err = SearchUsers(searchUserQuery)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(searchUserQuery.Result.Users, ShouldHaveLength, 5)
|
||||||
|
for _, user := range searchUserQuery.Result.Users {
|
||||||
|
if user.Login == login {
|
||||||
|
So(user.AuthModule, ShouldHaveLength, 1)
|
||||||
|
So(user.AuthModule[0], ShouldEqual, "test2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "log in" again with the first auth module
|
||||||
|
updateAuthCmd := &models.UpdateAuthInfoCommand{UserId: query.Result.Id, AuthModule: "test1", AuthId: "test1"}
|
||||||
|
err = UpdateAuthInfo(updateAuthCmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
searchUserQuery = &models.SearchUsersQuery{}
|
||||||
|
err = SearchUsers(searchUserQuery)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
for _, user := range searchUserQuery.Result.Users {
|
||||||
|
if user.Login == login {
|
||||||
|
So(user.AuthModule, ShouldHaveLength, 1)
|
||||||
|
So(user.AuthModule[0], ShouldEqual, "test1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Given one grafana admin user", func() {
|
Convey("Given one grafana admin user", func() {
|
||||||
|
Loading…
Reference in New Issue
Block a user