Chore: restructure legacy store for identity (#92572)

* Restructure user queries

* restructure display query

* restructure team queries

* restructure team bindings query

* Restructure team members

* Restructure store
This commit is contained in:
Karl Persson 2024-08-28 14:15:26 +02:00 committed by GitHub
parent 965a9e2d79
commit 1eb49e1b0f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 375 additions and 349 deletions

View File

@ -0,0 +1,53 @@
package legacy
import (
"context"
"fmt"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
type ListDisplayQuery struct {
OrgID int64
UIDs []string
IDs []int64
}
var sqlQueryDisplayTemplate = mustTemplate("display_query.sql")
func newListDisplay(sql *legacysql.LegacyDatabaseHelper, q *ListDisplayQuery) listDisplayQuery {
return listDisplayQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
UserTable: sql.Table("user"),
OrgUserTable: sql.Table("org_user"),
Query: q,
}
}
type listDisplayQuery struct {
sqltemplate.SQLTemplate
Query *ListDisplayQuery
UserTable string
OrgUserTable string
}
func (r listDisplayQuery) Validate() error {
return nil // TODO
}
// GetDisplay implements LegacyIdentityStore.
func (s *legacySQLStore) ListDisplay(ctx context.Context, ns claims.NamespaceInfo, query ListDisplayQuery) (*ListUserResult, error) {
query.OrgID = ns.OrgID
if ns.OrgID == 0 {
return nil, fmt.Errorf("expected non zero org id")
}
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
return s.queryUsers(ctx, sql, sqlQueryDisplayTemplate, newListDisplay(sql, &query), 10000)
}

View File

@ -1,136 +0,0 @@
package legacy
import (
"embed"
"fmt"
"text/template"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
// Templates setup.
var (
//go:embed *.sql
sqlTemplatesFS embed.FS
sqlTemplates = template.Must(template.New("sql").ParseFS(sqlTemplatesFS, `*.sql`))
)
func mustTemplate(filename string) *template.Template {
if t := sqlTemplates.Lookup(filename); t != nil {
return t
}
panic(fmt.Sprintf("template file not found: %s", filename))
}
// Templates.
var (
sqlQueryTeams = mustTemplate("query_teams.sql")
sqlQueryUsers = mustTemplate("query_users.sql")
sqlQueryDisplay = mustTemplate("query_display.sql")
sqlQueryTeamBindings = mustTemplate("query_team_bindings.sql")
sqlQueryTeamMembers = mustTemplate("query_team_members.sql")
)
type sqlQueryListUsers struct {
sqltemplate.SQLTemplate
Query *ListUserQuery
UserTable string
OrgUserTable string
}
func newListUser(sql *legacysql.LegacyDatabaseHelper, q *ListUserQuery) sqlQueryListUsers {
return sqlQueryListUsers{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
UserTable: sql.Table("user"),
OrgUserTable: sql.Table("org_user"),
Query: q,
}
}
func (r sqlQueryListUsers) Validate() error {
return nil // TODO
}
type sqlQueryListTeams struct {
sqltemplate.SQLTemplate
Query *ListTeamQuery
TeamTable string
}
func newListTeams(sql *legacysql.LegacyDatabaseHelper, q *ListTeamQuery) sqlQueryListTeams {
return sqlQueryListTeams{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
TeamTable: sql.Table("team"),
Query: q,
}
}
func (r sqlQueryListTeams) Validate() error {
return nil // TODO
}
type sqlQueryGetDisplay struct {
sqltemplate.SQLTemplate
Query *GetUserDisplayQuery
UserTable string
OrgUserTable string
}
func newGetDisplay(sql *legacysql.LegacyDatabaseHelper, q *GetUserDisplayQuery) sqlQueryGetDisplay {
return sqlQueryGetDisplay{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
UserTable: sql.Table("user"),
OrgUserTable: sql.Table("org_user"),
Query: q,
}
}
func (r sqlQueryGetDisplay) Validate() error {
return nil // TODO
}
type sqlQueryListTeamBindings struct {
sqltemplate.SQLTemplate
Query *ListTeamBindingsQuery
UserTable string
TeamTable string
TeamMemberTable string
}
func (r sqlQueryListTeamBindings) Validate() error {
return nil // TODO
}
func newListTeamBindings(sql *legacysql.LegacyDatabaseHelper, q *ListTeamBindingsQuery) sqlQueryListTeamBindings {
return sqlQueryListTeamBindings{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
UserTable: sql.Table("user"),
TeamTable: sql.Table("team"),
TeamMemberTable: sql.Table("team_member"),
Query: q,
}
}
type sqlQueryListTeamMembers struct {
sqltemplate.SQLTemplate
Query *ListTeamMembersQuery
UserTable string
TeamTable string
TeamMemberTable string
}
func (r sqlQueryListTeamMembers) Validate() error {
return nil // TODO
}
func newListTeamMembers(sql *legacysql.LegacyDatabaseHelper, q *ListTeamMembersQuery) sqlQueryListTeamMembers {
return sqlQueryListTeamMembers{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
UserTable: sql.Table("user"),
TeamTable: sql.Table("team"),
TeamMemberTable: sql.Table("team_member"),
Query: q,
}
}

View File

@ -0,0 +1,54 @@
package legacy
import (
"context"
"embed"
"fmt"
"text/template"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/storage/legacysql"
)
// In every case, RBAC should be applied before calling, or before returning results to the requester
type LegacyIdentityStore interface {
ListDisplay(ctx context.Context, ns claims.NamespaceInfo, query ListDisplayQuery) (*ListUserResult, error)
ListUsers(ctx context.Context, ns claims.NamespaceInfo, query ListUserQuery) (*ListUserResult, error)
ListTeams(ctx context.Context, ns claims.NamespaceInfo, query ListTeamQuery) (*ListTeamResult, error)
ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error)
ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error)
GetUserTeams(ctx context.Context, ns claims.NamespaceInfo, uid string) ([]team.Team, error)
}
var (
_ LegacyIdentityStore = (*legacySQLStore)(nil)
)
func NewLegacySQLStores(sql legacysql.LegacyDatabaseProvider) LegacyIdentityStore {
return &legacySQLStore{
sql: sql,
}
}
type legacySQLStore struct {
sql legacysql.LegacyDatabaseProvider
}
// Templates setup.
var (
//go:embed *.sql
sqlTemplatesFS embed.FS
sqlTemplates = template.Must(template.New("sql").ParseFS(sqlTemplatesFS, `*.sql`))
)
func mustTemplate(filename string) *template.Template {
if t := sqlTemplates.Lookup(filename); t != nil {
return t
}
panic(fmt.Sprintf("template file not found: %s", filename))
}

