The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/annotations/accesscontrol/accesscontrol.go

166 lines
5.0 KiB

package accesscontrol
import (
"context"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/infra/db"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
)
var (
ErrReadForbidden = errutil.NewBase(
errutil.StatusForbidden,
"annotations.accesscontrol.read",
errutil.WithPublicMessage("User missing permissions"),
)
ErrAccessControlInternal = errutil.NewBase(
errutil.StatusInternal,
"annotations.accesscontrol.internal",
errutil.WithPublicMessage("Internal error while checking permissions"),
)
)
type AuthService struct {
db db.DB
features featuremgmt.FeatureToggles
dashSvc dashboards.DashboardService
}
func NewAuthService(db db.DB, features featuremgmt.FeatureToggles, dashSvc dashboards.DashboardService) *AuthService {
return &AuthService{
db: db,
features: features,
dashSvc: dashSvc,
}
}
// Authorize checks if the user has permission to read annotations, then returns a struct containing dashboards and scope types that the user has access to.
func (authz *AuthService) Authorize(ctx context.Context, query annotations.ItemQuery) (*AccessResources, error) {
user := query.SignedInUser
if user == nil || user.IsNil() {
return nil, ErrReadForbidden.Errorf("missing user")
}
scopes, has := user.GetPermissions()[ac.ActionAnnotationsRead]
if !has {
return nil, ErrReadForbidden.Errorf("user does not have permission to read annotations")
}
scopeTypes := annotationScopeTypes(scopes)
_, canAccessOrgAnnotations := scopeTypes[annotations.Organization.String()]
_, canAccessDashAnnotations := scopeTypes[annotations.Dashboard.String()]
if authz.features.IsEnabled(ctx, featuremgmt.FlagAnnotationPermissionUpdate) {
canAccessDashAnnotations = true
}
var visibleDashboards map[string]int64
var err error
if canAccessDashAnnotations {
if query.AnnotationID != 0 {
annotationDashboardUID, err := authz.getAnnotationDashboard(ctx, query)
if err != nil {
return nil, ErrAccessControlInternal.Errorf("failed to fetch annotations: %w", err)
}
query.DashboardUID = annotationDashboardUID
}
visibleDashboards, err = authz.dashboardsWithVisibleAnnotations(ctx, query)
if err != nil {
return nil, ErrAccessControlInternal.Errorf("failed to fetch dashboards: %w", err)
}
}
return &AccessResources{
Dashboards: visibleDashboards,
CanAccessDashAnnotations: canAccessDashAnnotations,
CanAccessOrgAnnotations: canAccessOrgAnnotations,
}, nil
}
func (authz *AuthService) getAnnotationDashboard(ctx context.Context, query annotations.ItemQuery) (string, error) {
var items []annotations.Item
params := make([]any, 0)
err := authz.db.WithDbSession(ctx, func(sess *db.Session) error {
sql := `
SELECT
a.id,
a.org_id,
a.dashboard_uid
FROM annotation as a
WHERE a.org_id = ? AND a.id = ?
`
params = append(params, query.OrgID, query.AnnotationID)
return sess.SQL(sql, params...).Find(&items)
})
if err != nil {
return "", err
}
if len(items) == 0 {
return "", ErrAccessControlInternal.Errorf("annotation not found")
}
return items[0].DashboardUID, nil
}
func (authz *AuthService) dashboardsWithVisibleAnnotations(ctx context.Context, query annotations.ItemQuery) (map[string]int64, error) {
recursiveQueriesSupported, err := authz.db.RecursiveQueriesAreSupported()
if err != nil {
return nil, err
}
filterType := searchstore.TypeDashboard
if authz.features.IsEnabled(ctx, featuremgmt.FlagAnnotationPermissionUpdate) {
filterType = searchstore.TypeAnnotation
}
filters := []any{
permissions.NewAccessControlDashboardPermissionFilter(query.SignedInUser, dashboardaccess.PERMISSION_VIEW, filterType, authz.features, recursiveQueriesSupported, authz.db.GetDialect()),
searchstore.OrgFilter{OrgId: query.OrgID},
}
if query.DashboardUID != "" {
filters = append(filters, searchstore.DashboardFilter{
UIDs: []string{query.DashboardUID},
})
}
dashs, err := authz.dashSvc.SearchDashboards(ctx, &dashboards.FindPersistedDashboardsQuery{
OrgId: query.SignedInUser.GetOrgID(),
Filters: filters,
SignedInUser: query.SignedInUser,
Page: query.Page,
Type: filterType,
Limit: 1000,
})
if err != nil {
return nil, err
}
visibleDashboards := make(map[string]int64)
for _, d := range dashs {
visibleDashboards[d.UID] = d.ID
}
return visibleDashboards, nil
}
func annotationScopeTypes(scopes []string) map[any]struct{} {
allScopeTypes := map[any]struct{}{
annotations.Dashboard.String(): {},
annotations.Organization.String(): {},
}
types, hasWildcardScope := ac.ParseScopes(ac.ScopeAnnotationsProvider.GetResourceScopeType(""), scopes)
if hasWildcardScope {
types = allScopeTypes
}
return types
}