Identity: Add read-only identity apiserver (#90418)
This commit is contained in:
@@ -98,6 +98,19 @@ type UpdateUserLastSeenAtCommand struct {
|
||||
OrgID int64
|
||||
}
|
||||
|
||||
type ListUsersCommand struct {
|
||||
OrgID int64
|
||||
Limit int64
|
||||
ContinueID int64
|
||||
IsServiceAccount bool
|
||||
}
|
||||
|
||||
type ListUserResult struct {
|
||||
Users []*User
|
||||
ContinueID int64
|
||||
RV int64
|
||||
}
|
||||
|
||||
type SearchUsersQuery struct {
|
||||
SignedInUser identity.Requester
|
||||
OrgID int64 `xorm:"org_id"`
|
||||
@@ -120,7 +133,7 @@ type SearchUserQueryResult struct {
|
||||
|
||||
type UserSearchHitDTO struct {
|
||||
ID int64 `json:"id" xorm:"id"`
|
||||
UID string `json:"uid" xorm:"id"`
|
||||
UID string `json:"uid" xorm:"uid"`
|
||||
Name string `json:"name"`
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
@@ -206,6 +219,11 @@ type GetUserByIDQuery struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
type GetUserByUIDQuery struct {
|
||||
OrgID int64
|
||||
UID string
|
||||
}
|
||||
|
||||
type StartVerifyEmailCommand struct {
|
||||
User User
|
||||
Email string
|
||||
|
||||
@@ -13,8 +13,10 @@ type Service interface {
|
||||
CreateServiceAccount(context.Context, *CreateUserCommand) (*User, error)
|
||||
Delete(context.Context, *DeleteUserCommand) error
|
||||
GetByID(context.Context, *GetUserByIDQuery) (*User, error)
|
||||
GetByUID(context.Context, *GetUserByUIDQuery) (*User, error)
|
||||
GetByLogin(context.Context, *GetUserByLoginQuery) (*User, error)
|
||||
GetByEmail(context.Context, *GetUserByEmailQuery) (*User, error)
|
||||
List(context.Context, *ListUsersCommand) (*ListUserResult, error)
|
||||
Update(context.Context, *UpdateUserCommand) error
|
||||
UpdateLastSeenAt(context.Context, *UpdateUserLastSeenAtCommand) error
|
||||
GetSignedInUser(context.Context, *GetSignedInUserQuery) (*SignedInUser, error)
|
||||
|
||||
@@ -20,8 +20,10 @@ 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)
|
||||
GetByLogin(context.Context, *user.GetUserByLoginQuery) (*user.User, error)
|
||||
GetByEmail(context.Context, *user.GetUserByEmailQuery) (*user.User, error)
|
||||
List(context.Context, *user.ListUsersCommand) (*user.ListUserResult, error)
|
||||
Delete(context.Context, int64) error
|
||||
LoginConflict(ctx context.Context, login, email string) error
|
||||
Update(context.Context, *user.UpdateUserCommand) error
|
||||
@@ -107,6 +109,24 @@ 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) {
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return user.ErrUserNotFound
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return &usr, err
|
||||
}
|
||||
|
||||
func (ss *sqlStore) notServiceAccountFilter() string {
|
||||
return fmt.Sprintf("%s.is_service_account = %s",
|
||||
ss.dialect.Quote("user"),
|
||||
@@ -506,7 +526,7 @@ func (ss *sqlStore) Search(ctx context.Context, query *user.SearchUsersQuery) (*
|
||||
sess.Limit(query.Limit, offset)
|
||||
}
|
||||
|
||||
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.Cols("u.id", "u.uid", "u.email", "u.name", "u.login", "u.is_admin", "u.is_disabled", "u.last_seen_at", "user_auth.auth_module")
|
||||
|
||||
if len(query.SortOpts) > 0 {
|
||||
for i := range query.SortOpts {
|
||||
@@ -559,6 +579,40 @@ func (ss *sqlStore) Search(ctx context.Context, query *user.SearchUsersQuery) (*
|
||||
return &result, err
|
||||
}
|
||||
|
||||
func (ss *sqlStore) List(ctx context.Context, query *user.ListUsersCommand) (*user.ListUserResult, error) {
|
||||
limit := int(query.Limit)
|
||||
if limit <= 0 {
|
||||
limit = 25
|
||||
}
|
||||
result := &user.ListUserResult{
|
||||
Users: make([]*user.User, 0),
|
||||
}
|
||||
max := ""
|
||||
err := ss.db.WithDbSession(ctx, func(dbSess *db.Session) error {
|
||||
sess := dbSess.Table("user")
|
||||
sess.Where("id >= ? AND is_service_account = ?", query.ContinueID, query.IsServiceAccount)
|
||||
err := sess.OrderBy("id asc").Limit(limit + 1).Find(&result.Users)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the revision version
|
||||
_, err = dbSess.Table("user").Select("MAX(updated)").Get(&max)
|
||||
return err
|
||||
})
|
||||
if max != "" {
|
||||
t, err := time.Parse(time.DateTime, max)
|
||||
if err == nil {
|
||||
result.RV = t.UnixMilli()
|
||||
}
|
||||
}
|
||||
if len(result.Users) > limit {
|
||||
result.ContinueID = result.Users[limit].ID
|
||||
result.Users = result.Users[:limit]
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func setOptional[T any](v *T, add func(v T)) {
|
||||
if v != nil {
|
||||
add(*v)
|
||||
|
||||
@@ -212,6 +212,16 @@ func (s *Service) GetByID(ctx context.Context, query *user.GetUserByIDQuery) (*u
|
||||
return s.store.GetByID(ctx, query.ID)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (s *Service) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "user.GetByLogin")
|
||||
defer span.End()
|
||||
@@ -368,6 +378,15 @@ func (s *Service) getSignedInUser(ctx context.Context, query *user.GetSignedInUs
|
||||
return usr, err
|
||||
}
|
||||
|
||||
func (s *Service) List(ctx context.Context, query *user.ListUsersCommand) (*user.ListUserResult, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "user.List", trace.WithAttributes(
|
||||
attribute.Int64("orgID", query.OrgID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return s.store.List(ctx, query)
|
||||
}
|
||||
|
||||
func (s *Service) Search(ctx context.Context, query *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "user.Search", trace.WithAttributes(
|
||||
attribute.Int64("orgID", query.OrgID),
|
||||
|
||||
@@ -291,6 +291,10 @@ 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) {
|
||||
return f.ExpectedUser, f.ExpectedError
|
||||
}
|
||||
|
||||
func (f *FakeUserStore) LoginConflict(context.Context, string, string) error {
|
||||
return f.ExpectedError
|
||||
}
|
||||
@@ -327,6 +331,10 @@ func (f *FakeUserStore) Search(ctx context.Context, query *user.SearchUsersQuery
|
||||
return f.ExpectedSearchUserQueryResult, f.ExpectedError
|
||||
}
|
||||
|
||||
func (f *FakeUserStore) List(ctx context.Context, query *user.ListUsersCommand) (*user.ListUserResult, error) {
|
||||
return nil, f.ExpectedError
|
||||
}
|
||||
|
||||
func (f *FakeUserStore) Count(ctx context.Context) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ type FakeUserService struct {
|
||||
ExpectedError error
|
||||
ExpectedSetUsingOrgError error
|
||||
ExpectedSearchUsers user.SearchUserQueryResult
|
||||
ExpectedListUsers user.ListUserResult
|
||||
ExpectedUserProfileDTO *user.UserProfileDTO
|
||||
ExpectedUserProfileDTOs []*user.UserProfileDTO
|
||||
ExpectedUsageStats map[string]any
|
||||
@@ -53,6 +54,10 @@ func (f *FakeUserService) GetByID(ctx context.Context, query *user.GetUserByIDQu
|
||||
return f.ExpectedUser, f.ExpectedError
|
||||
}
|
||||
|
||||
func (f *FakeUserService) GetByUID(ctx context.Context, query *user.GetUserByUIDQuery) (*user.User, error) {
|
||||
return f.ExpectedUser, f.ExpectedError
|
||||
}
|
||||
|
||||
func (f *FakeUserService) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) {
|
||||
return f.ExpectedUser, f.ExpectedError
|
||||
}
|
||||
@@ -93,6 +98,10 @@ func (f *FakeUserService) Search(ctx context.Context, query *user.SearchUsersQue
|
||||
return &f.ExpectedSearchUsers, f.ExpectedError
|
||||
}
|
||||
|
||||
func (f *FakeUserService) List(ctx context.Context, query *user.ListUsersCommand) (*user.ListUserResult, error) {
|
||||
return &f.ExpectedListUsers, f.ExpectedError
|
||||
}
|
||||
|
||||
func (f *FakeUserService) BatchDisableUsers(ctx context.Context, cmd *user.BatchDisableUsersCommand) error {
|
||||
if f.BatchDisableUsersFn != nil {
|
||||
return f.BatchDisableUsersFn(ctx, cmd)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
package usertest
|
||||
|
||||
@@ -200,6 +200,36 @@ func (_m *MockService) GetByLogin(_a0 context.Context, _a1 *user.GetUserByLoginQ
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetByUID provides a mock function with given fields: _a0, _a1
|
||||
func (_m *MockService) GetByUID(_a0 context.Context, _a1 *user.GetUserByUIDQuery) (*user.User, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetByUID")
|
||||
}
|
||||
|
||||
var r0 *user.User
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *user.GetUserByUIDQuery) (*user.User, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *user.GetUserByUIDQuery) *user.User); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*user.User)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *user.GetUserByUIDQuery) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetProfile provides a mock function with given fields: _a0, _a1
|
||||
func (_m *MockService) GetProfile(_a0 context.Context, _a1 *user.GetUserProfileQuery) (*user.UserProfileDTO, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
@@ -260,36 +290,6 @@ func (_m *MockService) GetSignedInUser(_a0 context.Context, _a1 *user.GetSignedI
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetSignedInUserWithCacheCtx provides a mock function with given fields: _a0, _a1
|
||||
func (_m *MockService) GetSignedInUserWithCacheCtx(_a0 context.Context, _a1 *user.GetSignedInUserQuery) (*user.SignedInUser, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSignedInUserWithCacheCtx")
|
||||
}
|
||||
|
||||
var r0 *user.SignedInUser
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *user.GetSignedInUserQuery) (*user.SignedInUser, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *user.GetSignedInUserQuery) *user.SignedInUser); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*user.SignedInUser)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *user.GetSignedInUserQuery) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetUsageStats provides a mock function with given fields: ctx
|
||||
func (_m *MockService) GetUsageStats(ctx context.Context) map[string]interface{} {
|
||||
ret := _m.Called(ctx)
|
||||
@@ -310,6 +310,36 @@ func (_m *MockService) GetUsageStats(ctx context.Context) map[string]interface{}
|
||||
return r0
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: _a0, _a1
|
||||
func (_m *MockService) List(_a0 context.Context, _a1 *user.ListUsersCommand) (*user.ListUserResult, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for List")
|
||||
}
|
||||
|
||||
var r0 *user.ListUserResult
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *user.ListUsersCommand) (*user.ListUserResult, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *user.ListUsersCommand) *user.ListUserResult); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*user.ListUserResult)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *user.ListUsersCommand) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Search provides a mock function with given fields: _a0, _a1
|
||||
func (_m *MockService) Search(_a0 context.Context, _a1 *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
Reference in New Issue
Block a user