Auth: Move access control API to SignedInUser interface (#73144)

* move access control api to SignedInUser interface

* remove unused code

* add logic for reading perms from a specific org

* move the specific org logic to org_user.go

* add a comment

---------

Co-authored-by: IevaVasiljeva <ieva.vasiljeva@grafana.com>
This commit is contained in:
Jo 2023-08-18 12:42:18 +02:00 committed by GitHub
parent 4c9469fc9e
commit 26339f978b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 50 additions and 41 deletions

View File

@ -463,25 +463,20 @@ func (hs *HTTPServer) declareFixedRoles() error {
func (hs *HTTPServer) getAccessControlMetadata(c *contextmodel.ReqContext,
orgID int64, prefix string, resourceID string) ac.Metadata {
ids := map[string]bool{resourceID: true}
return hs.getMultiAccessControlMetadata(c, orgID, prefix, ids)[resourceID]
return hs.getMultiAccessControlMetadata(c, prefix, ids)[resourceID]
}
// getMultiAccessControlMetadata returns the accesscontrol metadata associated with a given set of resources
// Context must contain permissions in the given org (see LoadPermissionsMiddleware or AuthorizeInOrgMiddleware)
func (hs *HTTPServer) getMultiAccessControlMetadata(c *contextmodel.ReqContext,
orgID int64, prefix string, resourceIDs map[string]bool) map[string]ac.Metadata {
prefix string, resourceIDs map[string]bool) map[string]ac.Metadata {
if !c.QueryBool("accesscontrol") {
return map[string]ac.Metadata{}
}
if c.SignedInUser.Permissions == nil {
if len(c.SignedInUser.GetPermissions()) == 0 {
return map[string]ac.Metadata{}
}
permissions, ok := c.SignedInUser.Permissions[orgID]
if !ok {
return map[string]ac.Metadata{}
}
return ac.GetResourcesMetadata(c.Req.Context(), permissions, prefix, resourceIDs)
return ac.GetResourcesMetadata(c.Req.Context(), c.SignedInUser.GetPermissions(), prefix, resourceIDs)
}

View File

@ -6,9 +6,9 @@ import (
"github.com/grafana/grafana/pkg/api/response"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/stats"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
)
@ -67,7 +67,7 @@ func (hs *HTTPServer) AdminGetStats(c *contextmodel.ReqContext) response.Respons
return response.JSON(http.StatusOK, adminStats)
}
func (hs *HTTPServer) getAuthorizedSettings(ctx context.Context, user *user.SignedInUser, bag setting.SettingsBag) (setting.SettingsBag, error) {
func (hs *HTTPServer) getAuthorizedSettings(ctx context.Context, user identity.Requester, bag setting.SettingsBag) (setting.SettingsBag, error) {
eval := func(scope string) (bool, error) {
return hs.AccessControl.Evaluate(ctx, user, ac.EvalPermission(ac.ActionSettingsRead, scope))
}
@ -108,7 +108,7 @@ func (hs *HTTPServer) getAuthorizedSettings(ctx context.Context, user *user.Sign
return authorizedBag, nil
}
func (hs *HTTPServer) getAuthorizedVerboseSettings(ctx context.Context, user *user.SignedInUser, bag setting.VerboseSettingsBag) (setting.VerboseSettingsBag, error) {
func (hs *HTTPServer) getAuthorizedVerboseSettings(ctx context.Context, user identity.Requester, bag setting.VerboseSettingsBag) (setting.VerboseSettingsBag, error) {
eval := func(scope string) (bool, error) {
return hs.AccessControl.Evaluate(ctx, user, ac.EvalPermission(ac.ActionSettingsRead, scope))
}

View File

@ -57,7 +57,7 @@ func (hs *HTTPServer) GetAPIKeys(c *contextmodel.ReqContext) response.Response {
}
}
metadata := hs.getMultiAccessControlMetadata(c, c.OrgID, "apikeys:id", ids)
metadata := hs.getMultiAccessControlMetadata(c, "apikeys:id", ids)
if len(metadata) > 0 {
for _, key := range result {
key.AccessControl = metadata[strconv.FormatInt(key.ID, 10)]

View File

@ -390,7 +390,7 @@ func (hs *HTTPServer) getFolderACMetadata(c *contextmodel.ReqContext, f *folder.
folderIDs[p.UID] = true
}
allMetadata := hs.getMultiAccessControlMetadata(c, c.OrgID, dashboards.ScopeFoldersPrefix, folderIDs)
allMetadata := hs.getMultiAccessControlMetadata(c, dashboards.ScopeFoldersPrefix, folderIDs)
metadata := allMetadata[f.UID]
// Flatten metadata - if any parent has a permission, the child folder inherits it

View File

@ -310,7 +310,15 @@ func (hs *HTTPServer) searchOrgUsersHelper(c *contextmodel.ReqContext, query *or
}
// Get accesscontrol metadata and IPD labels for users in the target org
accessControlMetadata := hs.getMultiAccessControlMetadata(c, query.OrgID, "users:id:", userIDs)
accessControlMetadata := map[string]accesscontrol.Metadata{}
if c.QueryBool("accesscontrol") && c.SignedInUser.Permissions != nil {
// TODO https://github.com/grafana/grafana-authnz-team/issues/268 - user access control service for fetching permissions from another organization
permissions, ok := c.SignedInUser.Permissions[query.OrgID]
if ok {
accessControlMetadata = accesscontrol.GetResourcesMetadata(c.Req.Context(), permissions, "users:id:", userIDs)
}
}
for i := range filteredUsers {
filteredUsers[i].AccessControl = accessControlMetadata[fmt.Sprint(filteredUsers[i].UserID)]
if module, ok := modules[filteredUsers[i].UserID]; ok {

View File

@ -121,8 +121,7 @@ func (hs *HTTPServer) GetPluginList(c *contextmodel.ReqContext) response.Respons
}
// Compute metadata
pluginsMetadata := hs.getMultiAccessControlMetadata(c, c.OrgID,
pluginaccesscontrol.ScopeProvider.GetResourceScope(""), filteredPluginIDs)
pluginsMetadata := hs.getMultiAccessControlMetadata(c, pluginaccesscontrol.ScopeProvider.GetResourceScope(""), filteredPluginIDs)
// Prepare DTO
result := make(dtos.PluginList, 0)

View File

@ -167,7 +167,7 @@ func (hs *HTTPServer) SearchTeams(c *contextmodel.ReqContext) response.Response
teamIDs[strconv.FormatInt(team.ID, 10)] = true
}
metadata := hs.getMultiAccessControlMetadata(c, c.SignedInUser.GetOrgID(), "teams:id:", teamIDs)
metadata := hs.getMultiAccessControlMetadata(c, "teams:id:", teamIDs)
if len(metadata) > 0 {
for _, team := range queryResult.Teams {
team.AccessControl = metadata[strconv.FormatInt(team.ID, 10)]

View File

@ -64,7 +64,7 @@ type SearchOptions struct {
}
type TeamPermissionsService interface {
GetPermissions(ctx context.Context, user *user.SignedInUser, resourceID string) ([]ResourcePermission, error)
GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]ResourcePermission, error)
SetUserPermission(ctx context.Context, orgID int64, user User, resourceID, permission string) (*ResourcePermission, error)
}
@ -86,7 +86,7 @@ type ServiceAccountPermissionsService interface {
type PermissionsService interface {
// GetPermissions returns all permissions for given resourceID
GetPermissions(ctx context.Context, user *user.SignedInUser, resourceID string) ([]ResourcePermission, error)
GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]ResourcePermission, error)
// SetUserPermission sets permission on resource for a user
SetUserPermission(ctx context.Context, orgID int64, user User, resourceID, permission string) (*ResourcePermission, error)
// SetTeamPermission sets permission on resource for a team
@ -151,13 +151,13 @@ var ReqSignedIn = func(c *contextmodel.ReqContext) bool {
}
var ReqGrafanaAdmin = func(c *contextmodel.ReqContext) bool {
return c.IsGrafanaAdmin
return c.SignedInUser.GetIsGrafanaAdmin()
}
// ReqHasRole generates a fallback to check whether the user has a role
// ReqHasRole(org.RoleAdmin) will always return true for Grafana server admins, eg, a Grafana Admin / Viewer role combination
func ReqHasRole(role org.RoleType) func(c *contextmodel.ReqContext) bool {
return func(c *contextmodel.ReqContext) bool { return c.HasRole(role) }
return func(c *contextmodel.ReqContext) bool { return c.SignedInUser.HasRole(role) }
}
func BuildPermissionsMap(permissions []Permission) map[string]bool {

View File

@ -5,7 +5,6 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/user"
)
var _ accesscontrol.Service = new(FakeService)
@ -121,7 +120,7 @@ type FakePermissionsService struct {
ExpectedMappedAction string
}
func (f *FakePermissionsService) GetPermissions(ctx context.Context, user *user.SignedInUser, resourceID string) ([]accesscontrol.ResourcePermission, error) {
func (f *FakePermissionsService) GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]accesscontrol.ResourcePermission, error) {
return f.ExpectedPermissions, f.ExpectedErr
}

View File

@ -114,7 +114,8 @@ func (api *AccessControlAPI) searchUserPermissions(c *contextmodel.ReqContext) r
return response.JSON(http.StatusBadRequest, "provide one of 'action' or 'actionPrefix'")
}
permissions, err := api.Service.SearchUserPermissions(c.Req.Context(), c.OrgID, searchOptions)
permissions, err := api.Service.SearchUserPermissions(c.Req.Context(),
c.SignedInUser.GetOrgID(), searchOptions)
if err != nil {
response.Error(http.StatusInternalServerError, "could not search user permissions", err)
}

View File

@ -15,6 +15,7 @@ import (
"github.com/grafana/grafana/pkg/middleware/cookies"
"github.com/grafana/grafana/pkg/models/usertoken"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/org"
@ -30,7 +31,7 @@ func Middleware(ac AccessControl) func(Evaluator) web.Handler {
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)
if err == nil && orgID > 0 && orgID != c.OrgID {
if err == nil && orgID > 0 && orgID != c.SignedInUser.GetOrgID() {
forceLogin = true
}
@ -56,9 +57,9 @@ func Middleware(ac AccessControl) func(Evaluator) web.Handler {
}
}
func authorize(c *contextmodel.ReqContext, ac AccessControl, user *user.SignedInUser, evaluator Evaluator) {
func authorize(c *contextmodel.ReqContext, ac AccessControl, user identity.Requester, evaluator Evaluator) {
injected, err := evaluator.MutateScopes(c.Req.Context(), scopeInjector(scopeParams{
OrgID: c.OrgID,
OrgID: user.GetOrgID(),
URLParams: web.Params(c.Req),
}))
if err != nil {
@ -78,9 +79,11 @@ func deny(c *contextmodel.ReqContext, evaluator Evaluator, err error) {
if err != nil {
c.Logger.Error("Error from access control system", "error", err, "accessErrorID", id)
} else {
namespace, identifier := c.SignedInUser.GetNamespacedID()
c.Logger.Info(
"Access denied",
"userID", c.UserID,
"namespace", namespace,
"userID", identifier,
"accessErrorID", id,
"permissions", evaluator.GoString(),
)

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/auth/identity"
)
var _ accesscontrol.PermissionsService = new(MockPermissionsService)
@ -19,7 +19,7 @@ type MockPermissionsService struct {
mock.Mock
}
func (m *MockPermissionsService) GetPermissions(ctx context.Context, user *user.SignedInUser, resourceID string) ([]accesscontrol.ResourcePermission, error) {
func (m *MockPermissionsService) GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]accesscontrol.ResourcePermission, error) {
mockedArgs := m.Called(ctx, user, resourceID)
return mockedArgs.Get(0).([]accesscontrol.ResourcePermission), mockedArgs.Error(1)
}

View File

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
@ -253,7 +254,7 @@ var _ accesscontrol.DatasourcePermissionsService = new(DatasourcePermissionsServ
type DatasourcePermissionsService struct{}
func (e DatasourcePermissionsService) GetPermissions(ctx context.Context, user *user.SignedInUser, resourceID string) ([]accesscontrol.ResourcePermission, error) {
func (e DatasourcePermissionsService) GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]accesscontrol.ResourcePermission, error) {
return nil, nil
}

View File

@ -155,7 +155,7 @@ func (a *api) setUserPermission(c *contextmodel.ReqContext) response.Response {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
_, err = a.service.SetUserPermission(c.Req.Context(), c.OrgID, accesscontrol.User{ID: userID}, resourceID, cmd.Permission)
_, err = a.service.SetUserPermission(c.Req.Context(), c.SignedInUser.GetOrgID(), accesscontrol.User{ID: userID}, resourceID, cmd.Permission)
if err != nil {
return response.Error(http.StatusBadRequest, "failed to set user permission", err)
}
@ -175,7 +175,7 @@ func (a *api) setTeamPermission(c *contextmodel.ReqContext) response.Response {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
_, err = a.service.SetTeamPermission(c.Req.Context(), c.OrgID, teamID, resourceID, cmd.Permission)
_, err = a.service.SetTeamPermission(c.Req.Context(), c.SignedInUser.GetOrgID(), teamID, resourceID, cmd.Permission)
if err != nil {
return response.Error(http.StatusBadRequest, "failed to set team permission", err)
}
@ -192,7 +192,7 @@ func (a *api) setBuiltinRolePermission(c *contextmodel.ReqContext) response.Resp
return response.Error(http.StatusBadRequest, "bad request data", err)
}
_, err := a.service.SetBuiltInRolePermission(c.Req.Context(), c.OrgID, builtInRole, resourceID, cmd.Permission)
_, err := a.service.SetBuiltInRolePermission(c.Req.Context(), c.SignedInUser.GetOrgID(), builtInRole, resourceID, cmd.Permission)
if err != nil {
return response.Error(http.StatusBadRequest, "failed to set role permission", err)
}
@ -208,7 +208,7 @@ func (a *api) setPermissions(c *contextmodel.ReqContext) response.Response {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
_, err := a.service.SetPermissions(c.Req.Context(), c.OrgID, resourceID, cmd.Permissions...)
_, err := a.service.SetPermissions(c.Req.Context(), c.SignedInUser.GetOrgID(), resourceID, cmd.Permissions...)
if err != nil {
return response.Error(http.StatusBadRequest, "failed to set permissions", err)
}

View File

@ -2,7 +2,7 @@ package resourcepermissions
import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/auth/identity"
)
type SetResourcePermissionCommand struct {
@ -29,5 +29,5 @@ type GetResourcePermissionsQuery struct {
OnlyManaged bool
InheritedScopes []string
EnforceAccessControl bool
User *user.SignedInUser
User identity.Requester
}

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/services/org"
@ -115,17 +116,17 @@ type Service struct {
userService user.Service
}
func (s *Service) GetPermissions(ctx context.Context, user *user.SignedInUser, resourceID string) ([]accesscontrol.ResourcePermission, error) {
func (s *Service) GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]accesscontrol.ResourcePermission, error) {
var inheritedScopes []string
if s.options.InheritedScopesSolver != nil {
var err error
inheritedScopes, err = s.options.InheritedScopesSolver(ctx, user.OrgID, resourceID)
inheritedScopes, err = s.options.InheritedScopesSolver(ctx, user.GetOrgID(), resourceID)
if err != nil {
return nil, err
}
}
return s.store.GetResourcePermissions(ctx, user.OrgID, GetResourcePermissionsQuery{
return s.store.GetResourcePermissions(ctx, user.GetOrgID(), GetResourcePermissionsQuery{
User: user,
Actions: s.actions,
Resource: s.options.Resource,

View File

@ -53,6 +53,8 @@ type Requester interface {
// Legacy
// HasRole returns true if the active entity has the given role in the active organization.
HasRole(role roletype.RoleType) bool
// GetCacheKey returns a unique key for the entity.
// Add an extra prefix to avoid collisions with other caches
GetCacheKey() (string, error)