AuthZ: Extend /api/search to work with self-contained permissions (#70749)
* Search sql filter draft, unfinished * Search works for empty roles * Add current AuthModule to SignedInUser * clean up, changes to the search * Use constant prefixes * Change AuthModule to AuthenticatedBy * Add tests for using the permissions from the SignedInUser * Refactor and simplify code * Fix sql generation for pg and mysql * Fixes, clean up * Add test for empty permission list * Fix * Fix any vs all in case of edit permission * Update pkg/services/authn/authn.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> * Update pkg/services/sqlstore/permissions/dashboard_test.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> * Fixes, changes based on the review --------- Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
@@ -172,36 +173,68 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
|
||||
permSelector := strings.Builder{}
|
||||
var permSelectorArgs []interface{}
|
||||
|
||||
// useSelfContainedPermissions is true if the user's permissions are stored and set from the JWT token
|
||||
// currently it's used for the extended JWT module (when the user is authenticated via a JWT token generated by Grafana)
|
||||
useSelfContainedPermissions := f.user.AuthenticatedBy == login.ExtendedJWTModule
|
||||
|
||||
if len(f.dashboardActions) > 0 {
|
||||
toCheck := actionsToCheck(f.dashboardActions, f.user.Permissions[f.user.OrgID], dashWildcards, folderWildcards)
|
||||
|
||||
if len(toCheck) > 0 {
|
||||
builder.WriteString("(dashboard.uid IN (SELECT substr(scope, 16) FROM permission WHERE scope LIKE 'dashboards:uid:%'")
|
||||
builder.WriteString(rolesFilter)
|
||||
args = append(args, params...)
|
||||
if !useSelfContainedPermissions {
|
||||
builder.WriteString("(dashboard.uid IN (SELECT substr(scope, 16) FROM permission WHERE scope LIKE 'dashboards:uid:%'")
|
||||
builder.WriteString(rolesFilter)
|
||||
args = append(args, params...)
|
||||
|
||||
if len(toCheck) == 1 {
|
||||
builder.WriteString(" AND action = ?")
|
||||
args = append(args, toCheck[0])
|
||||
if len(toCheck) == 1 {
|
||||
builder.WriteString(" AND action = ?")
|
||||
args = append(args, toCheck[0])
|
||||
} else {
|
||||
builder.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheck)-1) + ") GROUP BY role_id, scope HAVING COUNT(action) = ?")
|
||||
args = append(args, toCheck...)
|
||||
args = append(args, len(toCheck))
|
||||
}
|
||||
builder.WriteString(") AND NOT dashboard.is_folder)")
|
||||
} else {
|
||||
builder.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheck)-1) + ") GROUP BY role_id, scope HAVING COUNT(action) = ?")
|
||||
args = append(args, toCheck...)
|
||||
args = append(args, len(toCheck))
|
||||
actions := parseStringSliceFromInterfaceSlice(toCheck)
|
||||
|
||||
args = getAllowedUIDs(actions, f.user, dashboards.ScopeDashboardsPrefix)
|
||||
|
||||
// Only add the IN clause if we have any dashboards to check
|
||||
if len(args) > 0 {
|
||||
builder.WriteString("(dashboard.uid IN (?" + strings.Repeat(", ?", len(args)-1) + "")
|
||||
builder.WriteString(") AND NOT dashboard.is_folder)")
|
||||
} else {
|
||||
builder.WriteString("(1 = 0)")
|
||||
}
|
||||
}
|
||||
builder.WriteString(") AND NOT dashboard.is_folder)")
|
||||
|
||||
builder.WriteString(" OR ")
|
||||
permSelector.WriteString("(SELECT substr(scope, 13) FROM permission WHERE scope LIKE 'folders:uid:%' ")
|
||||
permSelector.WriteString(rolesFilter)
|
||||
permSelectorArgs = append(permSelectorArgs, params...)
|
||||
|
||||
if len(toCheck) == 1 {
|
||||
permSelector.WriteString(" AND action = ?")
|
||||
permSelectorArgs = append(permSelectorArgs, toCheck[0])
|
||||
if !useSelfContainedPermissions {
|
||||
permSelector.WriteString("(SELECT substr(scope, 13) FROM permission WHERE scope LIKE 'folders:uid:%' ")
|
||||
permSelector.WriteString(rolesFilter)
|
||||
permSelectorArgs = append(permSelectorArgs, params...)
|
||||
|
||||
if len(toCheck) == 1 {
|
||||
permSelector.WriteString(" AND action = ?")
|
||||
permSelectorArgs = append(permSelectorArgs, toCheck[0])
|
||||
} else {
|
||||
permSelector.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheck)-1) + ") GROUP BY role_id, scope HAVING COUNT(action) = ?")
|
||||
permSelectorArgs = append(permSelectorArgs, toCheck...)
|
||||
permSelectorArgs = append(permSelectorArgs, len(toCheck))
|
||||
}
|
||||
} else {
|
||||
permSelector.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheck)-1) + ") GROUP BY role_id, scope HAVING COUNT(action) = ?")
|
||||
permSelectorArgs = append(permSelectorArgs, toCheck...)
|
||||
permSelectorArgs = append(permSelectorArgs, len(toCheck))
|
||||
actions := parseStringSliceFromInterfaceSlice(toCheck)
|
||||
|
||||
permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix)
|
||||
|
||||
// Only add the IN clause if we have any folders to check
|
||||
if len(permSelectorArgs) > 0 {
|
||||
permSelector.WriteString("(?" + strings.Repeat(", ?", len(permSelectorArgs)-1) + "")
|
||||
} else {
|
||||
permSelector.WriteString("(")
|
||||
}
|
||||
}
|
||||
permSelector.WriteRune(')')
|
||||
|
||||
@@ -221,9 +254,13 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
|
||||
}
|
||||
default:
|
||||
builder.WriteString("(dashboard.folder_id IN (SELECT d.id FROM dashboard as d ")
|
||||
builder.WriteString("WHERE d.uid IN ")
|
||||
builder.WriteString(permSelector.String())
|
||||
args = append(args, permSelectorArgs...)
|
||||
if len(permSelectorArgs) > 0 {
|
||||
builder.WriteString("WHERE d.uid IN ")
|
||||
builder.WriteString(permSelector.String())
|
||||
args = append(args, permSelectorArgs...)
|
||||
} else {
|
||||
builder.WriteString("WHERE 1 = 0")
|
||||
}
|
||||
}
|
||||
builder.WriteString(") AND NOT dashboard.is_folder)")
|
||||
} else {
|
||||
@@ -242,17 +279,30 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
|
||||
|
||||
toCheck := actionsToCheck(f.folderActions, f.user.Permissions[f.user.OrgID], folderWildcards)
|
||||
if len(toCheck) > 0 {
|
||||
permSelector.WriteString("(SELECT substr(scope, 13) FROM permission WHERE scope LIKE 'folders:uid:%'")
|
||||
permSelector.WriteString(rolesFilter)
|
||||
permSelectorArgs = append(permSelectorArgs, params...)
|
||||
if len(toCheck) == 1 {
|
||||
permSelector.WriteString(" AND action = ?")
|
||||
permSelectorArgs = append(permSelectorArgs, toCheck[0])
|
||||
if !useSelfContainedPermissions {
|
||||
permSelector.WriteString("(SELECT substr(scope, 13) FROM permission WHERE scope LIKE 'folders:uid:%'")
|
||||
permSelector.WriteString(rolesFilter)
|
||||
permSelectorArgs = append(permSelectorArgs, params...)
|
||||
if len(toCheck) == 1 {
|
||||
permSelector.WriteString(" AND action = ?")
|
||||
permSelectorArgs = append(permSelectorArgs, toCheck[0])
|
||||
} else {
|
||||
permSelector.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheck)-1) + ") GROUP BY role_id, scope HAVING COUNT(action) = ?")
|
||||
permSelectorArgs = append(permSelectorArgs, toCheck...)
|
||||
permSelectorArgs = append(permSelectorArgs, len(toCheck))
|
||||
}
|
||||
} else {
|
||||
permSelector.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheck)-1) + ") GROUP BY role_id, scope HAVING COUNT(action) = ?")
|
||||
permSelectorArgs = append(permSelectorArgs, toCheck...)
|
||||
permSelectorArgs = append(permSelectorArgs, len(toCheck))
|
||||
actions := parseStringSliceFromInterfaceSlice(toCheck)
|
||||
|
||||
permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix)
|
||||
|
||||
if len(permSelectorArgs) > 0 {
|
||||
permSelector.WriteString("(?" + strings.Repeat(", ?", len(permSelectorArgs)-1) + "")
|
||||
} else {
|
||||
permSelector.WriteString("(")
|
||||
}
|
||||
}
|
||||
|
||||
permSelector.WriteRune(')')
|
||||
|
||||
switch f.features.IsEnabled(featuremgmt.FlagNestedFolders) {
|
||||
@@ -271,15 +321,20 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
|
||||
args = append(args, nestedFoldersArgs...)
|
||||
}
|
||||
default:
|
||||
builder.WriteString("(dashboard.uid IN ")
|
||||
builder.WriteString(permSelector.String())
|
||||
args = append(args, permSelectorArgs...)
|
||||
if len(permSelectorArgs) > 0 {
|
||||
builder.WriteString("(dashboard.uid IN ")
|
||||
builder.WriteString(permSelector.String())
|
||||
args = append(args, permSelectorArgs...)
|
||||
} else {
|
||||
builder.WriteString("(1 = 0")
|
||||
}
|
||||
}
|
||||
builder.WriteString(" AND dashboard.is_folder)")
|
||||
} else {
|
||||
builder.WriteString("dashboard.is_folder")
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteRune(')')
|
||||
|
||||
f.where = clause{string: builder.String(), params: args}
|
||||
@@ -365,3 +420,36 @@ func nestedFoldersSelectors(permSelector string, permSelectorArgs []interface{},
|
||||
|
||||
return strings.Join(wheres, ") OR "), args
|
||||
}
|
||||
|
||||
func parseStringSliceFromInterfaceSlice(slice []interface{}) []string {
|
||||
result := make([]string, 0, len(slice))
|
||||
for _, s := range slice {
|
||||
result = append(result, s.(string))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func getAllowedUIDs(actions []string, user *user.SignedInUser, scopePrefix string) []interface{} {
|
||||
uidToActions := make(map[string]map[string]struct{})
|
||||
for _, action := range actions {
|
||||
for _, uidScope := range user.Permissions[user.OrgID][action] {
|
||||
if !strings.HasPrefix(uidScope, scopePrefix) {
|
||||
continue
|
||||
}
|
||||
uid := strings.TrimPrefix(uidScope, scopePrefix)
|
||||
if _, exists := uidToActions[uid]; !exists {
|
||||
uidToActions[uid] = make(map[string]struct{})
|
||||
}
|
||||
uidToActions[uid][action] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// args max capacity is the length of the different uids
|
||||
args := make([]interface{}, 0, len(uidToActions))
|
||||
for uid, assignedActions := range uidToActions {
|
||||
if len(assignedActions) == len(actions) {
|
||||
args = append(args, uid)
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user