View File

@ -18,8 +18,8 @@ func TestIdentityQueries(t *testing.T) {
},
}
getDisplay := func(q *GetUserDisplayQuery) sqltemplate.SQLTemplate {
v := newGetDisplay(nodb, q)
getDisplay := func(q *ListDisplayQuery) sqltemplate.SQLTemplate {
v := newListDisplay(nodb, q)
v.SQLTemplate = mocks.NewTestingSQLTemplate()
return &v
}
@ -51,7 +51,7 @@ func TestIdentityQueries(t *testing.T) {
mocks.CheckQuerySnapshots(t, mocks.TemplateTestSetup{
RootDir: "testdata",
Templates: map[*template.Template][]mocks.TemplateTestCase{
sqlQueryTeams: {
sqlQueryTeamsTemplate: {
{
Name: "teams_uid",
Data: listTeams(&ListTeamQuery{
@ -75,7 +75,7 @@ func TestIdentityQueries(t *testing.T) {
}),
},
},
sqlQueryUsers: {
sqlQueryUsersTemplate: {
{
Name: "users_uid",
Data: listUsers(&ListUserQuery{
@ -99,31 +99,31 @@ func TestIdentityQueries(t *testing.T) {
}),
},
},
sqlQueryDisplay: {
sqlQueryDisplayTemplate: {
{
Name: "display_uids",
Data: getDisplay(&GetUserDisplayQuery{
Data: getDisplay(&ListDisplayQuery{
OrgID: 2,
UIDs: []string{"a", "b"},
}),
},
{
Name: "display_ids",
Data: getDisplay(&GetUserDisplayQuery{
Data: getDisplay(&ListDisplayQuery{
OrgID: 2,
IDs: []int64{1, 2},
}),
},
{
Name: "display_ids_uids",
Data: getDisplay(&GetUserDisplayQuery{
Data: getDisplay(&ListDisplayQuery{
OrgID: 2,
UIDs: []string{"a", "b"},
IDs: []int64{1, 2},
}),
},
},
sqlQueryTeamBindings: {
sqlQueryTeamBindingsTemplate: {
{
Name: "team_1_bindings",
Data: listTeamBindings(&ListTeamBindingsQuery{
@ -150,7 +150,7 @@ func TestIdentityQueries(t *testing.T) {
}),
},
},
sqlQueryTeamMembers: {
sqlQueryTeamMembersTemplate: {
{
Name: "team_1_members_page_1",
Data: listTeamMembers(&ListTeamMembersQuery{

View File

@ -4,27 +4,47 @@ import (
"context"
"database/sql"
"fmt"
"text/template"
"time"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/registry/apis/identity/common"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
var (
_ LegacyIdentityStore = (*legacySQLStore)(nil)
)
type ListTeamQuery struct {
OrgID int64
UID string
func NewLegacySQLStores(sql legacysql.LegacyDatabaseProvider) LegacyIdentityStore {
return &legacySQLStore{
sql: sql,
Pagination common.Pagination
}
type ListTeamResult struct {
Teams []team.Team
Continue int64
RV int64
}
var sqlQueryTeamsTemplate = mustTemplate("teams_query.sql")
type listTeamsQuery struct {
sqltemplate.SQLTemplate
Query *ListTeamQuery
TeamTable string
}
func newListTeams(sql *legacysql.LegacyDatabaseHelper, q *ListTeamQuery) listTeamsQuery {
return listTeamsQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
TeamTable: sql.Table("team"),
Query: q,
}
}
type legacySQLStore struct {
sql legacysql.LegacyDatabaseProvider
func (r listTeamsQuery) Validate() error {
return nil // TODO
}
// ListTeams implements LegacyIdentityStore.
@ -42,9 +62,9 @@ func (s *legacySQLStore) ListTeams(ctx context.Context, ns claims.NamespaceInfo,
}
req := newListTeams(sql, &query)
q, err := sqltemplate.Execute(sqlQueryTeams, req)
q, err := sqltemplate.Execute(sqlQueryTeamsTemplate, req)
if err != nil {
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeams.Name(), err)
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamsTemplate.Name(), err)
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
@ -83,81 +103,65 @@ func (s *legacySQLStore) ListTeams(ctx context.Context, ns claims.NamespaceInfo,
return res, err
}
// ListUsers implements LegacyIdentityStore.
func (s *legacySQLStore) ListUsers(ctx context.Context, ns claims.NamespaceInfo, query ListUserQuery) (*ListUserResult, error) {
// for continue
limit := int(query.Pagination.Limit)
query.Pagination.Limit += 1
query.OrgID = ns.OrgID
if ns.OrgID == 0 {
return nil, fmt.Errorf("expected non zero orgID")
}
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
res, err := s.queryUsers(ctx, sql, sqlQueryUsers, newListUser(sql, &query), limit)
if err == nil && query.UID != "" {
res.RV, err = sql.GetResourceVersion(ctx, "user", "updated")
}
return res, err
type ListTeamBindingsQuery struct {
// UID is team uid to list bindings for. If not set store should list bindings for all teams
UID string
OrgID int64
Pagination common.Pagination
}
// GetDisplay implements LegacyIdentityStore.
func (s *legacySQLStore) GetDisplay(ctx context.Context, ns claims.NamespaceInfo, query GetUserDisplayQuery) (*ListUserResult, error) {
query.OrgID = ns.OrgID
if ns.OrgID == 0 {
return nil, fmt.Errorf("expected non zero orgID")
}
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
return s.queryUsers(ctx, sql, sqlQueryDisplay, newGetDisplay(sql, &query), 10000)
type ListTeamBindingsResult struct {
Bindings []TeamBinding
Continue int64
RV int64
}
func (s *legacySQLStore) queryUsers(ctx context.Context, sql *legacysql.LegacyDatabaseHelper, t *template.Template, req sqltemplate.Args, limit int) (*ListUserResult, error) {
q, err := sqltemplate.Execute(t, req)
if err != nil {
return nil, fmt.Errorf("execute template %q: %w", sqlQueryUsers.Name(), err)
type TeamMember struct {
ID int64
TeamID int64
TeamUID string
UserID int64
UserUID string
Name string
Email string
Username string
External bool
Updated time.Time
Created time.Time
Permission team.PermissionType
}
func (m TeamMember) MemberID() string {
return identity.NewTypedIDString(claims.TypeUser, m.UserUID)
}
type TeamBinding struct {
TeamUID string
Members []TeamMember
}
var sqlQueryTeamBindingsTemplate = mustTemplate("team_bindings_query.sql")
type listTeamBindingsQuery struct {
sqltemplate.SQLTemplate
Query *ListTeamBindingsQuery
UserTable string
TeamTable string
TeamMemberTable string
}
func (r listTeamBindingsQuery) Validate() error {
return nil // TODO
}
func newListTeamBindings(sql *legacysql.LegacyDatabaseHelper, q *ListTeamBindingsQuery) listTeamBindingsQuery {
return listTeamBindingsQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
UserTable: sql.Table("user"),
TeamTable: sql.Table("team"),
TeamMemberTable: sql.Table("team_member"),
Query: q,
}
res := &ListUserResult{}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
defer func() {
if rows != nil {
_ = rows.Close()
}
}()
if err == nil {
var lastID int64
for rows.Next() {
u := user.User{}
err = rows.Scan(&u.OrgID, &u.ID, &u.UID, &u.Login, &u.Email, &u.Name,
&u.Created, &u.Updated, &u.IsServiceAccount, &u.IsDisabled, &u.IsAdmin,
)
if err != nil {
return res, err
}
lastID = u.ID
res.Users = append(res.Users, u)
if len(res.Users) > limit {
res.Users = res.Users[0 : len(res.Users)-1]
res.Continue = lastID
break
}
}
}
return res, err
}
// ListTeamsBindings implements LegacyIdentityStore.
@ -175,9 +179,9 @@ func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.Namespa
}
req := newListTeamBindings(sql, &query)
q, err := sqltemplate.Execute(sqlQueryTeamBindings, req)
q, err := sqltemplate.Execute(sqlQueryTeamBindingsTemplate, req)
if err != nil {
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeams.Name(), err)
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamsTemplate.Name(), err)
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
@ -233,6 +237,42 @@ func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.Namespa
return res, err
}
type ListTeamMembersQuery struct {
UID string
OrgID int64
Pagination common.Pagination
}
type ListTeamMembersResult struct {
Continue int64
Members []TeamMember
}
// Templates.
var sqlQueryTeamMembersTemplate = mustTemplate("team_members_query.sql")
type listTeamMembersQuery struct {
sqltemplate.SQLTemplate
Query *ListTeamMembersQuery
UserTable string
TeamTable string
TeamMemberTable string
}
func (r listTeamMembersQuery) Validate() error {
return nil // TODO
}
func newListTeamMembers(sql *legacysql.LegacyDatabaseHelper, q *ListTeamMembersQuery) listTeamMembersQuery {
return listTeamMembersQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
UserTable: sql.Table("user"),
TeamTable: sql.Table("team"),
TeamMemberTable: sql.Table("team_member"),
Query: q,
}
}
// ListTeamMembers implements LegacyIdentityStore.
func (s *legacySQLStore) ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error) {
query.Pagination.Limit += 1
@ -247,9 +287,9 @@ func (s *legacySQLStore) ListTeamMembers(ctx context.Context, ns claims.Namespac
}
req := newListTeamMembers(sql, &query)
q, err := sqltemplate.Execute(sqlQueryTeamMembers, req)
q, err := sqltemplate.Execute(sqlQueryTeamMembersTemplate, req)
if err != nil {
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeams.Name(), err)
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamsTemplate.Name(), err)
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)

View File

@ -1,4 +1,5 @@
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission FROM {{ .Ident .TeamMemberTable }} tm
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
FROM {{ .Ident .TeamMemberTable }} tm
INNER JOIN {{ .Ident .TeamTable }} t ON tm.team_id = t.id
INNER JOIN {{ .Ident .UserTable }} u ON tm.user_id = u.id
WHERE

View File

@ -1,4 +1,5 @@
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission FROM `grafana`.`team_member` tm
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
FROM `grafana`.`team_member` tm
INNER JOIN `grafana`.`team` t ON tm.team_id = t.id
INNER JOIN `grafana`.`user` u ON tm.user_id = u.id
WHERE

View File

@ -1,4 +1,5 @@
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission FROM `grafana`.`team_member` tm
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
FROM `grafana`.`team_member` tm
INNER JOIN `grafana`.`team` t ON tm.team_id = t.id
INNER JOIN `grafana`.`user` u ON tm.user_id = u.id
WHERE

View File

@ -1,4 +1,5 @@
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission FROM `grafana`.`team_member` tm
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
FROM `grafana`.`team_member` tm
INNER JOIN `grafana`.`team` t ON tm.team_id = t.id
INNER JOIN `grafana`.`user` u ON tm.user_id = u.id
WHERE

View File

@ -1,4 +1,5 @@
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission FROM "grafana"."team_member" tm
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
FROM "grafana"."team_member" tm
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
WHERE

View File

@ -1,4 +1,5 @@
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission FROM "grafana"."team_member" tm
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
FROM "grafana"."team_member" tm
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
WHERE

View File

@ -1,4 +1,5 @@
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission FROM "grafana"."team_member" tm
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
FROM "grafana"."team_member" tm
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
WHERE

View File

@ -1,4 +1,5 @@
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission FROM "grafana"."team_member" tm
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
FROM "grafana"."team_member" tm
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
WHERE

View File

@ -1,4 +1,5 @@
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission FROM "grafana"."team_member" tm
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
FROM "grafana"."team_member" tm
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
WHERE

View File

@ -1,4 +1,5 @@
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission FROM "grafana"."team_member" tm
SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission
FROM "grafana"."team_member" tm
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
WHERE

View File

@ -1,106 +0,0 @@
package legacy
import (
"context"
"time"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/registry/apis/identity/common"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/user"
)
type ListUserQuery struct {
OrgID int64
UID string
IsServiceAccount bool
Pagination common.Pagination
}
type ListUserResult struct {
Users []user.User
Continue int64
RV int64
}
type GetUserDisplayQuery struct {
OrgID int64
UIDs []string
IDs []int64
}
type ListTeamQuery struct {
OrgID int64
UID string
Pagination common.Pagination
}
type ListTeamResult struct {
Teams []team.Team
Continue int64
RV int64
}
type TeamMember struct {
ID int64
TeamID int64
TeamUID string
UserID int64
UserUID string
Name string
Email string
Username string
External bool
Updated time.Time
Created time.Time
Permission team.PermissionType
}
func (m TeamMember) MemberID() string {
return identity.NewTypedIDString(claims.TypeUser, m.UserUID)
}
type TeamBinding struct {
TeamUID string
Members []TeamMember
}
type ListTeamBindingsQuery struct {
// UID is team uid to list bindings for. If not set store should list bindings for all teams
UID string
OrgID int64
Pagination common.Pagination
}
type ListTeamBindingsResult struct {
Bindings []TeamBinding
Continue int64
RV int64
}
type ListTeamMembersQuery struct {
UID string
OrgID int64
Pagination common.Pagination
}
type ListTeamMembersResult struct {
Continue int64
Members []TeamMember
}
// In every case, RBAC should be applied before calling, or before returning results to the requester
type LegacyIdentityStore interface {
GetDisplay(ctx context.Context, ns claims.NamespaceInfo, query GetUserDisplayQuery) (*ListUserResult, error)
ListUsers(ctx context.Context, ns claims.NamespaceInfo, query ListUserQuery) (*ListUserResult, error)
ListTeams(ctx context.Context, ns claims.NamespaceInfo, query ListTeamQuery) (*ListTeamResult, error)
ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error)
ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error)
GetUserTeams(ctx context.Context, ns claims.NamespaceInfo, uid string) ([]team.Team, error)
}

View File

@ -0,0 +1,111 @@
package legacy
import (
"context"
"fmt"
"text/template"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/registry/apis/identity/common"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
type ListUserQuery struct {
OrgID int64
UID string
IsServiceAccount bool
Pagination common.Pagination
}
type ListUserResult struct {
Users []user.User
Continue int64
RV int64
}
var sqlQueryUsersTemplate = mustTemplate("users_query.sql")
func newListUser(sql *legacysql.LegacyDatabaseHelper, q *ListUserQuery) listUsersQuery {
return listUsersQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
UserTable: sql.Table("user"),
OrgUserTable: sql.Table("org_user"),
Query: q,
}
}
type listUsersQuery struct {
sqltemplate.SQLTemplate
Query *ListUserQuery
UserTable string
OrgUserTable string
}
func (r listUsersQuery) Validate() error {
return nil // TODO
}
// ListUsers implements LegacyIdentityStore.
func (s *legacySQLStore) ListUsers(ctx context.Context, ns claims.NamespaceInfo, query ListUserQuery) (*ListUserResult, error) {
// for continue
limit := int(query.Pagination.Limit)
query.Pagination.Limit += 1
query.OrgID = ns.OrgID
if ns.OrgID == 0 {
return nil, fmt.Errorf("expected non zero orgID")
}
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
res, err := s.queryUsers(ctx, sql, sqlQueryUsersTemplate, newListUser(sql, &query), limit)
if err == nil && query.UID != "" {
res.RV, err = sql.GetResourceVersion(ctx, "user", "updated")
}
return res, err
}
func (s *legacySQLStore) queryUsers(ctx context.Context, sql *legacysql.LegacyDatabaseHelper, t *template.Template, req sqltemplate.Args, limit int) (*ListUserResult, error) {
q, err := sqltemplate.Execute(t, req)
if err != nil {
return nil, fmt.Errorf("execute template %q: %w", t.Name(), err)
}
res := &ListUserResult{}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
defer func() {
if rows != nil {
_ = rows.Close()
}
}()
if err == nil {
var lastID int64
for rows.Next() {
u := user.User{}
err = rows.Scan(&u.OrgID, &u.ID, &u.UID, &u.Login, &u.Email, &u.Name,
&u.Created, &u.Updated, &u.IsServiceAccount, &u.IsDisabled, &u.IsAdmin,
)
if err != nil {
return res, err
}
lastID = u.ID
res.Users = append(res.Users, u)
if len(res.Users) > limit {
res.Users = res.Users[0 : len(res.Users)-1]
res.Continue = lastID
break
}
}
}
return res, err
}

View File

@ -81,7 +81,7 @@ func (r *LegacyDisplayStore) Connect(ctx context.Context, name string, _ runtime
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
keys := parseKeys(req.URL.Query()["key"])
users, err := r.store.GetDisplay(ctx, ns, legacy.GetUserDisplayQuery{
users, err := r.store.ListDisplay(ctx, ns, legacy.ListDisplayQuery{
OrgID: ns.OrgID,
UIDs: keys.uids,
IDs: keys.ids,