Alerting: rule group update API to ignore deletes of rules user is not authorized to access (#46905)

* verify that the user has access to all data sources used by the rule that needs to be deleted from the group
* if a user is not authorized to access the rule, the rule is removed from the list to delete
This commit is contained in:
Yuriy Tseretyan
2022-03-24 16:53:00 -04:00
committed by GitHub
parent 84001fe6be
commit 8868848e93
3 changed files with 219 additions and 55 deletions

View File

@@ -185,49 +185,67 @@ func authorizeDatasourceAccessForRule(rule *ngmodels.AlertRule, evaluator func(e
return true
}
// authorizeRuleChanges analyzes changes in the rule group, determines what actions the user is trying to perform and check whether those actions are authorized.
// If the user is not authorized to perform the changes the function returns ErrAuthorization with a description of what action is not authorized. If the evaluator function returns an error, the function returns it.
func authorizeRuleChanges(namespace *models.Folder, changes *changes, evaluator func(evaluator ac.Evaluator) bool) error {
// authorizeRuleChanges analyzes changes in the rule group, and checks whether the changes are authorized.
// NOTE: if there are rules for deletion, and the user does not have access to data sources that a rule uses, the rule is removed from the list.
// If the user is not authorized to perform the changes the function returns ErrAuthorization with a description of what action is not authorized.
// Return changes that the user is authorized to perform or ErrAuthorization
func authorizeRuleChanges(namespace *models.Folder, change *changes, evaluator func(evaluator ac.Evaluator) bool) (*changes, error) {
var result = &changes{
New: change.New,
Update: change.Update,
Delete: change.Delete,
}
namespaceScope := dashboards.ScopeFoldersProvider.GetResourceScope(strconv.FormatInt(namespace.Id, 10))
if len(changes.Delete) > 0 {
allowed := evaluator(ac.EvalPermission(ac.ActionAlertingRuleDelete, namespaceScope))
if !allowed {
return fmt.Errorf("%w user cannot delete alert rules that belong to folder %s", ErrAuthorization, namespace.Title)
if len(change.Delete) > 0 {
var allowedToDelete []*ngmodels.AlertRule
for _, rule := range change.Delete {
dsAllowed := authorizeDatasourceAccessForRule(rule, evaluator)
if dsAllowed {
allowedToDelete = append(allowedToDelete, rule)
}
}
if len(allowedToDelete) > 0 {
allowed := evaluator(ac.EvalPermission(ac.ActionAlertingRuleDelete, namespaceScope))
if !allowed {
return nil, fmt.Errorf("%w to delete alert rules that belong to folder %s", ErrAuthorization, namespace.Title)
}
}
result.Delete = allowedToDelete
}
var addAuthorized, updateAuthorized bool
if len(changes.New) > 0 {
if len(change.New) > 0 {
addAuthorized = evaluator(ac.EvalPermission(ac.ActionAlertingRuleCreate, namespaceScope))
if !addAuthorized {
return fmt.Errorf("%w user cannot create alert rules in the folder %s", ErrAuthorization, namespace.Title)
return nil, fmt.Errorf("%w to create alert rules in the folder %s", ErrAuthorization, namespace.Title)
}
for _, rule := range changes.New {
for _, rule := range change.New {
dsAllowed := authorizeDatasourceAccessForRule(rule, evaluator)
if !dsAllowed {
return fmt.Errorf("%w to create a new alert rule '%s' because the user does not have read permissions for one or many datasources the rule uses", ErrAuthorization, rule.Title)
return nil, fmt.Errorf("%w to create a new alert rule '%s' because the user does not have read permissions for one or many datasources the rule uses", ErrAuthorization, rule.Title)
}
}
}
for _, rule := range changes.Update {
for _, rule := range change.Update {
dsAllowed := authorizeDatasourceAccessForRule(rule.New, evaluator)
if !dsAllowed {
return fmt.Errorf("%w to update alert rule '%s' (UID: %s) because the user does not have read permissions for one or many datasources the rule uses", ErrAuthorization, rule.Existing.Title, rule.Existing.UID)
return nil, fmt.Errorf("%w to update alert rule '%s' (UID: %s) because the user does not have read permissions for one or many datasources the rule uses", ErrAuthorization, rule.Existing.Title, rule.Existing.UID)
}
// Check if the rule is moved from one folder to the current. If yes, then the user must have the authorization to delete rules from the source folder and add rules to the target folder.
if rule.Existing.NamespaceUID != rule.New.NamespaceUID {
allowed := evaluator(ac.EvalAll(ac.EvalPermission(ac.ActionAlertingRuleDelete, dashboards.ScopeFoldersProvider.GetResourceScopeUID(rule.Existing.NamespaceUID))))
if !allowed {
return fmt.Errorf("%w to delete alert rules from folder UID %s", ErrAuthorization, rule.Existing.NamespaceUID)
return nil, fmt.Errorf("%w to delete alert rules from folder UID %s", ErrAuthorization, rule.Existing.NamespaceUID)
}
if !addAuthorized {
addAuthorized = evaluator(ac.EvalPermission(ac.ActionAlertingRuleCreate, namespaceScope))
if !addAuthorized {
return fmt.Errorf("%w to create alert rules in the folder '%s'", ErrAuthorization, namespace.Title)
return nil, fmt.Errorf("%w to create alert rules in the folder '%s'", ErrAuthorization, namespace.Title)
}
}
continue
@@ -236,9 +254,9 @@ func authorizeRuleChanges(namespace *models.Folder, changes *changes, evaluator
if !updateAuthorized { // if it is false then the authorization was not checked. If it is true then the user is authorized to update rules
updateAuthorized = evaluator(ac.EvalAll(ac.EvalPermission(ac.ActionAlertingRuleUpdate, namespaceScope)))
if !updateAuthorized {
return fmt.Errorf("%w to update alert rules that belong to folder %s", ErrAuthorization, namespace.Title)
return nil, fmt.Errorf("%w to update alert rules that belong to folder %s", ErrAuthorization, namespace.Title)
}
}
}
return nil
return result, nil
}