grafana/pkg/registry/apis/iam/legacy/team.go

708 lines
16 KiB
Go

package legacy
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
"github.com/grafana/grafana/pkg/services/sqlstore/session"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
type GetTeamInternalIDQuery struct {
OrgID int64
UID string
}
type GetTeamInternalIDResult struct {
ID int64
}
var sqlQueryTeamInternalIDTemplate = mustTemplate("team_internal_id.sql")
func newGetTeamInternalID(sql *legacysql.LegacyDatabaseHelper, q *GetTeamInternalIDQuery) getTeamInternalIDQuery {
return getTeamInternalIDQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
TeamTable: sql.Table("team"),
Query: q,
}
}
type getTeamInternalIDQuery struct {
sqltemplate.SQLTemplate
TeamTable string
Query *GetTeamInternalIDQuery
}
func (r getTeamInternalIDQuery) Validate() error { return nil }
func (s *legacySQLStore) GetTeamInternalID(
ctx context.Context,
ns claims.NamespaceInfo,
query GetTeamInternalIDQuery,
) (*GetTeamInternalIDResult, error) {
query.OrgID = ns.OrgID
if query.OrgID == 0 {
return nil, fmt.Errorf("expected non zero org id")
}
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
req := newGetTeamInternalID(sql, &query)
q, err := sqltemplate.Execute(sqlQueryTeamInternalIDTemplate, req)
if err != nil {
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamInternalIDTemplate.Name(), err)
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
defer func() {
if rows != nil {
_ = rows.Close()
}
}()
if err != nil {
return nil, err
}
if !rows.Next() {
return nil, errors.New("team not found")
}
var id int64
if err := rows.Scan(&id); err != nil {
return nil, err
}
return &GetTeamInternalIDResult{
id,
}, nil
}
type ListTeamQuery struct {
OrgID int64
UID string
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,
}
}
func (r listTeamsQuery) Validate() error {
return nil // TODO
}
// ListTeams implements LegacyIdentityStore.
func (s *legacySQLStore) ListTeams(ctx context.Context, ns claims.NamespaceInfo, query ListTeamQuery) (*ListTeamResult, error) {
// for continue
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
}
req := newListTeams(sql, &query)
q, err := sqltemplate.Execute(sqlQueryTeamsTemplate, req)
if err != nil {
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamsTemplate.Name(), err)
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
defer func() {
if rows != nil {
_ = rows.Close()
}
}()
res := &ListTeamResult{}
if err != nil {
return nil, err
}
var lastID int64
for rows.Next() {
t := team.Team{}
err = rows.Scan(&t.ID, &t.UID, &t.Name, &t.Email, &t.ExternalUID, &t.IsProvisioned, &t.Created, &t.Updated)
if err != nil {
return res, err
}
lastID = t.ID
res.Teams = append(res.Teams, t)
if len(res.Teams) > int(query.Pagination.Limit)-1 {
res.Teams = res.Teams[0 : len(res.Teams)-1]
res.Continue = lastID
break
}
}
if query.UID == "" {
res.RV, err = sql.GetResourceVersion(ctx, "team", "updated")
}
return res, err
}
type CreateTeamCommand struct {
UID string
Name string
OrgID int64
Created DBTime
Updated DBTime
Email string
ExternalID string
IsProvisioned bool
ExternalUID string
}
type CreateTeamResult struct {
Team team.Team
}
var sqlCreateTeamTemplate = mustTemplate("create_team.sql")
func newCreateTeam(sql *legacysql.LegacyDatabaseHelper, cmd *CreateTeamCommand) createTeamQuery {
return createTeamQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
TeamTable: sql.Table("team"),
Command: cmd,
}
}
type createTeamQuery struct {
sqltemplate.SQLTemplate
TeamTable string
Command *CreateTeamCommand
}
func (r createTeamQuery) Validate() error {
return nil
}
func (s *legacySQLStore) CreateTeam(ctx context.Context, ns claims.NamespaceInfo, cmd CreateTeamCommand) (*CreateTeamResult, error) {
now := time.Now().UTC().Truncate(time.Second)
cmd.Created = NewDBTime(now)
cmd.Updated = NewDBTime(now)
cmd.OrgID = ns.OrgID
if cmd.OrgID == 0 {
return nil, fmt.Errorf("expected non zero org id")
}
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
req := newCreateTeam(sql, &cmd)
var createdTeam team.Team
err = sql.DB.GetSqlxSession().WithTransaction(ctx, func(st *session.SessionTx) error {
teamQuery, err := sqltemplate.Execute(sqlCreateTeamTemplate, req)
if err != nil {
return fmt.Errorf("failed to execute team template %q: %w", sqlCreateTeamTemplate.Name(), err)
}
teamID, err := st.ExecWithReturningId(ctx, teamQuery, req.GetArgs()...)
if err != nil {
return fmt.Errorf("failed to create team: %w", err)
}
createdTeam = team.Team{
ID: teamID,
UID: cmd.UID,
Name: cmd.Name,
OrgID: cmd.OrgID,
Email: cmd.Email,
ExternalUID: cmd.ExternalUID,
IsProvisioned: cmd.IsProvisioned,
Created: cmd.Created.Time,
Updated: cmd.Updated.Time,
}
return nil
})
if err != nil {
return nil, err
}
return &CreateTeamResult{Team: createdTeam}, nil
}
type UpdateTeamCommand struct {
UID string
Name string
Updated DBTime
Email string
ExternalID string
IsProvisioned bool
ExternalUID string
}
type UpdateTeamResult struct {
Team team.Team
}
var sqlUpdateTeamTemplate = mustTemplate("update_team.sql")
func newUpdateTeam(sql *legacysql.LegacyDatabaseHelper, cmd *UpdateTeamCommand) updateTeamQuery {
return updateTeamQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
TeamTable: sql.Table("team"),
Command: cmd,
}
}
type updateTeamQuery struct {
sqltemplate.SQLTemplate
TeamTable string
Command *UpdateTeamCommand
}
func (r updateTeamQuery) Validate() error {
return nil
}
func (s *legacySQLStore) UpdateTeam(ctx context.Context, ns claims.NamespaceInfo, cmd UpdateTeamCommand) (*UpdateTeamResult, error) {
now := time.Now().UTC().Truncate(time.Second)
cmd.Updated = NewDBTime(now)
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
req := newUpdateTeam(sql, &cmd)
var updatedTeam team.Team
err = sql.DB.GetSqlxSession().WithTransaction(ctx, func(st *session.SessionTx) error {
_, err := s.GetTeamInternalID(ctx, ns, GetTeamInternalIDQuery{
OrgID: ns.OrgID,
UID: cmd.UID,
})
if err != nil {
return fmt.Errorf("team not found: %w", err)
}
teamQuery, err := sqltemplate.Execute(sqlUpdateTeamTemplate, req)
if err != nil {
return fmt.Errorf("failed to execute team update template %q: %w", sqlUpdateTeamTemplate.Name(), err)
}
_, err = st.Exec(ctx, teamQuery, req.GetArgs()...)
if err != nil {
return fmt.Errorf("failed to update team: %w", err)
}
updatedTeam = team.Team{
UID: cmd.UID,
Name: cmd.Name,
Email: cmd.Email,
ExternalUID: cmd.ExternalUID,
IsProvisioned: cmd.IsProvisioned,
Updated: cmd.Updated.Time,
}
return nil
})
if err != nil {
return nil, err
}
return &UpdateTeamResult{Team: updatedTeam}, nil
}
type DeleteTeamCommand struct {
UID string
}
var sqlDeleteTeamTemplate = mustTemplate("delete_team.sql")
func newDeleteTeam(sql *legacysql.LegacyDatabaseHelper, cmd *DeleteTeamCommand) deleteTeamQuery {
return deleteTeamQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
TeamTable: sql.Table("team"),
Command: cmd,
}
}
type deleteTeamQuery struct {
sqltemplate.SQLTemplate
TeamTable string
Command *DeleteTeamCommand
}
func (r deleteTeamQuery) Validate() error {
return nil
}
func (s *legacySQLStore) DeleteTeam(ctx context.Context, ns claims.NamespaceInfo, cmd DeleteTeamCommand) error {
sql, err := s.sql(ctx)
if err != nil {
return err
}
req := newDeleteTeam(sql, &cmd)
if err := req.Validate(); err != nil {
return err
}
return sql.DB.GetSqlxSession().WithTransaction(ctx, func(st *session.SessionTx) error {
_, err := s.GetTeamInternalID(ctx, ns, GetTeamInternalIDQuery{
OrgID: ns.OrgID,
UID: cmd.UID,
})
if err != nil {
return err
}
teamDeleteReq := newDeleteTeam(sql, &cmd)
if err := teamDeleteReq.Validate(); err != nil {
return err
}
teamDeleteQuery, err := sqltemplate.Execute(sqlDeleteTeamTemplate, teamDeleteReq)
if err != nil {
return fmt.Errorf("error executing team delete template: %w", err)
}
_, err = st.Exec(ctx, teamDeleteQuery, teamDeleteReq.GetArgs()...)
if err != nil {
return fmt.Errorf("failed to delete team: %w", err)
}
return nil
})
}
type ListTeamBindingsQuery struct {
TeamID int64
UserID int64
OrgID int64
Pagination common.Pagination
}
type ListTeamBindingsResult struct {
Bindings []TeamMember
Continue int64
RV int64
}
type TeamMember struct {
ID int64
TeamID int64
TeamUID string
UserID int64
UserUID string
OrgID int64
Name string
Email string
Username string
External bool
Updated time.Time
Created time.Time
Permission team.PermissionType
}
func (m TeamMember) MemberID() string {
return claims.NewTypeID(claims.TypeUser, m.UserUID)
}
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,
}
}
// ListTeamsBindings implements LegacyIdentityStore.
func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error) {
// for continue
query.Pagination.Limit += 1
query.OrgID = ns.OrgID
if query.OrgID == 0 {
return nil, fmt.Errorf("expected non zero orgID")
}
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
req := newListTeamBindings(sql, &query)
q, err := sqltemplate.Execute(sqlQueryTeamBindingsTemplate, req)
if err != nil {
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamBindingsTemplate.Name(), err)
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
defer func() {
if rows != nil {
_ = rows.Close()
}
}()
if err != nil {
return nil, err
}
res := &ListTeamBindingsResult{
Bindings: make([]TeamMember, 0, int(query.Pagination.Limit)),
}
var lastID int64
for rows.Next() {
m := TeamMember{}
err = rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Created, &m.Updated, &m.Permission)
if err != nil {
return res, err
}
res.Bindings = append(res.Bindings, m)
lastID = m.ID
if len(res.Bindings) >= int(query.Pagination.Limit)-1 {
res.Continue = lastID
}
}
return res, err
}
type CreateTeamMemberCommand struct {
TeamID int64
TeamUID string
UserID int64
UserUID string
OrgID int64
Created DBTime
Updated DBTime
External bool
Permission team.PermissionType
}
type CreateTeamMemberResult struct {
TeamMember TeamMember
}
var sqlCreateTeamMemberQuery = mustTemplate("create_team_member_query.sql")
func newCreateTeamMember(sql *legacysql.LegacyDatabaseHelper, cmd *CreateTeamMemberCommand) createTeamMemberQuery {
return createTeamMemberQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
TeamMemberTable: sql.Table("team_member"),
Command: cmd,
}
}
type createTeamMemberQuery struct {
sqltemplate.SQLTemplate
TeamMemberTable string
Command *CreateTeamMemberCommand
}
func (r createTeamMemberQuery) Validate() error {
return nil
}
func (s *legacySQLStore) CreateTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd CreateTeamMemberCommand) (*CreateTeamMemberResult, error) {
now := time.Now().UTC().Truncate(time.Second)
cmd.Created = NewDBTime(now)
cmd.Updated = NewDBTime(now)
cmd.OrgID = ns.OrgID
if cmd.OrgID == 0 {
return nil, fmt.Errorf("expected non zero org id")
}
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
req := newCreateTeamMember(sql, &cmd)
var createdTeamMember TeamMember
err = sql.DB.GetSqlxSession().WithTransaction(ctx, func(st *session.SessionTx) error {
teamMemberQuery, err := sqltemplate.Execute(sqlCreateTeamMemberQuery, req)
if err != nil {
return fmt.Errorf("failed to execute team member template %q: %w", sqlCreateTeamMemberQuery.Name(), err)
}
teamMemberID, err := st.ExecWithReturningId(ctx, teamMemberQuery, req.GetArgs()...)
if err != nil {
return fmt.Errorf("failed to create team member: %w", err)
}
createdTeamMember = TeamMember{
ID: teamMemberID,
TeamID: cmd.TeamID,
TeamUID: cmd.TeamUID,
UserID: cmd.UserID,
UserUID: cmd.UserUID,
OrgID: cmd.OrgID,
Created: cmd.Created.Time,
Updated: cmd.Updated.Time,
External: cmd.External,
Permission: cmd.Permission,
}
return nil
})
if err != nil {
return nil, err
}
return &CreateTeamMemberResult{TeamMember: createdTeamMember}, nil
}
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
query.OrgID = ns.OrgID
if query.OrgID == 0 {
return nil, fmt.Errorf("expected non zero org id")
}
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
req := newListTeamMembers(sql, &query)
q, err := sqltemplate.Execute(sqlQueryTeamMembersTemplate, req)
if err != nil {
return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamsTemplate.Name(), err)
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
defer func() {
if rows != nil {
_ = rows.Close()
}
}()
if err != nil {
return nil, err
}
res := &ListTeamMembersResult{}
var lastID int64
for rows.Next() {
m, err := scanMember(rows)
if err != nil {
return nil, err
}
lastID = m.ID
res.Members = append(res.Members, m)
if len(res.Members) > int(query.Pagination.Limit)-1 {
res.Continue = lastID
res.Members = res.Members[0 : len(res.Members)-1]
break
}
}
return res, err
}
func scanMember(rows *sql.Rows) (TeamMember, error) {
m := TeamMember{}
err := rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Name, &m.Email, &m.Username, &m.External, &m.Created, &m.Updated, &m.Permission)
return m, err
}