package legacy import ( "context" "database/sql" "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 ListTeamBindingsQuery struct { UID string OrgID int64 Pagination common.Pagination } type ListTeamBindingsResult struct { Bindings []TeamMember Continue int64 RV int64 } type TeamMember struct { ID int64 UID string 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, } } 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.UID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Created, &m.Updated, &m.Permission, &m.External) 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 { UID string TeamID int64 TeamUID string UserID int64 UserUID string OrgID int64 Created legacysql.DBTime Updated legacysql.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() cmd.Created = legacysql.NewDBTime(now) cmd.Updated = legacysql.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, UID: cmd.UID, 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 } 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, } } 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 int64(len(res.Members)) > query.Pagination.Limit-1 { res.Continue = lastID res.Members = res.Members[0 : len(res.Members)-1] break } } return res, err } type UpdateTeamMemberCommand struct { UID string Permission team.PermissionType Updated legacysql.DBTime } type UpdateTeamMemberResult struct { UID string Permission team.PermissionType Updated legacysql.DBTime } var sqlUpdateTeamMemberQuery = mustTemplate("update_team_member_query.sql") func newUpdateTeamMember(sql *legacysql.LegacyDatabaseHelper, cmd *UpdateTeamMemberCommand) updateTeamMemberQuery { return updateTeamMemberQuery{ SQLTemplate: sqltemplate.New(sql.DialectForDriver()), TeamMemberTable: sql.Table("team_member"), Command: cmd, } } type updateTeamMemberQuery struct { sqltemplate.SQLTemplate TeamMemberTable string Command *UpdateTeamMemberCommand } func (r updateTeamMemberQuery) Validate() error { return nil } func (s *legacySQLStore) UpdateTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd UpdateTeamMemberCommand) (*UpdateTeamMemberResult, error) { now := time.Now().UTC() cmd.Updated = legacysql.NewDBTime(now) sql, err := s.sql(ctx) if err != nil { return nil, err } req := newUpdateTeamMember(sql, &cmd) var result UpdateTeamMemberResult err = sql.DB.GetSqlxSession().WithTransaction(ctx, func(st *session.SessionTx) error { teamMemberQuery, err := sqltemplate.Execute(sqlUpdateTeamMemberQuery, req) if err != nil { return fmt.Errorf("failed to execute team member template %q: %w", sqlUpdateTeamMemberQuery.Name(), err) } _, err = st.Exec(ctx, teamMemberQuery, req.GetArgs()...) if err != nil { return fmt.Errorf("failed to update team member: %w", err) } result = UpdateTeamMemberResult(cmd) return nil }) if err != nil { return nil, err } return &result, nil } type DeleteTeamMemberCommand struct { UID string } var sqlDeleteTeamMemberQuery = mustTemplate("delete_team_member_query.sql") func newDeleteTeamMember(sql *legacysql.LegacyDatabaseHelper, cmd *DeleteTeamMemberCommand) deleteTeamMemberQuery { return deleteTeamMemberQuery{ SQLTemplate: sqltemplate.New(sql.DialectForDriver()), TeamMemberTable: sql.Table("team_member"), Command: cmd, } } type deleteTeamMemberQuery struct { sqltemplate.SQLTemplate TeamMemberTable string Command *DeleteTeamMemberCommand } func (r deleteTeamMemberQuery) Validate() error { return nil } func (s *legacySQLStore) DeleteTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd DeleteTeamMemberCommand) error { sql, err := s.sql(ctx) if err != nil { return err } req := newDeleteTeamMember(sql, &cmd) if err := req.Validate(); err != nil { return err } err = sql.DB.GetSqlxSession().WithTransaction(ctx, func(st *session.SessionTx) error { teamMemberQuery, err := sqltemplate.Execute(sqlDeleteTeamMemberQuery, req) if err != nil { return fmt.Errorf("failed to execute team member template %q: %w", sqlDeleteTeamMemberQuery.Name(), err) } _, err = st.Exec(ctx, teamMemberQuery, req.GetArgs()...) if err != nil { return fmt.Errorf("failed to delete team member: %w", err) } return nil }) if err != nil { return err } return nil } func scanMember(rows *sql.Rows) (TeamMember, error) { m := TeamMember{} err := rows.Scan(&m.ID, &m.UID, &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 }