grafana-iam: Implement resourcepermission get (#110256)

* resource permissions get

* address review feedback

* address comments
- read using rp name
- narrow by scope and actionsets
- update sql tests

* align with verb simplification

* keep original format to avoid conflicts

* add sqltests

* cleanup

* Remove unecessary errors

* Move query template to queries

* Use splitN to make sure we have three parts

* Revert user permission management for now. We don't need it

* Revert error change

* group permissions by resource

* extract parse scope

* Move sql_test

* Move & test parseScope

* Add tests to getResourcePermission

* Linting

* Use namespace

* Add test to the backend

* Ongoing tests

* Remove pagination, fix query boolean, insert basic role binding

* Linting

* Straightened the created and updated times

* error handling and uniformization with other backend

* Restore comments to avoid later conflicts

* Integration testing

* switch to function, no need to make it a method

* isServiceAccount should default to FALSE instead of TRUE :surprised:

* PR feedback

* Sort spec permissions

* Shouldn't happen but double proofing

---------

Co-authored-by: Gabriel Mabille <gabriel.mabille@grafana.com>
This commit is contained in:
mohammad-hamid
2025-09-04 17:14:15 +02:00
committed by GitHub
co-authored by Gabriel Mabille
parent 40bf167cb7
commit abcdf20105
16 changed files with 1075 additions and 8 deletions
@@ -1,8 +1,98 @@
package resourcepermission
import (
"context"
"fmt"
"strings"
"github.com/grafana/authlib/types"
v0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/storage/legacysql"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// List
// Get
// getResourcePermissions queries resource permissions based on the provided ListResourcePermissionsQuery and groups them by resource (e.g. {folder.grafana.app, folders, fold1})
func (s *ResourcePermSqlBackend) getResourcePermissions(ctx context.Context, sql *legacysql.LegacyDatabaseHelper, query *ListResourcePermissionsQuery) (map[groupResourceName][]flatResourcePermission, error) {
rawQuery, args, err := buildListResourcePermissionsQueryFromTemplate(sql, query)
if err != nil {
return nil, err
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, rawQuery, args...)
if err != nil {
if rows != nil {
_ = rows.Close()
}
return nil, fmt.Errorf("querying resource permissions: %w", err)
}
defer func() {
_ = rows.Close()
}()
permissions := make(map[groupResourceName][]flatResourcePermission)
for rows.Next() {
var perm flatResourcePermission
if err := rows.Scan(
&perm.ID, &perm.Action, &perm.Scope, &perm.Created, &perm.Updated, &perm.RoleName,
&perm.SubjectUID, &perm.SubjectType, &perm.IsServiceAccount,
); err != nil {
return nil, fmt.Errorf("scanning resource permission: %w", err)
}
key, err := s.parseScope(perm.Scope)
if err != nil {
s.logger.Warn("skipping", "scope", perm.Scope, "err", err)
continue
}
permissions[*key] = append(permissions[*key], perm)
}
return permissions, nil
}
// getResourcePermission retrieves a single ResourcePermission by its name in the format <group>-<resource>-<name> (e.g. dashboard.grafana.app-dashboards-ad5rwqs)
func (s *ResourcePermSqlBackend) getResourcePermission(ctx context.Context, sql *legacysql.LegacyDatabaseHelper, ns types.NamespaceInfo, name string) (*v0alpha1.ResourcePermission, error) {
// e.g. dashboard.grafana.app-dashboards-ad5rwqs
parts := strings.SplitN(name, "-", 3)
if len(parts) != 3 {
return nil, fmt.Errorf("%w: %s", errInvalidName, name)
}
group, resourceType, uid := parts[0], parts[1], parts[2]
mapper, ok := s.mappers[schema.GroupResource{Group: group, Resource: resourceType}]
if !ok {
return nil, fmt.Errorf("%w: %s/%s", errUnknownGroupResource, group, resourceType)
}
resourceQuery := &ListResourcePermissionsQuery{
Scope: mapper.Scope(uid),
OrgID: ns.OrgID,
ActionSets: mapper.ActionSets(),
}
permsByResource, err := s.getResourcePermissions(ctx, sql, resourceQuery)
if err != nil {
return nil, err
}
if len(permsByResource) == 0 {
return nil, fmt.Errorf("resource permission %q: %w", resourceQuery.Scope, errNotFound)
}
resourcePermission, err := toV0ResourcePermissions(permsByResource)
if err != nil {
return nil, err
}
if resourcePermission == nil {
return nil, fmt.Errorf("resource permission %q: %w", resourceQuery.Scope, errNotFound)
}
return &resourcePermission[0], nil
}
// Create