DataSourcePermissions: Handle licensing properly for ds permissions (#59694)

* RBAC: add viewer grand if dspermissions enforcement is not enabled

* RBAC: Change permissions based on role prefix

* RBAC: Add option to for permission service to add a license middleware

* RBAC: Remove actions from query struct
This commit is contained in:
Karl Persson
2022-12-02 13:19:14 +01:00
committed by GitHub
parent a7d4bbf024
commit 6d1bcd9f40
9 changed files with 29 additions and 42 deletions

View File

@@ -114,7 +114,7 @@ func (hs *HTTPServer) declareFixedRoles() error {
} }
// when running oss or enterprise without a license all users should be able to query data sources // when running oss or enterprise without a license all users should be able to query data sources
if !hs.License.FeatureEnabled("accesscontrol.enforcement") { if !hs.License.FeatureEnabled("dspermissions.enforcement") {
datasourcesReaderRole.Grants = []string{string(org.RoleViewer)} datasourcesReaderRole.Grants = []string{string(org.RoleViewer)}
} }

View File

@@ -18,7 +18,6 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/api" "github.com/grafana/grafana/pkg/services/accesscontrol/api"
"github.com/grafana/grafana/pkg/services/accesscontrol/database" "github.com/grafana/grafana/pkg/services/accesscontrol/database"
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils" "github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
@@ -87,10 +86,6 @@ func (s *Service) GetUsageStats(_ context.Context) map[string]interface{} {
} }
} }
var actionsToFetch = append(
ossaccesscontrol.TeamAdminActions, append(ossaccesscontrol.DashboardAdminActions, append(ossaccesscontrol.FolderAdminActions, ossaccesscontrol.ServiceAccountAdminActions...)...)...,
)
// GetUserPermissions returns user permissions based on built-in roles // GetUserPermissions returns user permissions based on built-in roles
func (s *Service) GetUserPermissions(ctx context.Context, user *user.SignedInUser, options accesscontrol.Options) ([]accesscontrol.Permission, error) { func (s *Service) GetUserPermissions(ctx context.Context, user *user.SignedInUser, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary) timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary)
@@ -112,11 +107,11 @@ func (s *Service) getUserPermissions(ctx context.Context, user *user.SignedInUse
} }
dbPermissions, err := s.store.GetUserPermissions(ctx, accesscontrol.GetUserPermissionsQuery{ dbPermissions, err := s.store.GetUserPermissions(ctx, accesscontrol.GetUserPermissionsQuery{
OrgID: user.OrgID, OrgID: user.OrgID,
UserID: user.UserID, UserID: user.UserID,
Roles: accesscontrol.GetOrgRoles(user), Roles: accesscontrol.GetOrgRoles(user),
TeamIDs: user.Teams, TeamIDs: user.Teams,
Actions: actionsToFetch, RolePrefix: accesscontrol.ManagedRolePrefix,
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -35,16 +35,11 @@ func (s *AccessControlStore) GetUserPermissions(ctx context.Context, query acces
INNER JOIN role ON role.id = permission.role_id INNER JOIN role ON role.id = permission.role_id
` + filter ` + filter
if len(query.Actions) > 0 { if query.RolePrefix != "" {
q += " WHERE permission.action IN(" q += " WHERE role.name LIKE ?"
if len(query.Actions) > 0 { params = append(params, query.RolePrefix+"%")
q += "?" + strings.Repeat(",?", len(query.Actions)-1)
}
q += ")"
for _, a := range query.Actions {
params = append(params, a)
}
} }
if err := sess.SQL(q, params...).Find(&result); err != nil { if err := sess.SQL(q, params...).Find(&result); err != nil {
return err return err
} }

View File

@@ -30,7 +30,6 @@ type getUserPermissionsTestCase struct {
userPermissions []string userPermissions []string
teamPermissions []string teamPermissions []string
builtinPermissions []string builtinPermissions []string
actions []string
expected int expected int
} }
@@ -63,16 +62,6 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
builtinPermissions: []string{"5", "6"}, builtinPermissions: []string{"5", "6"},
expected: 5, expected: 5,
}, },
{
desc: "Should filter on actions",
orgID: 1,
role: "",
userPermissions: []string{"1", "2", "10"},
teamPermissions: []string{"100", "2"},
builtinPermissions: []string{"5", "6"},
expected: 3,
actions: []string{"dashboards:write"},
},
{ {
desc: "should only get br permissions for anonymous user", desc: "should only get br permissions for anonymous user",
anonymousUser: true, anonymousUser: true,
@@ -137,7 +126,6 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
OrgID: tt.orgID, OrgID: tt.orgID,
UserID: userID, UserID: userID,
Roles: roles, Roles: roles,
Actions: tt.actions,
TeamIDs: teamIDs, TeamIDs: teamIDs,
}) })

View File

@@ -215,11 +215,11 @@ func (p Permission) OSSPermission() Permission {
} }
type GetUserPermissionsQuery struct { type GetUserPermissionsQuery struct {
OrgID int64 `json:"-"` OrgID int64
UserID int64 `json:"userId"` UserID int64
Roles []string Roles []string
Actions []string TeamIDs []int64
TeamIDs []int64 RolePrefix string
} }
// ResourcePermission is structure that holds all actions that either a team / user / builtin-role // ResourcePermission is structure that holds all actions that either a team / user / builtin-role

