grafana/pkg/registry/apis/iam/resourcepermission/templates.go

293 lines
8.9 KiB
Go
Raw Normal View History

package resourcepermission
import (
"embed"
"fmt"
"text/template"
`grafana-iam`: Implement `resourcepermission` creation (#110246) * Extract from #108753 Co-Authored-By: mohammad-hamid <mohammad.hamid@grafana.com> * Tackle create Co-Authored-By: mohammad-hamid <mohammad.hamid@grafana.com> * WIP use identity store to resolve role names * WIP * create role * Remove unecessary comments * comments * sql templates * test role insert tplt * Add tests :sweat_smile: * Test permission insert template * Test permission delete template * Test assignment_insert template * Manually test insertion * Remove delete permissions. This is a create case we don't have permissions for that resource * generate name handled by the apiserver library * Remove comment and conversion * Small renaming nits * changes from main * Add storage backend tests * Add test to sql * Test role contains a unique permission * linting * Account for pr feedback Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * Reuse mappers * Move function to models * Add check between name and spec resource * Check if the resource does not already exist Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * fix query * Check basic roles Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * Account for error * Make struct names consistent * Nit. I prefer createAndAssignManagedRole * Remove notifyign * log errors instead of returning them * Fix exist query join * Test errors * Remove dup --------- Co-authored-by: mohammad-hamid <mohammad.hamid@grafana.com> Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2025-09-05 20:22:25 +08:00
"time"
`grafana-iam`: Implement `resourcepermission` creation (#110246) * Extract from #108753 Co-Authored-By: mohammad-hamid <mohammad.hamid@grafana.com> * Tackle create Co-Authored-By: mohammad-hamid <mohammad.hamid@grafana.com> * WIP use identity store to resolve role names * WIP * create role * Remove unecessary comments * comments * sql templates * test role insert tplt * Add tests :sweat_smile: * Test permission insert template * Test permission delete template * Test assignment_insert template * Manually test insertion * Remove delete permissions. This is a create case we don't have permissions for that resource * generate name handled by the apiserver library * Remove comment and conversion * Small renaming nits * changes from main * Add storage backend tests * Add test to sql * Test role contains a unique permission * linting * Account for pr feedback Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * Reuse mappers * Move function to models * Add check between name and spec resource * Check if the resource does not already exist Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * fix query * Check basic roles Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * Account for error * Make struct names consistent * Nit. I prefer createAndAssignManagedRole * Remove notifyign * log errors instead of returning them * Fix exist query join * Test errors * Remove dup --------- Co-authored-by: mohammad-hamid <mohammad.hamid@grafana.com> Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2025-09-05 20:22:25 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
// Templates setup.
var (
//go:embed queries/*.sql
sqlTemplatesFS embed.FS
sqlTemplates = template.Must(template.New("sql").ParseFS(sqlTemplatesFS, `queries/*.sql`))
resourcePermissionsQueryTplt = mustTemplate("resource_permission_query.sql")
resourcePermissionDeletionQueryTplt = mustTemplate("resource_permission_deletion_query.sql")
roleInsertTplt = mustTemplate("role_insert.sql")
assignmentInsertTplt = mustTemplate("assignment_insert.sql")
permissionInsertTplt = mustTemplate("permission_insert.sql")
permissionRemoveTplt = mustTemplate("permission_remove.sql")
pageQueryTplt = mustTemplate("page_query.sql")
latestUpdateTplt = mustTemplate("latest_update_query.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))
}
type pageQueryTemplate struct {
sqltemplate.SQLTemplate
Query *PageQuery
PermissionTable string
RoleTable string
ManagedRolePattern string
}
func (r pageQueryTemplate) Validate() error {
return nil
}
func buildPageQueryFromTemplate(dbHelper *legacysql.LegacyDatabaseHelper, query *PageQuery) (string, []interface{}, error) {
req := pageQueryTemplate{
SQLTemplate: sqltemplate.New(dbHelper.DialectForDriver()),
Query: query,
PermissionTable: dbHelper.Table("permission"),
RoleTable: dbHelper.Table("role"),
ManagedRolePattern: "managed:%",
}
rawQuery, err := sqltemplate.Execute(pageQueryTplt, req)
if err != nil {
return "", nil, fmt.Errorf("execute template %q: %w", pageQueryTplt.Name(), err)
}
return rawQuery, req.GetArgs(), nil
}
type latestUpdateTemplate struct {
sqltemplate.SQLTemplate
OrgID int64
ScopePatterns []string
PermissionTable string
RoleTable string
ManagedPattern string
}
func (l latestUpdateTemplate) Validate() error {
if l.OrgID <= 0 {
return fmt.Errorf("orgID must be set")
}
if len(l.ScopePatterns) == 0 {
return fmt.Errorf("at least one scope pattern is required")
}
return nil
}
func buildLatestUpdateQueryFromTemplate(dbHelper *legacysql.LegacyDatabaseHelper, orgID int64, scopePatterns []string) (string, []interface{}, error) {
req := latestUpdateTemplate{
SQLTemplate: sqltemplate.New(dbHelper.DialectForDriver()),
OrgID: orgID,
ScopePatterns: scopePatterns,
PermissionTable: dbHelper.Table("permission"),
RoleTable: dbHelper.Table("role"),
ManagedPattern: "managed:%",
}
rawQuery, err := sqltemplate.Execute(latestUpdateTplt, req)
if err != nil {
return "", nil, fmt.Errorf("execute template %q: %w", latestUpdateTplt.Name(), err)
}
return rawQuery, req.GetArgs(), nil
}
type listResourcePermissionsQueryTemplate struct {
sqltemplate.SQLTemplate
Query *ListResourcePermissionsQuery
PermissionTable string
RoleTable string
UserTable string
TeamTable string
BuiltinRoleTable string
UserRoleTable string
TeamRoleTable string
ManagedRolePattern string
}
func (r listResourcePermissionsQueryTemplate) Validate() error {
return nil
}
func buildListResourcePermissionsQueryFromTemplate(dbHelper *legacysql.LegacyDatabaseHelper, query *ListResourcePermissionsQuery) (string, []interface{}, error) {
req := listResourcePermissionsQueryTemplate{
SQLTemplate: sqltemplate.New(dbHelper.DialectForDriver()),
Query: query,
PermissionTable: dbHelper.Table("permission"),
RoleTable: dbHelper.Table("role"),
UserTable: dbHelper.Table("user"),
TeamTable: dbHelper.Table("team"),
BuiltinRoleTable: dbHelper.Table("builtin_role"),
UserRoleTable: dbHelper.Table("user_role"),
TeamRoleTable: dbHelper.Table("team_role"),
ManagedRolePattern: "managed:%",
}
rawQuery, err := sqltemplate.Execute(resourcePermissionsQueryTplt, req)
if err != nil {
return "", nil, fmt.Errorf("execute template %q: %w", resourcePermissionsQueryTplt.Name(), err)
}
return rawQuery, req.GetArgs(), nil
}
`grafana-iam`: Implement `resourcepermission` creation (#110246) * Extract from #108753 Co-Authored-By: mohammad-hamid <mohammad.hamid@grafana.com> * Tackle create Co-Authored-By: mohammad-hamid <mohammad.hamid@grafana.com> * WIP use identity store to resolve role names * WIP * create role * Remove unecessary comments * comments * sql templates * test role insert tplt * Add tests :sweat_smile: * Test permission insert template * Test permission delete template * Test assignment_insert template * Manually test insertion * Remove delete permissions. This is a create case we don't have permissions for that resource * generate name handled by the apiserver library * Remove comment and conversion * Small renaming nits * changes from main * Add storage backend tests * Add test to sql * Test role contains a unique permission * linting * Account for pr feedback Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * Reuse mappers * Move function to models * Add check between name and spec resource * Check if the resource does not already exist Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * fix query * Check basic roles Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> * Account for error * Make struct names consistent * Nit. I prefer createAndAssignManagedRole * Remove notifyign * log errors instead of returning them * Fix exist query join * Test errors * Remove dup --------- Co-authored-by: mohammad-hamid <mohammad.hamid@grafana.com> Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2025-09-05 20:22:25 +08:00
type insertRoleTemplate struct {
sqltemplate.SQLTemplate
RoleTable string
OrgID int64
UID string
Name string
Now string
}
func (t insertRoleTemplate) Validate() error {
return nil
}
func buildInsertRoleQuery(dbHelper *legacysql.LegacyDatabaseHelper, orgID int64, uid string, name string) (string, []any, error) {
req := insertRoleTemplate{
SQLTemplate: sqltemplate.New(dbHelper.DialectForDriver()),
RoleTable: dbHelper.Table("role"),
OrgID: orgID,
UID: uid,
Name: name,
Now: timeNow().Format(time.DateTime),
}
rawQuery, err := sqltemplate.Execute(roleInsertTplt, req)
if err != nil {
return "", nil, fmt.Errorf("rendering sql template: %w", err)
}
return rawQuery, req.GetArgs(), nil
}
type insertAssignmentTemplate struct {
sqltemplate.SQLTemplate
AssignmentTable string
AssignmentColumn string
RoleID int64
OrgID int64
SubjectID any // int64 or string
Now string
}
func (t insertAssignmentTemplate) Validate() error {
if t.AssignmentTable == "" {
return fmt.Errorf("assignment table is required")
}
if t.AssignmentColumn == "" {
return fmt.Errorf("assignment column is required")
}
return nil
}
func buildInsertAssignmentQuery(dbHelper *legacysql.LegacyDatabaseHelper, orgID int64, roleID int64, assignment rbacAssignmentCreate) (string, []any, error) {
req := insertAssignmentTemplate{
SQLTemplate: sqltemplate.New(dbHelper.DialectForDriver()),
AssignmentTable: dbHelper.Table(assignment.AssignmentTable),
AssignmentColumn: assignment.AssignmentColumn,
RoleID: roleID,
OrgID: orgID,
SubjectID: assignment.SubjectID,
Now: timeNow().Format(time.DateTime),
}
rawQuery, err := sqltemplate.Execute(assignmentInsertTplt, req)
if err != nil {
return "", nil, fmt.Errorf("rendering sql template: %w", err)
}
return rawQuery, req.GetArgs(), nil
}
type insertPermissionTemplate struct {
sqltemplate.SQLTemplate
PermissionTable string
RoleID int64
Permission accesscontrol.Permission
Now string
}
func (t insertPermissionTemplate) Validate() error {
return nil
}
func buildInsertPermissionQuery(dbHelper *legacysql.LegacyDatabaseHelper, roleID int64, permission accesscontrol.Permission) (string, []any, error) {
req := insertPermissionTemplate{
SQLTemplate: sqltemplate.New(dbHelper.DialectForDriver()),
PermissionTable: dbHelper.Table("permission"),
RoleID: roleID,
Permission: permission,
Now: timeNow().Format(time.DateTime),
}
rawQuery, err := sqltemplate.Execute(permissionInsertTplt, req)
if err != nil {
return "", nil, fmt.Errorf("rendering sql template: %w", err)
}
return rawQuery, req.GetArgs(), nil
}
type removePermissionTemplate struct {
sqltemplate.SQLTemplate
PermissionTable string
RoleTable string
Scope string
Action string
OrgID int64
RoleName string
}
func (t removePermissionTemplate) Validate() error {
return nil
}
func buildRemovePermissionQuery(dbHelper *legacysql.LegacyDatabaseHelper, scope, action, roleName string, orgID int64) (string, []any, error) {
req := removePermissionTemplate{
SQLTemplate: sqltemplate.New(dbHelper.DialectForDriver()),
PermissionTable: dbHelper.Table("permission"),
RoleTable: dbHelper.Table("role"),
Scope: scope,
Action: action,
OrgID: orgID,
RoleName: roleName,
}
rawQuery, err := sqltemplate.Execute(permissionRemoveTplt, req)
if err != nil {
return "", nil, fmt.Errorf("rendering sql template: %w", err)
}
return rawQuery, req.GetArgs(), nil
}
type deleteResourcePermissionsQueryTemplate struct {
sqltemplate.SQLTemplate
Query *DeleteResourcePermissionsQuery
PermissionTable string
RoleTable string
ManagedRolePattern string
}
func (r deleteResourcePermissionsQueryTemplate) Validate() error {
return nil
}
func buildDeleteResourcePermissionsQueryFromTemplate(sql *legacysql.LegacyDatabaseHelper, query *DeleteResourcePermissionsQuery) (string, []interface{}, error) {
req := deleteResourcePermissionsQueryTemplate{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
Query: query,
PermissionTable: sql.Table("permission"),
RoleTable: sql.Table("role"),
ManagedRolePattern: "managed:%",
}
rawQuery, err := sqltemplate.Execute(resourcePermissionDeletionQueryTplt, req)
if err != nil {
return "", nil, fmt.Errorf("execute template %q: %w", resourcePermissionDeletionQueryTplt.Name(), err)
}
return rawQuery, req.GetArgs(), nil
}