mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Instrument tracing across accesscontrol (#91864)
Instrument tracing across accesscontrol --------- Co-authored-by: Dave Henderson <dave.henderson@grafana.com>
This commit is contained in:
parent
68f545210d
commit
028e8ac59e
@ -211,49 +211,79 @@ var reqDatasourceByIdNotFound = `{
|
||||
}`
|
||||
|
||||
func TestDataSourceQueryError(t *testing.T) {
|
||||
type body struct {
|
||||
Message string `json:"message"`
|
||||
MessageId string `json:"messageId"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
}
|
||||
|
||||
tcs := []struct {
|
||||
request string
|
||||
clientErr error
|
||||
expectedStatus int
|
||||
expectedBody string
|
||||
expectedBody body
|
||||
}{
|
||||
{
|
||||
request: reqValid,
|
||||
clientErr: plugins.ErrPluginUnavailable,
|
||||
expectedStatus: http.StatusInternalServerError,
|
||||
expectedBody: `{"message":"Plugin unavailable","messageId":"plugin.unavailable","statusCode":500,"traceID":""}`,
|
||||
expectedBody: body{
|
||||
Message: "Plugin unavailable",
|
||||
MessageId: "plugin.unavailable",
|
||||
StatusCode: 500,
|
||||
},
|
||||
},
|
||||
{
|
||||
request: reqValid,
|
||||
clientErr: plugins.ErrMethodNotImplemented,
|
||||
expectedStatus: http.StatusNotFound,
|
||||
expectedBody: `{"message":"Method not implemented","messageId":"plugin.notImplemented","statusCode":404,"traceID":""}`,
|
||||
expectedBody: body{
|
||||
Message: "Method not implemented",
|
||||
MessageId: "plugin.notImplemented",
|
||||
StatusCode: 404,
|
||||
},
|
||||
},
|
||||
{
|
||||
request: reqValid,
|
||||
clientErr: errors.New("surprise surprise"),
|
||||
expectedStatus: errutil.StatusInternal.HTTPStatus(),
|
||||
expectedBody: `{"message":"An error occurred within the plugin","messageId":"plugin.downstreamError","statusCode":500,"traceID":""}`,
|
||||
expectedBody: body{
|
||||
Message: "An error occurred within the plugin",
|
||||
MessageId: "plugin.downstreamError",
|
||||
StatusCode: 500,
|
||||
},
|
||||
},
|
||||
{
|
||||
request: reqNoQueries,
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
expectedBody: `{"message":"No queries found","messageId":"query.noQueries","statusCode":400,"traceID":""}`,
|
||||
expectedBody: body{
|
||||
Message: "No queries found",
|
||||
MessageId: "query.noQueries",
|
||||
StatusCode: 400,
|
||||
},
|
||||
},
|
||||
{
|
||||
request: reqQueryWithInvalidDatasourceID,
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
expectedBody: `{"message":"Query does not contain a valid data source identifier","messageId":"query.invalidDatasourceId","statusCode":400,"traceID":""}`,
|
||||
expectedBody: body{
|
||||
Message: "Query does not contain a valid data source identifier",
|
||||
MessageId: "query.invalidDatasourceId",
|
||||
StatusCode: 400,
|
||||
},
|
||||
},
|
||||
{
|
||||
request: reqDatasourceByUidNotFound,
|
||||
expectedStatus: http.StatusNotFound,
|
||||
expectedBody: `{"message":"Data source not found","traceID":""}`,
|
||||
expectedBody: body{
|
||||
Message: "Data source not found",
|
||||
},
|
||||
},
|
||||
{
|
||||
request: reqDatasourceByIdNotFound,
|
||||
expectedStatus: http.StatusNotFound,
|
||||
expectedBody: `{"message":"Data source not found","traceID":""}`,
|
||||
expectedBody: body{
|
||||
Message: "Data source not found",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -291,14 +321,24 @@ func TestDataSourceQueryError(t *testing.T) {
|
||||
hs.QuotaService = quotatest.New(false, nil)
|
||||
})
|
||||
req := srv.NewPostRequest("/api/ds/query", strings.NewReader(tc.request))
|
||||
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {datasources.ActionQuery: []string{datasources.ScopeAll}}}})
|
||||
resp, err := srv.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tc.expectedStatus, resp.StatusCode)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedBody, string(body))
|
||||
|
||||
var responseBody body
|
||||
err = json.Unmarshal(bodyBytes, &responseBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tc.expectedBody.Message, responseBody.Message)
|
||||
require.Equal(t, tc.expectedBody.MessageId, responseBody.MessageId)
|
||||
require.Equal(t, tc.expectedBody.StatusCode, responseBody.StatusCode)
|
||||
|
||||
require.NoError(t, resp.Body.Close())
|
||||
})
|
||||
}
|
||||
|
@ -199,12 +199,15 @@ func TestCallResource(t *testing.T) {
|
||||
resp, err := srv.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
body := new(strings.Builder)
|
||||
_, err = io.Copy(body, resp.Body)
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedBody := `{ "message": "Failed to call resource", "traceID": "" }`
|
||||
require.JSONEq(t, expectedBody, body.String())
|
||||
var responseBody struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
err = json.Unmarshal(bodyBytes, &responseBody)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, responseBody.Message, "Failed to call resource")
|
||||
require.NoError(t, resp.Body.Close())
|
||||
require.Equal(t, 500, resp.StatusCode)
|
||||
})
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.opentelemetry.io/otel"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@ -18,6 +19,7 @@ import (
|
||||
|
||||
var (
|
||||
errAccessNotImplemented = errors.New("access control not implemented for resource")
|
||||
tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/accesscontrol/acimpl")
|
||||
)
|
||||
|
||||
var _ accesscontrol.AccessControl = new(AccessControl)
|
||||
@ -52,6 +54,9 @@ type AccessControl struct {
|
||||
}
|
||||
|
||||
func (a *AccessControl) Evaluate(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.Evaluate")
|
||||
defer span.End()
|
||||
|
||||
if a.features.IsEnabledGlobally(featuremgmt.FlagZanzana) {
|
||||
return a.evaluateCompare(ctx, user, evaluator)
|
||||
}
|
||||
@ -60,6 +65,9 @@ func (a *AccessControl) Evaluate(ctx context.Context, user identity.Requester, e
|
||||
}
|
||||
|
||||
func (a *AccessControl) evaluate(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.evaluate")
|
||||
defer span.End()
|
||||
|
||||
timer := prometheus.NewTimer(metrics.MAccessEvaluationsSummary)
|
||||
defer timer.ObserveDuration()
|
||||
metrics.MAccessEvaluationCount.Inc()
|
||||
@ -98,6 +106,9 @@ func (a *AccessControl) evaluate(ctx context.Context, user identity.Requester, e
|
||||
}
|
||||
|
||||
func (a *AccessControl) evaluateZanzana(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.evaluateZanzana")
|
||||
defer span.End()
|
||||
|
||||
eval, err := evaluator.MutateScopes(ctx, a.resolvers.GetScopeAttributeMutator(user.GetOrgID()))
|
||||
if err != nil {
|
||||
if !errors.Is(err, accesscontrol.ErrResolverNotFound) {
|
||||
@ -140,6 +151,9 @@ type evalResult struct {
|
||||
|
||||
// evaluateCompare run RBAC and zanzana checks in parallel and then compare result
|
||||
func (a *AccessControl) evaluateCompare(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.evaluateCompare")
|
||||
defer span.End()
|
||||
|
||||
res := make(chan evalResult, 2)
|
||||
go func() {
|
||||
timer := prometheus.NewTimer(a.metrics.mAccessEngineEvaluationsSeconds.WithLabelValues("zanzana"))
|
||||
@ -192,5 +206,8 @@ func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver a
|
||||
}
|
||||
|
||||
func (a *AccessControl) debug(ctx context.Context, ident identity.Requester, msg string, eval accesscontrol.Evaluator) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.debug")
|
||||
defer span.End()
|
||||
|
||||
a.log.FromContext(ctx).Debug(msg, "id", ident.GetID(), "orgID", ident.GetOrgID(), "permissions", eval.GoString())
|
||||
}
|
||||
|
@ -85,7 +85,6 @@ func ProvideOSSService(
|
||||
log: log.New("accesscontrol.service"),
|
||||
roles: accesscontrol.BuildBasicRoleDefinitions(),
|
||||
store: store,
|
||||
tracer: tracer,
|
||||
sync: migrator.NewZanzanaSynchroniser(zclient, db),
|
||||
permRegistry: permRegistry,
|
||||
}
|
||||
@ -103,7 +102,6 @@ type Service struct {
|
||||
registrations accesscontrol.RegistrationList
|
||||
roles map[string]*accesscontrol.RoleDTO
|
||||
store accesscontrol.Store
|
||||
tracer tracing.Tracer
|
||||
sync *migrator.ZanzanaSynchroniser
|
||||
permRegistry permreg.PermissionRegistry
|
||||
}
|
||||
@ -116,7 +114,7 @@ func (s *Service) GetUsageStats(_ context.Context) map[string]any {
|
||||
|
||||
// GetUserPermissions returns user permissions based on built-in roles
|
||||
func (s *Service) GetUserPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.GetUserPermissionsOSS")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.GetUserPermissions")
|
||||
defer span.End()
|
||||
|
||||
timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary)
|
||||
@ -130,7 +128,7 @@ func (s *Service) GetUserPermissions(ctx context.Context, user identity.Requeste
|
||||
}
|
||||
|
||||
func (s *Service) getUserPermissions(ctx context.Context, user identity.Requester, _ accesscontrol.Options) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.getUserPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getUserPermissions")
|
||||
defer span.End()
|
||||
|
||||
permissions := make([]accesscontrol.Permission, 0)
|
||||
@ -166,7 +164,7 @@ func (s *Service) getUserPermissions(ctx context.Context, user identity.Requeste
|
||||
}
|
||||
|
||||
func (s *Service) getBasicRolePermissions(ctx context.Context, role string, orgID int64) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.getBasicRolePermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getBasicRolePermissions")
|
||||
defer span.End()
|
||||
|
||||
var permissions []accesscontrol.Permission
|
||||
@ -188,7 +186,7 @@ func (s *Service) getBasicRolePermissions(ctx context.Context, role string, orgI
|
||||
}
|
||||
|
||||
func (s *Service) getTeamsPermissions(ctx context.Context, teamIDs []int64, orgID int64) (map[int64][]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.getTeamsPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getTeamsPermissions")
|
||||
defer span.End()
|
||||
|
||||
teamPermissions, err := s.store.GetTeamsPermissions(ctx, accesscontrol.GetUserPermissionsQuery{
|
||||
@ -208,7 +206,7 @@ func (s *Service) getTeamsPermissions(ctx context.Context, teamIDs []int64, orgI
|
||||
|
||||
// Returns only permissions directly assigned to user, without basic role and team permissions
|
||||
func (s *Service) getUserDirectPermissions(ctx context.Context, user identity.Requester) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.getUserDirectPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getUserDirectPermissions")
|
||||
defer span.End()
|
||||
|
||||
var userID int64
|
||||
@ -240,7 +238,7 @@ func (s *Service) getUserDirectPermissions(ctx context.Context, user identity.Re
|
||||
}
|
||||
|
||||
func (s *Service) getCachedUserPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.getCachedUserPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedUserPermissions")
|
||||
defer span.End()
|
||||
|
||||
permissions := []accesscontrol.Permission{}
|
||||
@ -266,7 +264,7 @@ func (s *Service) getCachedUserPermissions(ctx context.Context, user identity.Re
|
||||
}
|
||||
|
||||
func (s *Service) getCachedBasicRolesPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options, permissions []accesscontrol.Permission) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.getCachedBasicRolesPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedBasicRolesPermissions")
|
||||
defer span.End()
|
||||
|
||||
basicRoles := accesscontrol.GetOrgRoles(user)
|
||||
@ -283,7 +281,7 @@ func (s *Service) getCachedBasicRolesPermissions(ctx context.Context, user ident
|
||||
}
|
||||
|
||||
func (s *Service) getCachedBasicRolePermissions(ctx context.Context, role string, orgID int64, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.getCachedBasicRolePermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedBasicRolePermissions")
|
||||
defer span.End()
|
||||
|
||||
key := accesscontrol.GetBasicRolePermissionCacheKey(role, orgID)
|
||||
@ -294,7 +292,7 @@ func (s *Service) getCachedBasicRolePermissions(ctx context.Context, role string
|
||||
}
|
||||
|
||||
func (s *Service) getCachedUserDirectPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.getCachedUserDirectPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedUserDirectPermissions")
|
||||
defer span.End()
|
||||
|
||||
key := accesscontrol.GetUserDirectPermissionCacheKey(user)
|
||||
@ -308,7 +306,7 @@ type getPermissionsFunc = func(ctx context.Context) ([]accesscontrol.Permission,
|
||||
|
||||
// Generic method for getting various permissions from cache
|
||||
func (s *Service) getCachedPermissions(ctx context.Context, key string, getPermissionsFn getPermissionsFunc, options accesscontrol.Options) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.getCachedPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedPermissions")
|
||||
defer span.End()
|
||||
|
||||
if !options.ReloadCache {
|
||||
@ -333,7 +331,7 @@ func (s *Service) getCachedPermissions(ctx context.Context, key string, getPermi
|
||||
}
|
||||
|
||||
func (s *Service) getCachedTeamsPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options, permissions []accesscontrol.Permission) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.getCachedTeamsPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedTeamsPermissions")
|
||||
defer span.End()
|
||||
|
||||
teams := user.GetTeams()
|
||||
@ -380,14 +378,14 @@ func (s *Service) ClearUserPermissionCache(user identity.Requester) {
|
||||
}
|
||||
|
||||
func (s *Service) DeleteUserPermissions(ctx context.Context, orgID int64, userID int64) error {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.DeleteUserPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.DeleteUserPermissions")
|
||||
defer span.End()
|
||||
|
||||
return s.store.DeleteUserPermissions(ctx, orgID, userID)
|
||||
}
|
||||
|
||||
func (s *Service) DeleteTeamPermissions(ctx context.Context, orgID int64, teamID int64) error {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.DeleteTeamPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.DeleteTeamPermissions")
|
||||
defer span.End()
|
||||
|
||||
return s.store.DeleteTeamPermissions(ctx, orgID, teamID)
|
||||
@ -419,7 +417,7 @@ func (s *Service) DeclareFixedRoles(registrations ...accesscontrol.RoleRegistrat
|
||||
|
||||
// RegisterFixedRoles registers all declared roles in RAM
|
||||
func (s *Service) RegisterFixedRoles(ctx context.Context) error {
|
||||
_, span := s.tracer.Start(ctx, "authz.RegisterFixedRoles")
|
||||
_, span := tracer.Start(ctx, "accesscontrol.acimpl.RegisterFixedRoles")
|
||||
defer span.End()
|
||||
|
||||
s.registrations.Range(func(registration accesscontrol.RoleRegistration) bool {
|
||||
@ -445,7 +443,7 @@ func (s *Service) RegisterFixedRoles(ctx context.Context) error {
|
||||
// DeclarePluginRoles allow the caller to declare, to the service, plugin roles and their assignments
|
||||
// to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin"
|
||||
func (s *Service) DeclarePluginRoles(ctx context.Context, ID, name string, regs []plugins.RoleRegistration) error {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.DeclarePluginRoles")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.DeclarePluginRoles")
|
||||
defer span.End()
|
||||
|
||||
// Protect behind feature toggle
|
||||
@ -488,7 +486,7 @@ func GetActionFilter(options accesscontrol.SearchOptions) func(action string) bo
|
||||
|
||||
// SearchUsersPermissions returns all users' permissions filtered by action prefixes
|
||||
func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Requester, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.SearchUsersPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.SearchUsersPermissions")
|
||||
defer span.End()
|
||||
|
||||
// Limit roles to available in OSS
|
||||
@ -602,7 +600,7 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
|
||||
}
|
||||
|
||||
func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.SearchUserPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.SearchUserPermissions")
|
||||
defer span.End()
|
||||
|
||||
timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary)
|
||||
@ -619,7 +617,7 @@ func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, search
|
||||
}
|
||||
|
||||
func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.searchUserPermissions")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.searchUserPermissions")
|
||||
defer span.End()
|
||||
|
||||
userID, err := searchOptions.ComputeUserID()
|
||||
@ -672,7 +670,7 @@ func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, search
|
||||
}
|
||||
|
||||
func (s *Service) searchUserPermissionsFromCache(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, bool) {
|
||||
_, span := s.tracer.Start(ctx, "authz.searchUserPermissionsFromCache")
|
||||
_, span := tracer.Start(ctx, "accesscontrol.acimpl.searchUserPermissionsFromCache")
|
||||
defer span.End()
|
||||
|
||||
userID, err := searchOptions.ComputeUserID()
|
||||
@ -714,7 +712,7 @@ func PermissionMatchesSearchOptions(permission accesscontrol.Permission, searchO
|
||||
}
|
||||
|
||||
func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.SaveExternalServiceRole")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.SaveExternalServiceRole")
|
||||
defer span.End()
|
||||
|
||||
if !s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) {
|
||||
@ -730,7 +728,7 @@ func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol
|
||||
}
|
||||
|
||||
func (s *Service) DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error {
|
||||
ctx, span := s.tracer.Start(ctx, "authz.DeleteExternalServiceRole")
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.DeleteExternalServiceRole")
|
||||
defer span.End()
|
||||
|
||||
if !s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) {
|
||||
@ -748,7 +746,7 @@ func (*Service) SyncUserRoles(ctx context.Context, orgID int64, cmd accesscontro
|
||||
}
|
||||
|
||||
func (s *Service) GetRoleByName(ctx context.Context, orgID int64, roleName string) (*accesscontrol.RoleDTO, error) {
|
||||
_, span := s.tracer.Start(ctx, "authz.GetRoleByName")
|
||||
_, span := tracer.Start(ctx, "accesscontrol.acimpl.GetRoleByName")
|
||||
defer span.End()
|
||||
|
||||
err := accesscontrol.ErrRoleNotFound
|
||||
|
@ -42,7 +42,6 @@ func setupTestEnv(t testing.TB) *Service {
|
||||
log: log.New("accesscontrol"),
|
||||
registrations: accesscontrol.RegistrationList{},
|
||||
roles: accesscontrol.BuildBasicRoleDefinitions(),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
store: database.ProvideService(db.InitTestReplDB(t)),
|
||||
permRegistry: permreg.ProvidePermissionRegistry(),
|
||||
}
|
||||
|
@ -10,8 +10,11 @@ import (
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"go.opentelemetry.io/otel"
|
||||
)
|
||||
|
||||
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/accesscontrol/api")
|
||||
|
||||
func NewAccessControlAPI(router routing.RouteRegister, accesscontrol ac.AccessControl, service ac.Service,
|
||||
features featuremgmt.FeatureToggles) *AccessControlAPI {
|
||||
return &AccessControlAPI{
|
||||
@ -43,8 +46,11 @@ func (api *AccessControlAPI) RegisterAPIEndpoints() {
|
||||
|
||||
// GET /api/access-control/user/actions
|
||||
func (api *AccessControlAPI) getUserActions(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.api.getUserActions")
|
||||
defer span.End()
|
||||
|
||||
reloadCache := c.QueryBool("reloadcache")
|
||||
permissions, err := api.Service.GetUserPermissions(c.Req.Context(),
|
||||
permissions, err := api.Service.GetUserPermissions(ctx,
|
||||
c.SignedInUser, ac.Options{ReloadCache: reloadCache})
|
||||
if err != nil {
|
||||
return response.JSON(http.StatusInternalServerError, err)
|
||||
@ -55,18 +61,24 @@ func (api *AccessControlAPI) getUserActions(c *contextmodel.ReqContext) response
|
||||
|
||||
// GET /api/access-control/user/permissions
|
||||
func (api *AccessControlAPI) getUserPermissions(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.api.getUserPermissions")
|
||||
defer span.End()
|
||||
|
||||
reloadCache := c.QueryBool("reloadcache")
|
||||
permissions, err := api.Service.GetUserPermissions(c.Req.Context(),
|
||||
permissions, err := api.Service.GetUserPermissions(ctx,
|
||||
c.SignedInUser, ac.Options{ReloadCache: reloadCache})
|
||||
if err != nil {
|
||||
return response.JSON(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, ac.GroupScopesByActionContext(c.Req.Context(), permissions))
|
||||
return response.JSON(http.StatusOK, ac.GroupScopesByActionContext(ctx, permissions))
|
||||
}
|
||||
|
||||
// GET /api/access-control/users/permissions/search
|
||||
func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.api.searchUsersPermissions")
|
||||
defer span.End()
|
||||
|
||||
searchOptions := ac.SearchOptions{
|
||||
ActionPrefix: c.Query("actionPrefix"),
|
||||
Action: c.Query("action"),
|
||||
@ -84,7 +96,7 @@ func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext)
|
||||
}
|
||||
|
||||
// Compute metadata
|
||||
permissions, err := api.Service.SearchUsersPermissions(c.Req.Context(), c.SignedInUser, searchOptions)
|
||||
permissions, err := api.Service.SearchUsersPermissions(ctx, c.SignedInUser, searchOptions)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "could not get org user permissions", err)
|
||||
}
|
||||
|
@ -8,8 +8,11 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"go.opentelemetry.io/otel"
|
||||
)
|
||||
|
||||
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/accesscontrol/database")
|
||||
|
||||
const (
|
||||
// userAssignsSQL is a query to select all users assignments.
|
||||
userAssignsSQL = `SELECT ur.user_id, ur.org_id, ur.role_id
|
||||
@ -45,6 +48,9 @@ type AccessControlStore struct {
|
||||
}
|
||||
|
||||
func (s *AccessControlStore) GetUserPermissions(ctx context.Context, query accesscontrol.GetUserPermissionsQuery) ([]accesscontrol.Permission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.database.GetUserPermissions")
|
||||
defer span.End()
|
||||
|
||||
result := make([]accesscontrol.Permission, 0)
|
||||
err := s.sql.ReadReplica().WithDbSession(ctx, func(sess *db.Session) error {
|
||||
if query.UserID == 0 && len(query.TeamIDs) == 0 && len(query.Roles) == 0 {
|
||||
@ -100,6 +106,9 @@ func (p teamPermission) Permission() accesscontrol.Permission {
|
||||
}
|
||||
|
||||
func (s *AccessControlStore) GetTeamsPermissions(ctx context.Context, query accesscontrol.GetUserPermissionsQuery) (map[int64][]accesscontrol.Permission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.database.GetTeamsPermissions")
|
||||
defer span.End()
|
||||
|
||||
teams := query.TeamIDs
|
||||
orgID := query.OrgID
|
||||
rolePrefixes := query.RolePrefixes
|
||||
@ -156,6 +165,9 @@ func (s *AccessControlStore) GetTeamsPermissions(ctx context.Context, query acce
|
||||
|
||||
// SearchUsersPermissions returns the list of user permissions in specific organization indexed by UserID
|
||||
func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID int64, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.database.SearchUsersPermissions")
|
||||
defer span.End()
|
||||
|
||||
type UserRBACPermission struct {
|
||||
UserID int64 `xorm:"user_id"`
|
||||
Action string `xorm:"action"`
|
||||
@ -278,6 +290,9 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i
|
||||
|
||||
// GetUsersBasicRoles returns the list of user basic roles (Admin, Editor, Viewer, Grafana Admin) indexed by UserID
|
||||
func (s *AccessControlStore) GetUsersBasicRoles(ctx context.Context, userFilter []int64, orgID int64) (map[int64][]string, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.database.GetUsersBasicRoles")
|
||||
defer span.End()
|
||||
|
||||
type UserOrgRole struct {
|
||||
UserID int64 `xorm:"id"`
|
||||
OrgRole string `xorm:"role"`
|
||||
@ -318,6 +333,9 @@ func (s *AccessControlStore) GetUsersBasicRoles(ctx context.Context, userFilter
|
||||
}
|
||||
|
||||
func (s *AccessControlStore) DeleteUserPermissions(ctx context.Context, orgID, userID int64) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.database.DeleteUserPermissions")
|
||||
defer span.End()
|
||||
|
||||
err := s.sql.DB().WithDbSession(ctx, func(sess *db.Session) error {
|
||||
roleDeleteQuery := "DELETE FROM user_role WHERE user_id = ?"
|
||||
roleDeleteParams := []any{roleDeleteQuery, userID}
|
||||
@ -383,6 +401,9 @@ func (s *AccessControlStore) DeleteUserPermissions(ctx context.Context, orgID, u
|
||||
}
|
||||
|
||||
func (s *AccessControlStore) DeleteTeamPermissions(ctx context.Context, orgID, teamID int64) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.database.DeleteTeamPermissions")
|
||||
defer span.End()
|
||||
|
||||
err := s.sql.DB().WithDbSession(ctx, func(sess *db.Session) error {
|
||||
roleDeleteQuery := "DELETE FROM team_role WHERE team_id = ? AND org_id = ?"
|
||||
roleDeleteParams := []any{roleDeleteQuery, teamID, orgID}
|
||||
|
@ -17,6 +17,9 @@ func extServiceRoleName(externalServiceID string) string {
|
||||
}
|
||||
|
||||
func (s *AccessControlStore) DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.database.DeleteExternalServiceRole")
|
||||
defer span.End()
|
||||
|
||||
uid := accesscontrol.PrefixedRoleUID(extServiceRoleName(externalServiceID))
|
||||
return s.sql.DB().WithDbSession(ctx, func(sess *db.Session) error {
|
||||
stored, errGet := getRoleByUID(ctx, sess, uid)
|
||||
@ -52,6 +55,9 @@ func (s *AccessControlStore) DeleteExternalServiceRole(ctx context.Context, exte
|
||||
}
|
||||
|
||||
func (s *AccessControlStore) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.database.SaveExternalServiceRole")
|
||||
defer span.End()
|
||||
|
||||
role := genExternalServiceRole(cmd)
|
||||
assignment := genExternalServiceAssignment(cmd)
|
||||
|
||||
@ -103,6 +109,9 @@ func genExternalServiceAssignment(cmd accesscontrol.SaveExternalServiceRoleComma
|
||||
}
|
||||
|
||||
func getRoleByUID(ctx context.Context, sess *db.Session, uid string) (*accesscontrol.Role, error) {
|
||||
_, span := tracer.Start(ctx, "accesscontrol.database.getRoleByUID")
|
||||
defer span.End()
|
||||
|
||||
var role accesscontrol.Role
|
||||
has, err := sess.Where("uid = ?", uid).Get(&role)
|
||||
if err != nil {
|
||||
@ -115,6 +124,9 @@ func getRoleByUID(ctx context.Context, sess *db.Session, uid string) (*accesscon
|
||||
}
|
||||
|
||||
func getRoleAssignments(ctx context.Context, sess *db.Session, roleID int64) ([]accesscontrol.UserRole, error) {
|
||||
_, span := tracer.Start(ctx, "accesscontrol.database.GgetRoleAssignments")
|
||||
defer span.End()
|
||||
|
||||
var assignements []accesscontrol.UserRole
|
||||
if err := sess.Where("role_id = ?", roleID).Find(&assignements); err != nil {
|
||||
return nil, err
|
||||
@ -123,6 +135,9 @@ func getRoleAssignments(ctx context.Context, sess *db.Session, roleID int64) ([]
|
||||
}
|
||||
|
||||
func getRolePermissions(ctx context.Context, sess *db.Session, id int64) ([]accesscontrol.Permission, error) {
|
||||
_, span := tracer.Start(ctx, "accesscontrol.database.getRolePermissions")
|
||||
defer span.End()
|
||||
|
||||
var permissions []accesscontrol.Permission
|
||||
if err := sess.Where("role_id = ?", id).Find(&permissions); err != nil {
|
||||
return nil, err
|
||||
@ -157,6 +172,9 @@ func permissionDiff(previous, new []accesscontrol.Permission) (added, removed []
|
||||
}
|
||||
|
||||
func (*AccessControlStore) saveRole(ctx context.Context, sess *db.Session, role *accesscontrol.Role) (*accesscontrol.Role, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.database.saveRole")
|
||||
defer span.End()
|
||||
|
||||
existingRole, err := getRoleByUID(ctx, sess, role.UID)
|
||||
if err != nil && !errors.Is(err, accesscontrol.ErrRoleNotFound) {
|
||||
return nil, err
|
||||
@ -177,6 +195,9 @@ func (*AccessControlStore) saveRole(ctx context.Context, sess *db.Session, role
|
||||
}
|
||||
|
||||
func (*AccessControlStore) savePermissions(ctx context.Context, sess *db.Session, roleID int64, permissions []accesscontrol.Permission) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.database.savePermissions")
|
||||
defer span.End()
|
||||
|
||||
now := time.Now()
|
||||
storedPermissions, err := getRolePermissions(ctx, sess, roleID)
|
||||
if err != nil {
|
||||
@ -210,6 +231,9 @@ func (*AccessControlStore) savePermissions(ctx context.Context, sess *db.Session
|
||||
}
|
||||
|
||||
func (*AccessControlStore) saveUserAssignment(ctx context.Context, sess *db.Session, assignment accesscontrol.UserRole) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.database.saveUserAssignment")
|
||||
defer span.End()
|
||||
|
||||
// alreadyAssigned checks if the assignment already exists without accounting for the organization
|
||||
assignments, errGetAssigns := getRoleAssignments(ctx, sess, assignment.RoleID)
|
||||
if errGetAssigns != nil {
|
||||
|
@ -13,6 +13,9 @@ import (
|
||||
)
|
||||
|
||||
func GetAccessPolicies(ctx context.Context, orgID int64, sql *session.SessionDB, resolver accesscontrol.ScopeAttributeResolverFunc) ([]accesspolicy.Resource, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.database.GetAccessPolicies")
|
||||
defer span.End()
|
||||
|
||||
type permissionInfo struct {
|
||||
RoleUID string
|
||||
RoleName string
|
||||
|
@ -104,6 +104,9 @@ func (p permissionEvaluator) EvaluateCustom(fn CheckerFn) (bool, error) {
|
||||
}
|
||||
|
||||
func (p permissionEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.permissionEvaluatorMutateScopes")
|
||||
defer span.End()
|
||||
|
||||
if p.Scopes == nil {
|
||||
return EvalPermission(p.Action), nil
|
||||
}
|
||||
@ -172,6 +175,9 @@ func (a allEvaluator) EvaluateCustom(fn CheckerFn) (bool, error) {
|
||||
}
|
||||
|
||||
func (a allEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.allEvaluator.MutateScopes")
|
||||
defer span.End()
|
||||
|
||||
resolved := false
|
||||
modified := make([]Evaluator, 0, len(a.allOf))
|
||||
for _, e := range a.allOf {
|
||||
@ -245,6 +251,9 @@ func (a anyEvaluator) EvaluateCustom(fn CheckerFn) (bool, error) {
|
||||
}
|
||||
|
||||
func (a anyEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.anyEvaluator.MutateScopes")
|
||||
defer span.End()
|
||||
|
||||
resolved := false
|
||||
modified := make([]Evaluator, 0, len(a.anyOf))
|
||||
for _, e := range a.anyOf {
|
||||
|
@ -11,6 +11,9 @@ type Metadata map[string]bool
|
||||
|
||||
// GetResourcesMetadata returns a map of accesscontrol metadata, listing for each resource, users available actions
|
||||
func GetResourcesMetadata(ctx context.Context, permissions map[string][]string, prefix string, resourceIDs map[string]bool) map[string]Metadata {
|
||||
_, span := tracer.Start(ctx, "accesscontrol.GetResourcesMetadata")
|
||||
defer span.End()
|
||||
|
||||
wildcards := WildcardsFromPrefix(prefix)
|
||||
|
||||
// index of the prefix in the scope
|
||||
|
@ -29,6 +29,10 @@ import (
|
||||
func Middleware(ac AccessControl) func(Evaluator) web.Handler {
|
||||
return func(evaluator Evaluator) web.Handler {
|
||||
return func(c *contextmodel.ReqContext) {
|
||||
ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.Middleware")
|
||||
defer span.End()
|
||||
c.Req = c.Req.WithContext(ctx)
|
||||
|
||||
if c.AllowAnonymous {
|
||||
forceLogin, _ := strconv.ParseBool(c.Req.URL.Query().Get("forceLogin")) // ignoring error, assuming false for non-true values is ok.
|
||||
orgID, err := strconv.ParseInt(c.Req.URL.Query().Get("orgId"), 10, 64)
|
||||
@ -59,7 +63,11 @@ func Middleware(ac AccessControl) func(Evaluator) web.Handler {
|
||||
}
|
||||
|
||||
func authorize(c *contextmodel.ReqContext, ac AccessControl, user identity.Requester, evaluator Evaluator) {
|
||||
injected, err := evaluator.MutateScopes(c.Req.Context(), scopeInjector(scopeParams{
|
||||
ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.authorize")
|
||||
defer span.End()
|
||||
c.Req = c.Req.WithContext(ctx)
|
||||
|
||||
injected, err := evaluator.MutateScopes(ctx, scopeInjector(scopeParams{
|
||||
OrgID: user.GetOrgID(),
|
||||
URLParams: web.Params(c.Req),
|
||||
}))
|
||||
@ -68,7 +76,7 @@ func authorize(c *contextmodel.ReqContext, ac AccessControl, user identity.Reque
|
||||
return
|
||||
}
|
||||
|
||||
hasAccess, err := ac.Evaluate(c.Req.Context(), user, injected)
|
||||
hasAccess, err := ac.Evaluate(ctx, user, injected)
|
||||
if !hasAccess || err != nil {
|
||||
deny(c, injected, err)
|
||||
return
|
||||
@ -177,6 +185,10 @@ type OrgIDGetter func(c *contextmodel.ReqContext) (int64, error)
|
||||
func AuthorizeInOrgMiddleware(ac AccessControl, authnService authn.Service) func(OrgIDGetter, Evaluator) web.Handler {
|
||||
return func(getTargetOrg OrgIDGetter, evaluator Evaluator) web.Handler {
|
||||
return func(c *contextmodel.ReqContext) {
|
||||
ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.AuthorizeInOrgMiddleware")
|
||||
defer span.End()
|
||||
c.Req = c.Req.WithContext(ctx)
|
||||
|
||||
targetOrgID, err := getTargetOrg(c)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrInvalidRequestBody) {
|
||||
|
@ -8,12 +8,15 @@ import (
|
||||
"strings"
|
||||
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
"go.opentelemetry.io/otel"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
||||
)
|
||||
|
||||
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/accesscontrol/migrator")
|
||||
|
||||
// A TupleCollector is responsible to build and store [openfgav1.TupleKey] into provided tuple map.
|
||||
// They key used should be a unique group key for the collector so we can skip over an already synced group.
|
||||
type TupleCollector func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error
|
||||
@ -54,6 +57,9 @@ func NewZanzanaSynchroniser(client zanzana.Client, store db.DB, collectors ...Tu
|
||||
// Sync runs all collectors and tries to write all collected tuples.
|
||||
// It will skip over any "sync group" that has already been written.
|
||||
func (z *ZanzanaSynchroniser) Sync(ctx context.Context) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.migrator.Sync")
|
||||
defer span.End()
|
||||
|
||||
tuplesMap := make(map[string][]*openfgav1.TupleKey)
|
||||
|
||||
for _, c := range z.collectors {
|
||||
@ -145,6 +151,9 @@ func managedPermissionsCollector(store db.DB) TupleCollector {
|
||||
|
||||
func teamMembershipCollector(store db.DB) TupleCollector {
|
||||
return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.migrator.teamMembershipCollector")
|
||||
defer span.End()
|
||||
|
||||
const collectorID = "team_membership"
|
||||
const query = `
|
||||
SELECT t.uid as team_uid, u.uid as user_uid, tm.permission
|
||||
@ -191,6 +200,9 @@ func teamMembershipCollector(store db.DB) TupleCollector {
|
||||
// folderTreeCollector collects folder tree structure and writes it as relation tuples
|
||||
func folderTreeCollector(store db.DB) TupleCollector {
|
||||
return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.migrator.folderTreeCollector")
|
||||
defer span.End()
|
||||
|
||||
const collectorID = "folder"
|
||||
const query = `
|
||||
SELECT uid, parent_uid, org_id FROM folder
|
||||
@ -236,6 +248,9 @@ func folderTreeCollector(store db.DB) TupleCollector {
|
||||
// dashboardFolderCollector collects information about dashboards parent folders
|
||||
func dashboardFolderCollector(store db.DB) TupleCollector {
|
||||
return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.migrator.dashboardFolderCollector")
|
||||
defer span.End()
|
||||
|
||||
const collectorID = "folder"
|
||||
const query = `
|
||||
SELECT org_id, uid, folder_uid, is_folder FROM dashboard WHERE is_folder = 0 AND folder_uid IS NOT NULL
|
||||
|
@ -113,6 +113,9 @@ func ProvideDashboardPermissions(
|
||||
Resource: "dashboards",
|
||||
ResourceAttribute: "uid",
|
||||
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.ProvideDashboardPermissions.ResourceValidator")
|
||||
defer span.End()
|
||||
|
||||
dashboard, err := getDashboard(ctx, orgID, resourceID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -10,8 +10,11 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"go.opentelemetry.io/otel"
|
||||
)
|
||||
|
||||
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/accesscontrol/ossaccesscontrol")
|
||||
|
||||
// DatasourceQueryActions contains permissions to read information
|
||||
// about a data source and submit arbitrary queries to it.
|
||||
var DatasourceQueryActions = []string{
|
||||
@ -51,6 +54,9 @@ func (e DatasourcePermissionsService) SetBuiltInRolePermission(ctx context.Conte
|
||||
// if an OSS/unlicensed instance is upgraded to Enterprise/licensed.
|
||||
// https://github.com/grafana/identity-access-team/issues/672
|
||||
func (e DatasourcePermissionsService) SetPermissions(ctx context.Context, orgID int64, resourceID string, commands ...accesscontrol.SetResourcePermissionCommand) ([]accesscontrol.ResourcePermission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.SetPermissions")
|
||||
defer span.End()
|
||||
|
||||
dbCommands := make([]resourcepermissions.SetResourcePermissionsCommand, 0, len(commands))
|
||||
for _, cmd := range commands {
|
||||
// Only set query permissions for built-in roles; do not set permissions for data sources with * as UID, as this would grant wildcard permissions
|
||||
@ -75,6 +81,9 @@ func (e DatasourcePermissionsService) SetPermissions(ctx context.Context, orgID
|
||||
}
|
||||
|
||||
func (e DatasourcePermissionsService) DeleteResourcePermissions(ctx context.Context, orgID int64, resourceID string) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.DeleteResourcePermissions")
|
||||
defer span.End()
|
||||
|
||||
return e.store.DeleteResourcePermissions(ctx, orgID, &resourcepermissions.DeleteResourcePermissionsCmd{
|
||||
Resource: datasources.ScopeRoot,
|
||||
ResourceAttribute: "uid",
|
||||
|
@ -95,6 +95,9 @@ func ProvideFolderPermissions(
|
||||
Resource: "folders",
|
||||
ResourceAttribute: "uid",
|
||||
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.ProvideFolderPermissions.ResourceValidator")
|
||||
defer span.End()
|
||||
|
||||
query := &dashboards.GetDashboardQuery{UID: resourceID, OrgID: orgID}
|
||||
queryResult, err := dashboardStore.GetDashboard(ctx, query)
|
||||
if err != nil {
|
||||
|
@ -44,6 +44,9 @@ func ProvideServiceAccountPermissions(
|
||||
Resource: "serviceaccounts",
|
||||
ResourceAttribute: "id",
|
||||
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.ProvideServiceAccountPermissions.ResourceValidator")
|
||||
defer span.End()
|
||||
|
||||
id, err := strconv.ParseInt(resourceID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -46,12 +46,15 @@ func ProvideTeamPermissions(
|
||||
ResourceAttribute: "id",
|
||||
OnlyManaged: true,
|
||||
ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.ProvideTeamerPermissions.ResourceValidator")
|
||||
defer span.End()
|
||||
|
||||
id, err := strconv.ParseInt(resourceID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = teamService.GetTeamByID(context.Background(), &team.GetTeamByIDQuery{
|
||||
_, err = teamService.GetTeamByID(ctx, &team.GetTeamByIDQuery{
|
||||
OrgID: orgID,
|
||||
ID: id,
|
||||
})
|
||||
|
@ -62,6 +62,9 @@ func (s *Resolvers) AddScopeAttributeResolver(prefix string, resolver ScopeAttri
|
||||
|
||||
func (s *Resolvers) GetScopeAttributeMutator(orgID int64) ScopeAttributeMutator {
|
||||
return func(ctx context.Context, scope string) ([]string, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.GetScopeAttributeMutator")
|
||||
defer span.End()
|
||||
|
||||
key := getScopeCacheKey(orgID, scope)
|
||||
// Check cache before computing the scope
|
||||
if cachedScope, ok := s.cache.Get(key); ok {
|
||||
|
@ -13,8 +13,11 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
"go.opentelemetry.io/otel"
|
||||
)
|
||||
|
||||
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/accesscontrol/resourcepermissions")
|
||||
|
||||
type api struct {
|
||||
cfg *setting.Cfg
|
||||
ac accesscontrol.AccessControl
|
||||
@ -140,6 +143,10 @@ type getResourcePermissionsResponse []resourcePermissionDTO
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (a *api) getPermissions(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.resourcepermissions.getPermissions")
|
||||
defer span.End()
|
||||
c.Req = c.Req.WithContext(ctx)
|
||||
|
||||
resourceID := web.Params(c.Req)[":resourceID"]
|
||||
|
||||
permissions, err := a.service.GetPermissions(c.Req.Context(), c.SignedInUser, resourceID)
|
||||
@ -227,6 +234,10 @@ type SetResourcePermissionsForUserParams struct {
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (a *api) setUserPermission(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.resourcepermissions.setUserPermission")
|
||||
defer span.End()
|
||||
c.Req = c.Req.WithContext(ctx)
|
||||
|
||||
userID, err := strconv.ParseInt(web.Params(c.Req)[":userID"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Err(ErrInvalidParam.Build(ErrInvalidParamData("userID", err)))
|
||||
@ -280,6 +291,10 @@ type SetResourcePermissionsForTeamParams struct {
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (a *api) setTeamPermission(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.resourcepermissions.setTeamPermission")
|
||||
defer span.End()
|
||||
c.Req = c.Req.WithContext(ctx)
|
||||
|
||||
teamID, err := strconv.ParseInt(web.Params(c.Req)[":teamID"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Err(ErrInvalidParam.Build(ErrInvalidParamData("teamID", err)))
|
||||
@ -333,6 +348,10 @@ type SetResourcePermissionsForBuiltInRoleParams struct {
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (a *api) setBuiltinRolePermission(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.resourcepermissions.setBuiltinRolePermission")
|
||||
defer span.End()
|
||||
c.Req = c.Req.WithContext(ctx)
|
||||
|
||||
builtInRole := web.Params(c.Req)[":builtInRole"]
|
||||
resourceID := web.Params(c.Req)[":resourceID"]
|
||||
|
||||
@ -379,6 +398,9 @@ type SetResourcePermissionsParams struct {
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (a *api) setPermissions(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.resourcepermissions.setPermissions")
|
||||
defer span.End()
|
||||
|
||||
resourceID := web.Params(c.Req)[":resourceID"]
|
||||
|
||||
cmd := setPermissionsCommand{}
|
||||
@ -386,7 +408,7 @@ func (a *api) setPermissions(c *contextmodel.ReqContext) response.Response {
|
||||
return response.Error(http.StatusBadRequest, "Bad request data: "+err.Error(), err)
|
||||
}
|
||||
|
||||
_, err := a.service.SetPermissions(c.Req.Context(), c.SignedInUser.GetOrgID(), resourceID, cmd.Permissions...)
|
||||
_, err := a.service.SetPermissions(ctx, c.SignedInUser.GetOrgID(), resourceID, cmd.Permissions...)
|
||||
if err != nil {
|
||||
return response.Err(err)
|
||||
}
|
||||
|
@ -138,6 +138,9 @@ type Service struct {
|
||||
}
|
||||
|
||||
func (s *Service) GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]accesscontrol.ResourcePermission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.GetPermissions")
|
||||
defer span.End()
|
||||
|
||||
var inheritedScopes []string
|
||||
if s.options.InheritedScopesSolver != nil {
|
||||
var err error
|
||||
@ -206,6 +209,9 @@ func (s *Service) GetPermissions(ctx context.Context, user identity.Requester, r
|
||||
}
|
||||
|
||||
func (s *Service) SetUserPermission(ctx context.Context, orgID int64, user accesscontrol.User, resourceID, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.SetUserPermission")
|
||||
defer span.End()
|
||||
|
||||
actions, err := s.mapPermission(permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -229,6 +235,9 @@ func (s *Service) SetUserPermission(ctx context.Context, orgID int64, user acces
|
||||
}
|
||||
|
||||
func (s *Service) SetTeamPermission(ctx context.Context, orgID, teamID int64, resourceID, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.SetTeamPermission")
|
||||
defer span.End()
|
||||
|
||||
actions, err := s.mapPermission(permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -252,6 +261,9 @@ func (s *Service) SetTeamPermission(ctx context.Context, orgID, teamID int64, re
|
||||
}
|
||||
|
||||
func (s *Service) SetBuiltInRolePermission(ctx context.Context, orgID int64, builtInRole, resourceID, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.SetBuiltInRolePermission")
|
||||
defer span.End()
|
||||
|
||||
actions, err := s.mapPermission(permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -278,6 +290,9 @@ func (s *Service) SetPermissions(
|
||||
ctx context.Context, orgID int64, resourceID string,
|
||||
commands ...accesscontrol.SetResourcePermissionCommand,
|
||||
) ([]accesscontrol.ResourcePermission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.SetPermissions")
|
||||
defer span.End()
|
||||
|
||||
if err := s.validateResource(ctx, orgID, resourceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -355,6 +370,9 @@ func (s *Service) mapPermission(permission string) ([]string, error) {
|
||||
}
|
||||
|
||||
func (s *Service) validateResource(ctx context.Context, orgID int64, resourceID string) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.validateResource")
|
||||
defer span.End()
|
||||
|
||||
if s.options.ResourceValidator != nil {
|
||||
return s.options.ResourceValidator(ctx, orgID, resourceID)
|
||||
}
|
||||
@ -362,6 +380,9 @@ func (s *Service) validateResource(ctx context.Context, orgID int64, resourceID
|
||||
}
|
||||
|
||||
func (s *Service) validateUser(ctx context.Context, orgID, userID int64) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.validateUser")
|
||||
defer span.End()
|
||||
|
||||
if !s.options.Assignments.Users {
|
||||
return ErrInvalidAssignment.Build(ErrInvalidAssignmentData("users"))
|
||||
}
|
||||
@ -376,6 +397,9 @@ func (s *Service) validateUser(ctx context.Context, orgID, userID int64) error {
|
||||
}
|
||||
|
||||
func (s *Service) validateTeam(ctx context.Context, orgID, teamID int64) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.validateTeam")
|
||||
defer span.End()
|
||||
|
||||
if !s.options.Assignments.Teams {
|
||||
return ErrInvalidAssignment.Build(ErrInvalidAssignmentData("teams"))
|
||||
}
|
||||
@ -391,7 +415,10 @@ func (s *Service) validateTeam(ctx context.Context, orgID, teamID int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) validateBuiltinRole(_ context.Context, builtinRole string) error {
|
||||
func (s *Service) validateBuiltinRole(ctx context.Context, builtinRole string) error {
|
||||
_, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.validateBuiltinRole")
|
||||
defer span.End()
|
||||
|
||||
if !s.options.Assignments.BuiltInRoles {
|
||||
return ErrInvalidAssignment.Build(ErrInvalidAssignmentData("builtInRoles"))
|
||||
}
|
||||
@ -553,6 +580,9 @@ func (a *ActionSetSvc) ExpandActionSetsWithFilter(permissions []accesscontrol.Pe
|
||||
// RegisterActionSets allow the caller to expand the existing action sets with additional permissions
|
||||
// This is intended to be used by plugins, and currently supports extending folder and dashboard action sets
|
||||
func (a *ActionSetSvc) RegisterActionSets(ctx context.Context, pluginID string, registrations []plugins.ActionSet) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.RegisterActionSets")
|
||||
defer span.End()
|
||||
|
||||
if !a.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) || !a.features.IsEnabled(ctx, featuremgmt.FlagAccessControlOnCall) {
|
||||
return nil
|
||||
}
|
||||
|
@ -63,6 +63,9 @@ type DeleteResourcePermissionsCmd struct {
|
||||
}
|
||||
|
||||
func (s *store) DeleteResourcePermissions(ctx context.Context, orgID int64, cmd *DeleteResourcePermissionsCmd) error {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.DeleteResourcePermissions")
|
||||
defer span.End()
|
||||
|
||||
scope := accesscontrol.Scope(cmd.Resource, cmd.ResourceAttribute, cmd.ResourceID)
|
||||
|
||||
err := s.sql.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
||||
@ -88,6 +91,9 @@ func (s *store) SetUserResourcePermission(
|
||||
cmd SetResourcePermissionCommand,
|
||||
hook UserResourceHookFunc,
|
||||
) (*accesscontrol.ResourcePermission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.SetUserResourcePermission")
|
||||
defer span.End()
|
||||
|
||||
if usr.ID == 0 {
|
||||
return nil, user.ErrUserNotFound
|
||||
}
|
||||
@ -125,6 +131,9 @@ func (s *store) SetTeamResourcePermission(
|
||||
cmd SetResourcePermissionCommand,
|
||||
hook TeamResourceHookFunc,
|
||||
) (*accesscontrol.ResourcePermission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.SetTeamResourcePermission")
|
||||
defer span.End()
|
||||
|
||||
if teamID == 0 {
|
||||
return nil, team.ErrTeamNotFound
|
||||
}
|
||||
@ -164,6 +173,9 @@ func (s *store) SetBuiltInResourcePermission(
|
||||
cmd SetResourcePermissionCommand,
|
||||
hook BuiltinResourceHookFunc,
|
||||
) (*accesscontrol.ResourcePermission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.SetBuiltInResourcePermission")
|
||||
defer span.End()
|
||||
|
||||
if !org.RoleType(builtInRole).IsValid() || builtInRole == accesscontrol.RoleGrafanaAdmin {
|
||||
return nil, fmt.Errorf("invalid role: %s", builtInRole)
|
||||
}
|
||||
@ -207,6 +219,9 @@ func (s *store) SetResourcePermissions(
|
||||
commands []SetResourcePermissionsCommand,
|
||||
hooks ResourceHooks,
|
||||
) ([]accesscontrol.ResourcePermission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.SetResourcePermissions")
|
||||
defer span.End()
|
||||
|
||||
var err error
|
||||
var permissions []accesscontrol.ResourcePermission
|
||||
|
||||
@ -288,6 +303,9 @@ func (s *store) setResourcePermission(
|
||||
}
|
||||
|
||||
func (s *store) GetResourcePermissions(ctx context.Context, orgID int64, query GetResourcePermissionsQuery) ([]accesscontrol.ResourcePermission, error) {
|
||||
ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.GetResourcePermissions")
|
||||
defer span.End()
|
||||
|
||||
var result []accesscontrol.ResourcePermission
|
||||
|
||||
err := s.sql.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
|
Loading…
Reference in New Issue
Block a user