View File

@@ -33,6 +33,10 @@ func newApi(ac accesscontrol.AccessControl, router routing.RouteRegister, manage
func (a *api) registerEndpoints() { func (a *api) registerEndpoints() {
auth := accesscontrol.Middleware(a.ac) auth := accesscontrol.Middleware(a.ac)
disable := disableMiddleware(a.ac.IsDisabled()) disable := disableMiddleware(a.ac.IsDisabled())
licenseMW := a.service.options.LicenseMW
if licenseMW == nil {
licenseMW = nopMiddleware
}
a.router.Group(fmt.Sprintf("/api/access-control/%s", a.service.options.Resource), func(r routing.RouteRegister) { a.router.Group(fmt.Sprintf("/api/access-control/%s", a.service.options.Resource), func(r routing.RouteRegister) {
actionRead := fmt.Sprintf("%s.permissions:read", a.service.options.Resource) actionRead := fmt.Sprintf("%s.permissions:read", a.service.options.Resource)
@@ -40,15 +44,15 @@ func (a *api) registerEndpoints() {
scope := accesscontrol.Scope(a.service.options.Resource, a.service.options.ResourceAttribute, accesscontrol.Parameter(":resourceID")) scope := accesscontrol.Scope(a.service.options.Resource, a.service.options.ResourceAttribute, accesscontrol.Parameter(":resourceID"))
r.Get("/description", auth(disable, accesscontrol.EvalPermission(actionRead)), routing.Wrap(a.getDescription)) r.Get("/description", auth(disable, accesscontrol.EvalPermission(actionRead)), routing.Wrap(a.getDescription))
r.Get("/:resourceID", auth(disable, accesscontrol.EvalPermission(actionRead, scope)), routing.Wrap(a.getPermissions)) r.Get("/:resourceID", auth(disable, accesscontrol.EvalPermission(actionRead, scope)), routing.Wrap(a.getPermissions))
r.Post("/:resourceID", auth(disable, accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setPermissions)) r.Post("/:resourceID", licenseMW, auth(disable, accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setPermissions))
if a.service.options.Assignments.Users { if a.service.options.Assignments.Users {
r.Post("/:resourceID/users/:userID", auth(disable, accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setUserPermission)) r.Post("/:resourceID/users/:userID", licenseMW, auth(disable, accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setUserPermission))
} }
if a.service.options.Assignments.Teams { if a.service.options.Assignments.Teams {
r.Post("/:resourceID/teams/:teamID", auth(disable, accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setTeamPermission)) r.Post("/:resourceID/teams/:teamID", licenseMW, auth(disable, accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setTeamPermission))
} }
if a.service.options.Assignments.BuiltInRoles { if a.service.options.Assignments.BuiltInRoles {
r.Post("/:resourceID/builtInRoles/:builtInRole", auth(disable, accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setBuiltinRolePermission)) r.Post("/:resourceID/builtInRoles/:builtInRole", licenseMW, auth(disable, accesscontrol.EvalPermission(actionWrite, scope)), routing.Wrap(a.setBuiltinRolePermission))
} }
}) })
} }

View File

@@ -15,3 +15,5 @@ func disableMiddleware(shouldDisable bool) web.Handler {
} }
} }
} }
func nopMiddleware(c *models.ReqContext) {}

View File

@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/web"
) )
type ResourceValidator func(ctx context.Context, orgID int64, resourceID string) error type ResourceValidator func(ctx context.Context, orgID int64, resourceID string) error
@@ -39,4 +40,6 @@ type Options struct {
OnSetBuiltInRole func(session *db.Session, orgID int64, builtInRole, resourceID, permission string) error OnSetBuiltInRole func(session *db.Session, orgID int64, builtInRole, resourceID, permission string) error
// InheritedScopesSolver if configured can generate additional scopes that will be used when fetching permissions for a resource // InheritedScopesSolver if configured can generate additional scopes that will be used when fetching permissions for a resource
InheritedScopesSolver InheritedScopesSolver InheritedScopesSolver InheritedScopesSolver
// LicenseMV if configured is applied to endpoints that can modify permissions
LicenseMW web.Handler
} }

View File

@@ -68,7 +68,7 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat
dsPermissions.tabSuffix = () => ProBadge({ experimentId: permissionsExperimentId, eventVariant: 'trial' }); dsPermissions.tabSuffix = () => ProBadge({ experimentId: permissionsExperimentId, eventVariant: 'trial' });
} }
if (featureEnabled('dspermissions')) { if (featureEnabled('dspermissions.enforcement')) {
if (contextSrv.hasPermission(AccessControlAction.DataSourcesPermissionsRead)) { if (contextSrv.hasPermission(AccessControlAction.DataSourcesPermissionsRead)) {
navModel.children!.push(dsPermissions); navModel.children!.push(dsPermissions);
} }