mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
RBAC: Remove legacy AC from HasAccess permission check (#68995)
* remove unused HasAdmin and HasEdit permission methods * remove legacy AC from HasAccess method * remove unused function * update alerting tests to work with RBAC
This commit is contained in:
parent
82f353c696
commit
d98813796c
@ -179,7 +179,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
||||
LicenseInfo: dtos.FrontendSettingsLicenseInfoDTO{
|
||||
Expiry: hs.License.Expiry(),
|
||||
StateInfo: hs.License.StateInfo(),
|
||||
LicenseUrl: hs.License.LicenseURL(hasAccess(accesscontrol.ReqGrafanaAdmin, licensing.PageAccess)),
|
||||
LicenseUrl: hs.License.LicenseURL(hasAccess(licensing.PageAccess)),
|
||||
Edition: hs.License.Edition(),
|
||||
EnabledFeatures: hs.License.EnabledFeatures(),
|
||||
},
|
||||
|
@ -22,15 +22,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
// TODO this will be removed when we remove legacy AC fallback from HasAccess method
|
||||
func (hs *HTTPServer) editorInAnyFolder(c *contextmodel.ReqContext) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexViewData, error) {
|
||||
hasAccess := ac.HasAccess(hs.AccessControl, c)
|
||||
hasEditPerm := hasAccess(hs.editorInAnyFolder, ac.EvalAny(ac.EvalPermission(dashboards.ActionDashboardsCreate), ac.EvalPermission(dashboards.ActionFoldersCreate)))
|
||||
|
||||
settings, err := hs.getFrontendSettings(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -76,7 +68,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
|
||||
settings.AppSubUrl = ""
|
||||
}
|
||||
|
||||
navTree, err := hs.navTreeService.GetNavTree(c, hasEditPerm, prefs)
|
||||
navTree, err := hs.navTreeService.GetNavTree(c, prefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -88,6 +80,9 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
|
||||
|
||||
theme := hs.getThemeForIndexData(prefs.Theme, c.Query("theme"))
|
||||
|
||||
hasAccess := ac.HasAccess(hs.AccessControl, c)
|
||||
hasEditPerm := hasAccess(ac.EvalAny(ac.EvalPermission(dashboards.ActionDashboardsCreate), ac.EvalPermission(dashboards.ActionFoldersCreate)))
|
||||
|
||||
data := dtos.IndexViewData{
|
||||
User: &dtos.CurrentUser{
|
||||
Id: c.UserID,
|
||||
|
@ -59,7 +59,7 @@ func (hs *HTTPServer) GetPluginList(c *contextmodel.ReqContext) response.Respons
|
||||
// Fallback to only letting admins list non-core plugins
|
||||
reqOrgAdmin := ac.ReqHasRole(org.RoleAdmin)
|
||||
hasAccess := ac.HasAccess(hs.AccessControl, c)
|
||||
canListNonCorePlugins := reqOrgAdmin(c) || hasAccess(reqOrgAdmin, ac.EvalAny(
|
||||
canListNonCorePlugins := reqOrgAdmin(c) || hasAccess(ac.EvalAny(
|
||||
ac.EvalPermission(datasources.ActionCreate),
|
||||
ac.EvalPermission(pluginaccesscontrol.ActionInstall),
|
||||
))
|
||||
@ -90,8 +90,7 @@ func (hs *HTTPServer) GetPluginList(c *contextmodel.ReqContext) response.Respons
|
||||
// * anyone that can install a plugin
|
||||
// Should be able to list this installed plugin:
|
||||
// * anyone that can edit its settings
|
||||
if !pluginDef.IsCorePlugin() && !canListNonCorePlugins && !hasAccess(reqOrgAdmin,
|
||||
ac.EvalPermission(pluginaccesscontrol.ActionWrite, pluginaccesscontrol.ScopeProvider.GetResourceScope(pluginDef.ID))) {
|
||||
if !pluginDef.IsCorePlugin() && !canListNonCorePlugins && !hasAccess(ac.EvalPermission(pluginaccesscontrol.ActionWrite, pluginaccesscontrol.ScopeProvider.GetResourceScope(pluginDef.ID))) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -177,8 +176,7 @@ func (hs *HTTPServer) GetPluginSettingByID(c *contextmodel.ReqContext) response.
|
||||
// We will need a different permission to allow users to configure the plugin without needing access to it.
|
||||
if plugin.IsApp() {
|
||||
hasAccess := ac.HasAccess(hs.AccessControl, c)
|
||||
if !hasAccess(ac.ReqSignedIn,
|
||||
ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginaccesscontrol.ScopeProvider.GetResourceScope(plugin.ID))) {
|
||||
if !hasAccess(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginaccesscontrol.ScopeProvider.GetResourceScope(plugin.ID))) {
|
||||
return response.Error(http.StatusForbidden, "Access Denied", nil)
|
||||
}
|
||||
}
|
||||
|
@ -135,12 +135,8 @@ func HasGlobalAccess(ac AccessControl, service Service, c *contextmodel.ReqConte
|
||||
}
|
||||
}
|
||||
|
||||
func HasAccess(ac AccessControl, c *contextmodel.ReqContext) func(fallback func(*contextmodel.ReqContext) bool, evaluator Evaluator) bool {
|
||||
return func(fallback func(*contextmodel.ReqContext) bool, evaluator Evaluator) bool {
|
||||
if ac.IsDisabled() {
|
||||
return fallback(c)
|
||||
}
|
||||
|
||||
func HasAccess(ac AccessControl, c *contextmodel.ReqContext) func(evaluator Evaluator) bool {
|
||||
return func(evaluator Evaluator) bool {
|
||||
hasAccess, err := ac.Evaluate(c.Req.Context(), c.SignedInUser, evaluator)
|
||||
if err != nil {
|
||||
c.Logger.Error("Error from access control system", "error", err)
|
||||
@ -159,21 +155,8 @@ var ReqGrafanaAdmin = func(c *contextmodel.ReqContext) bool {
|
||||
return c.IsGrafanaAdmin
|
||||
}
|
||||
|
||||
// ReqViewer returns true if the current user has org.RoleViewer. Note: this can be anonymous user as well
|
||||
var ReqViewer = func(c *contextmodel.ReqContext) bool {
|
||||
return c.OrgRole.Includes(org.RoleViewer)
|
||||
}
|
||||
|
||||
var ReqOrgAdmin = func(c *contextmodel.ReqContext) bool {
|
||||
return c.OrgRole == org.RoleAdmin
|
||||
}
|
||||
|
||||
var ReqOrgAdminOrEditor = func(c *contextmodel.ReqContext) bool {
|
||||
return c.OrgRole == org.RoleAdmin || c.OrgRole == org.RoleEditor
|
||||
}
|
||||
|
||||
// ReqHasRole generates a fallback to check whether the user has a role
|
||||
// Note that while ReqOrgAdmin returns false for a Grafana Admin / Viewer, ReqHasRole(org.RoleAdmin) will return true
|
||||
// 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) }
|
||||
}
|
||||
|
@ -6,5 +6,5 @@ import (
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
GetNavTree(c *contextmodel.ReqContext, hasEditPerm bool, prefs *pref.Preference) (*NavTreeRoot, error)
|
||||
GetNavTree(c *contextmodel.ReqContext, prefs *pref.Preference) (*NavTreeRoot, error)
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/navtree"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
)
|
||||
@ -19,7 +18,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
orgsAccessEvaluator := ac.EvalPermission(ac.ActionOrgsRead)
|
||||
authConfigUIAvailable := s.license.FeatureEnabled("saml") && s.features.IsEnabled(featuremgmt.FlagAuthenticationConfigUI)
|
||||
|
||||
if hasAccess(ac.ReqOrgAdmin, datasources.ConfigurationPageAccess) {
|
||||
if hasAccess(datasources.ConfigurationPageAccess) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Data sources",
|
||||
Icon: "database",
|
||||
@ -30,7 +29,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
}
|
||||
|
||||
// FIXME: while we don't have a permissions for listing plugins the legacy check has to stay as a default
|
||||
if pluginaccesscontrol.ReqCanAdminPlugins(s.cfg)(c) || hasAccess(pluginaccesscontrol.ReqCanAdminPlugins(s.cfg), pluginaccesscontrol.AdminAccessEvaluator) {
|
||||
if pluginaccesscontrol.ReqCanAdminPlugins(s.cfg)(c) || hasAccess(pluginaccesscontrol.AdminAccessEvaluator) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Plugins",
|
||||
Id: "plugins",
|
||||
@ -40,13 +39,13 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.ReqSignedIn, ac.EvalAny(ac.EvalPermission(ac.ActionOrgUsersRead), ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll))) {
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionOrgUsersRead), ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll))) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Users", SubTitle: "Manage users in Grafana", Id: "global-users", Url: s.cfg.AppSubURL + "/admin/users", Icon: "user",
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(s.ReqCanAdminTeams, ac.TeamsAccessEvaluator) {
|
||||
if hasAccess(ac.TeamsAccessEvaluator) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Teams",
|
||||
Id: "teams",
|
||||
@ -70,7 +69,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hasAccess(ac.ReqOrgAdmin, ac.ApiKeyAccessEvaluator) && !disabled {
|
||||
if hasAccess(ac.ApiKeyAccessEvaluator) && !disabled {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "API keys",
|
||||
Id: "apikeys",
|
||||
@ -80,7 +79,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.ReqOrgAdmin, ac.OrgPreferencesAccessEvaluator) {
|
||||
if hasAccess(ac.OrgPreferencesAccessEvaluator) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Default preferences",
|
||||
Id: "org-settings",
|
||||
@ -90,7 +89,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
})
|
||||
}
|
||||
|
||||
if authConfigUIAvailable && hasAccess(ac.ReqGrafanaAdmin, evalAuthenticationSettings()) {
|
||||
if authConfigUIAvailable && hasAccess(evalAuthenticationSettings()) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Authentication",
|
||||
Id: "authentication",
|
||||
@ -100,7 +99,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsAll)) {
|
||||
if hasAccess(ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsAll)) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Settings", SubTitle: "View the settings defined in your Grafana config", Id: "server-settings", Url: s.cfg.AppSubURL + "/admin/settings", Icon: "sliders-v-alt",
|
||||
})
|
||||
@ -112,7 +111,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
})
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(featuremgmt.FlagCorrelations) && hasAccess(ac.ReqOrgAdmin, correlations.ConfigurationPageAccess) {
|
||||
if s.features.IsEnabled(featuremgmt.FlagCorrelations) && hasAccess(correlations.ConfigurationPageAccess) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Correlations",
|
||||
Icon: "gf-glue",
|
||||
@ -122,13 +121,13 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
})
|
||||
}
|
||||
|
||||
if s.cfg.LDAPAuthEnabled && hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)) {
|
||||
if s.cfg.LDAPAuthEnabled && hasAccess(ac.EvalPermission(ac.ActionLDAPStatusRead)) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "LDAP", Id: "ldap", Url: s.cfg.AppSubURL + "/admin/ldap", Icon: "book",
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsAll)) && s.features.IsEnabled(featuremgmt.FlagStorage) {
|
||||
if hasAccess(ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsAll)) && s.features.IsEnabled(featuremgmt.FlagStorage) {
|
||||
storage := &navtree.NavLink{
|
||||
Text: "Storage",
|
||||
Id: "storage",
|
||||
@ -152,13 +151,9 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
return configNode, nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) ReqCanAdminTeams(c *contextmodel.ReqContext) bool {
|
||||
return c.OrgRole == org.RoleAdmin || (s.cfg.EditorsCanAdmin && c.OrgRole == org.RoleEditor)
|
||||
}
|
||||
|
||||
func enableServiceAccount(s *ServiceImpl, c *contextmodel.ReqContext) bool {
|
||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||
return hasAccess(ac.ReqOrgAdmin, serviceaccounts.AccessEvaluator)
|
||||
return hasAccess(serviceaccounts.AccessEvaluator)
|
||||
}
|
||||
|
||||
func evalAuthenticationSettings() ac.Evaluator {
|
||||
|
@ -41,8 +41,7 @@ func (s *ServiceImpl) addAppLinks(treeRoot *navtree.NavTreeRoot, c *contextmodel
|
||||
continue
|
||||
}
|
||||
|
||||
if !hasAccess(ac.ReqSignedIn,
|
||||
ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginaccesscontrol.ScopeProvider.GetResourceScope(plugin.ID))) {
|
||||
if !hasAccess(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginaccesscontrol.ScopeProvider.GetResourceScope(plugin.ID))) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -240,7 +239,7 @@ func (s *ServiceImpl) hasAccessToInclude(c *contextmodel.ReqContext, pluginID st
|
||||
return func(include *plugins.Includes) bool {
|
||||
useRBAC := s.features.IsEnabled(featuremgmt.FlagAccessControlOnCall) &&
|
||||
!s.accessControl.IsDisabled() && include.RequiresRBACAction()
|
||||
if useRBAC && !hasAccess(ac.ReqHasRole(include.Role), ac.EvalPermission(include.Action)) {
|
||||
if useRBAC && !hasAccess(ac.EvalPermission(include.Action)) {
|
||||
s.log.Debug("plugin include is covered by RBAC, user doesn't have access",
|
||||
"plugin", pluginID,
|
||||
"include", include.Name)
|
||||
|
@ -72,13 +72,13 @@ func ProvideService(cfg *setting.Cfg, accessControl ac.AccessControl, pluginStor
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, hasEditPerm bool, prefs *pref.Preference) (*navtree.NavTreeRoot, error) {
|
||||
func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Preference) (*navtree.NavTreeRoot, error) {
|
||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||
treeRoot := &navtree.NavTreeRoot{}
|
||||
|
||||
treeRoot.AddSection(s.getHomeNode(c, prefs))
|
||||
|
||||
if hasAccess(ac.ReqSignedIn, ac.EvalPermission(dashboards.ActionDashboardsRead)) {
|
||||
if hasAccess(ac.EvalPermission(dashboards.ActionDashboardsRead)) {
|
||||
starredItemsLinks, err := s.buildStarredItemsNavLinks(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -95,11 +95,11 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, hasEditPerm bool, p
|
||||
})
|
||||
}
|
||||
|
||||
if c.IsPublicDashboardView || hasAccess(ac.ReqSignedIn, ac.EvalAny(
|
||||
if c.IsPublicDashboardView || hasAccess(ac.EvalAny(
|
||||
ac.EvalPermission(dashboards.ActionFoldersRead), ac.EvalPermission(dashboards.ActionFoldersCreate),
|
||||
ac.EvalPermission(dashboards.ActionDashboardsRead), ac.EvalPermission(dashboards.ActionDashboardsCreate)),
|
||||
) {
|
||||
dashboardChildLinks := s.buildDashboardNavLinks(c, hasEditPerm)
|
||||
dashboardChildLinks := s.buildDashboardNavLinks(c)
|
||||
|
||||
dashboardLink := &navtree.NavLink{
|
||||
Text: "Dashboards",
|
||||
@ -114,11 +114,7 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, hasEditPerm bool, p
|
||||
treeRoot.AddSection(dashboardLink)
|
||||
}
|
||||
|
||||
canExplore := func(context *contextmodel.ReqContext) bool {
|
||||
return c.OrgRole == org.RoleAdmin || c.OrgRole == org.RoleEditor || s.cfg.ViewersCanEdit
|
||||
}
|
||||
|
||||
if setting.ExploreEnabled && hasAccess(canExplore, ac.EvalPermission(ac.ActionDatasourcesExplore)) {
|
||||
if setting.ExploreEnabled && hasAccess(ac.EvalPermission(ac.ActionDatasourcesExplore)) {
|
||||
treeRoot.AddSection(&navtree.NavLink{
|
||||
Text: "Explore",
|
||||
Id: "explore",
|
||||
@ -141,7 +137,7 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, hasEditPerm bool, p
|
||||
treeRoot.AddSection(legacyAlertSection)
|
||||
}
|
||||
} else if uaVisibleForOrg {
|
||||
if alertingSection := s.buildAlertNavLinks(c, hasEditPerm); alertingSection != nil {
|
||||
if alertingSection := s.buildAlertNavLinks(c); alertingSection != nil {
|
||||
treeRoot.AddSection(alertingSection)
|
||||
}
|
||||
}
|
||||
@ -220,7 +216,7 @@ func (s *ServiceImpl) addHelpLinks(treeRoot *navtree.NavTreeRoot, c *contextmode
|
||||
ac.EvalPermission(supportbundlesimpl.ActionCreate),
|
||||
)
|
||||
|
||||
if isSupportBundlesEnabled(s) && hasAccess(ac.ReqGrafanaAdmin, supportBundleAccess) {
|
||||
if isSupportBundlesEnabled(s) && hasAccess(supportBundleAccess) {
|
||||
supportBundleNode := &navtree.NavLink{
|
||||
Text: "Support bundles",
|
||||
Id: "support-bundles",
|
||||
@ -324,11 +320,8 @@ func (s *ServiceImpl) buildStarredItemsNavLinks(c *contextmodel.ReqContext) ([]*
|
||||
return starredItemsChildNavs, nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext, hasEditPerm bool) []*navtree.NavLink {
|
||||
func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navtree.NavLink {
|
||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||
hasEditPermInAnyFolder := func(c *contextmodel.ReqContext) bool {
|
||||
return hasEditPerm
|
||||
}
|
||||
|
||||
dashboardChildNavs := []*navtree.NavLink{}
|
||||
|
||||
@ -374,17 +367,15 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext, hasEdit
|
||||
})
|
||||
}
|
||||
|
||||
if hasEditPerm {
|
||||
if hasAccess(hasEditPermInAnyFolder, ac.EvalPermission(dashboards.ActionDashboardsCreate)) {
|
||||
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
|
||||
Text: "New dashboard", Icon: "plus", Url: s.cfg.AppSubURL + "/dashboard/new", HideFromTabs: true, Id: "dashboards/new", IsCreateAction: true,
|
||||
})
|
||||
if hasAccess(ac.EvalPermission(dashboards.ActionDashboardsCreate)) {
|
||||
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
|
||||
Text: "New dashboard", Icon: "plus", Url: s.cfg.AppSubURL + "/dashboard/new", HideFromTabs: true, Id: "dashboards/new", IsCreateAction: true,
|
||||
})
|
||||
|
||||
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
|
||||
Text: "Import dashboard", SubTitle: "Import dashboard from file or Grafana.com", Id: "dashboards/import", Icon: "plus",
|
||||
Url: s.cfg.AppSubURL + "/dashboard/import", HideFromTabs: true, IsCreateAction: true,
|
||||
})
|
||||
}
|
||||
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
|
||||
Text: "Import dashboard", SubTitle: "Import dashboard from file or Grafana.com", Id: "dashboards/import", Icon: "plus",
|
||||
Url: s.cfg.AppSubURL + "/dashboard/import", HideFromTabs: true, IsCreateAction: true,
|
||||
})
|
||||
}
|
||||
|
||||
return dashboardChildNavs
|
||||
@ -416,17 +407,17 @@ func (s *ServiceImpl) buildLegacyAlertNavLinks(c *contextmodel.ReqContext) *navt
|
||||
return &alertNav
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext, hasEditPerm bool) *navtree.NavLink {
|
||||
func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.NavLink {
|
||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||
var alertChildNavs []*navtree.NavLink
|
||||
|
||||
if hasAccess(ac.ReqViewer, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||
Text: "Alert rules", SubTitle: "Rules that determine whether an alert will fire", Id: "alert-list", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul",
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.ReqOrgAdminOrEditor, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingNotificationsRead), ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead))) {
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingNotificationsRead), ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead))) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||
Text: "Contact points", SubTitle: "Choose how to notify your contact points when an alert instance fires", Id: "receivers", Url: s.cfg.AppSubURL + "/alerting/notifications",
|
||||
Icon: "comment-alt-share",
|
||||
@ -434,7 +425,7 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext, hasEditPerm
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Notification policies", SubTitle: "Determine how alerts are routed to contact points", Id: "am-routes", Url: s.cfg.AppSubURL + "/alerting/routes", Icon: "sitemap"})
|
||||
}
|
||||
|
||||
if hasAccess(ac.ReqViewer, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingInstanceRead), ac.EvalPermission(ac.ActionAlertingInstancesExternalRead))) {
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingInstanceRead), ac.EvalPermission(ac.ActionAlertingInstancesExternalRead))) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Silences", SubTitle: "Stop notifications from one or more alerting rules", Id: "silences", Url: s.cfg.AppSubURL + "/alerting/silences", Icon: "bell-slash"})
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Alert groups", SubTitle: "See grouped alerts from an Alertmanager instance", Id: "groups", Url: s.cfg.AppSubURL + "/alerting/groups", Icon: "layer-group"})
|
||||
}
|
||||
@ -446,9 +437,7 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext, hasEditPerm
|
||||
})
|
||||
}
|
||||
|
||||
fallbackHasEditPerm := func(*contextmodel.ReqContext) bool { return hasEditPerm }
|
||||
|
||||
if hasAccess(fallbackHasEditPerm, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleCreate), ac.EvalPermission(ac.ActionAlertingRuleExternalWrite))) {
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleCreate), ac.EvalPermission(ac.ActionAlertingRuleExternalWrite))) {
|
||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||
Text: "Create alert rule", SubTitle: "Create an alert rule", Id: "alert",
|
||||
Icon: "plus", Url: s.cfg.AppSubURL + "/alerting/new", HideFromTabs: true, IsCreateAction: true,
|
||||
@ -480,7 +469,7 @@ func (s *ServiceImpl) buildDataConnectionsNavLink(c *contextmodel.ReqContext) *n
|
||||
|
||||
baseUrl := s.cfg.AppSubURL + "/connections"
|
||||
|
||||
if hasAccess(ac.ReqOrgAdmin, datasources.ConfigurationPageAccess) {
|
||||
if hasAccess(datasources.ConfigurationPageAccess) {
|
||||
// Add new connection
|
||||
children = append(children, &navtree.NavLink{
|
||||
Id: "connections-add-new-connection",
|
||||
|
@ -68,7 +68,7 @@ func (srv AlertmanagerSrv) RouteCreateSilence(c *contextmodel.ReqContext, postab
|
||||
if postableSilence.ID == "" {
|
||||
action = accesscontrol.ActionAlertingInstanceCreate
|
||||
}
|
||||
if !accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqOrgAdminOrEditor, accesscontrol.EvalPermission(action)) {
|
||||
if !accesscontrol.HasAccess(srv.ac, c)(accesscontrol.EvalPermission(action)) {
|
||||
errAction := "update"
|
||||
if postableSilence.ID == "" {
|
||||
errAction = "create"
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
@ -163,7 +163,7 @@ func TestStatusForTestReceivers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAlertmanagerConfig(t *testing.T) {
|
||||
sut := createSut(t, nil)
|
||||
sut := createSut(t)
|
||||
|
||||
t.Run("assert 404 Not Found when applying config to nonexistent org", func(t *testing.T) {
|
||||
rc := contextmodel.ReqContext{
|
||||
@ -199,7 +199,7 @@ func TestAlertmanagerConfig(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("assert 202 when alertmanager to configure is not ready", func(t *testing.T) {
|
||||
sut := createSut(t, nil)
|
||||
sut := createSut(t)
|
||||
rc := contextmodel.ReqContext{
|
||||
Context: &web.Context{
|
||||
Req: &http.Request{},
|
||||
@ -217,7 +217,7 @@ func TestAlertmanagerConfig(t *testing.T) {
|
||||
|
||||
t.Run("when objects are not provisioned", func(t *testing.T) {
|
||||
t.Run("route from GET config has no provenance", func(t *testing.T) {
|
||||
sut := createSut(t, nil)
|
||||
sut := createSut(t)
|
||||
rc := createRequestCtxInOrg(1)
|
||||
|
||||
response := sut.RouteGetAlertingConfig(rc)
|
||||
@ -226,7 +226,7 @@ func TestAlertmanagerConfig(t *testing.T) {
|
||||
require.Equal(t, apimodels.Provenance(ngmodels.ProvenanceNone), body.AlertmanagerConfig.Route.Provenance)
|
||||
})
|
||||
t.Run("contact point from GET config has no provenance", func(t *testing.T) {
|
||||
sut := createSut(t, nil)
|
||||
sut := createSut(t)
|
||||
rc := createRequestCtxInOrg(1)
|
||||
|
||||
response := sut.RouteGetAlertingConfig(rc)
|
||||
@ -235,7 +235,7 @@ func TestAlertmanagerConfig(t *testing.T) {
|
||||
require.Equal(t, apimodels.Provenance(ngmodels.ProvenanceNone), body.AlertmanagerConfig.Receivers[0].GrafanaManagedReceivers[0].Provenance)
|
||||
})
|
||||
t.Run("templates from GET config have no provenance", func(t *testing.T) {
|
||||
sut := createSut(t, nil)
|
||||
sut := createSut(t)
|
||||
rc := createRequestCtxInOrg(1)
|
||||
|
||||
response := sut.RouteGetAlertingConfig(rc)
|
||||
@ -247,7 +247,7 @@ func TestAlertmanagerConfig(t *testing.T) {
|
||||
|
||||
t.Run("when objects are provisioned", func(t *testing.T) {
|
||||
t.Run("route from GET config has expected provenance", func(t *testing.T) {
|
||||
sut := createSut(t, nil)
|
||||
sut := createSut(t)
|
||||
rc := createRequestCtxInOrg(1)
|
||||
setRouteProvenance(t, 1, sut.mam.ProvStore)
|
||||
|
||||
@ -257,7 +257,7 @@ func TestAlertmanagerConfig(t *testing.T) {
|
||||
require.Equal(t, apimodels.Provenance(ngmodels.ProvenanceAPI), body.AlertmanagerConfig.Route.Provenance)
|
||||
})
|
||||
t.Run("contact point from GET config has expected provenance", func(t *testing.T) {
|
||||
sut := createSut(t, nil)
|
||||
sut := createSut(t)
|
||||
rc := createRequestCtxInOrg(1)
|
||||
request := createAmConfigRequest(t)
|
||||
|
||||
@ -277,7 +277,7 @@ func TestAlertmanagerConfig(t *testing.T) {
|
||||
require.Equal(t, apimodels.Provenance(ngmodels.ProvenanceAPI), body.AlertmanagerConfig.Receivers[0].GrafanaManagedReceivers[0].Provenance)
|
||||
})
|
||||
t.Run("templates from GET config have expected provenance", func(t *testing.T) {
|
||||
sut := createSut(t, nil)
|
||||
sut := createSut(t)
|
||||
rc := createRequestCtxInOrg(1)
|
||||
setTemplateProvenance(t, 1, "a", sut.mam.ProvStore)
|
||||
|
||||
@ -292,7 +292,7 @@ func TestAlertmanagerConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRouteGetAlertingConfigHistory(t *testing.T) {
|
||||
sut := createSut(t, nil)
|
||||
sut := createSut(t)
|
||||
|
||||
t.Run("assert 200 and empty slice when no applied configurations are found", func(tt *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "https://grafana.net", nil)
|
||||
@ -382,7 +382,7 @@ func TestRouteGetAlertingConfigHistory(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRoutePostGrafanaAlertingConfigHistoryActivate(t *testing.T) {
|
||||
sut := createSut(t, nil)
|
||||
sut := createSut(t)
|
||||
|
||||
t.Run("assert 404 when no historical configurations are found", func(tt *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "https://grafana.net", nil)
|
||||
@ -422,7 +422,7 @@ func TestRoutePostGrafanaAlertingConfigHistoryActivate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRoutePostTestTemplates(t *testing.T) {
|
||||
sut := createSut(t, nil)
|
||||
sut := createSut(t)
|
||||
|
||||
t.Run("assert 404 when no alertmanager found", func(tt *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "https://grafana.net", nil)
|
||||
@ -507,10 +507,13 @@ func TestSilenceCreate(t *testing.T) {
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgRole: org.RoleEditor,
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
srv := createSut(t, nil)
|
||||
srv := createSut(t)
|
||||
|
||||
resp := srv.RouteCreateSilence(&rc, amv2.PostableSilence{
|
||||
ID: "",
|
||||
@ -525,114 +528,54 @@ func TestRouteCreateSilence(t *testing.T) {
|
||||
tesCases := []struct {
|
||||
name string
|
||||
silence func() apimodels.PostableSilence
|
||||
accessControl func() accesscontrol.AccessControl
|
||||
role org.RoleType
|
||||
permissions map[int64]map[string][]string
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "new silence, role-based access control is enabled, not authorized",
|
||||
silence: silenceGen(withEmptyID),
|
||||
accessControl: func() accesscontrol.AccessControl {
|
||||
return acMock.New()
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {},
|
||||
},
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "new silence, role-based access control is enabled, authorized",
|
||||
silence: silenceGen(withEmptyID),
|
||||
accessControl: func() accesscontrol.AccessControl {
|
||||
return acMock.New().WithPermissions([]accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionAlertingInstanceCreate},
|
||||
})
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
|
||||
},
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
{
|
||||
name: "new silence, role-based access control is disabled, Viewer",
|
||||
silence: silenceGen(withEmptyID),
|
||||
accessControl: func() accesscontrol.AccessControl {
|
||||
return acMock.New().WithDisabled()
|
||||
},
|
||||
role: org.RoleViewer,
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "new silence, role-based access control is disabled, Editor",
|
||||
silence: silenceGen(withEmptyID),
|
||||
accessControl: func() accesscontrol.AccessControl {
|
||||
return acMock.New().WithDisabled()
|
||||
},
|
||||
role: org.RoleEditor,
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
{
|
||||
name: "new silence, role-based access control is disabled, Admin",
|
||||
silence: silenceGen(withEmptyID),
|
||||
accessControl: func() accesscontrol.AccessControl {
|
||||
return acMock.New().WithDisabled()
|
||||
},
|
||||
role: org.RoleAdmin,
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
{
|
||||
name: "update silence, role-based access control is enabled, not authorized",
|
||||
silence: silenceGen(),
|
||||
accessControl: func() accesscontrol.AccessControl {
|
||||
return acMock.New()
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
|
||||
},
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "update silence, role-based access control is enabled, authorized",
|
||||
silence: silenceGen(),
|
||||
accessControl: func() accesscontrol.AccessControl {
|
||||
return acMock.New().WithPermissions([]accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionAlertingInstanceUpdate},
|
||||
})
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceUpdate: {}},
|
||||
},
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
{
|
||||
name: "update silence, role-based access control is disabled, Viewer",
|
||||
silence: silenceGen(),
|
||||
accessControl: func() accesscontrol.AccessControl {
|
||||
return acMock.New().WithDisabled()
|
||||
},
|
||||
role: org.RoleViewer,
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "update silence, role-based access control is disabled, Editor",
|
||||
silence: silenceGen(),
|
||||
accessControl: func() accesscontrol.AccessControl {
|
||||
return acMock.New().WithDisabled()
|
||||
},
|
||||
role: org.RoleEditor,
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
{
|
||||
name: "update silence, role-based access control is disabled, Admin",
|
||||
silence: silenceGen(),
|
||||
accessControl: func() accesscontrol.AccessControl {
|
||||
return acMock.New().WithDisabled()
|
||||
},
|
||||
role: org.RoleAdmin,
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tesCase := range tesCases {
|
||||
t.Run(tesCase.name, func(t *testing.T) {
|
||||
ac := tesCase.accessControl()
|
||||
sut := createSut(t, ac)
|
||||
sut := createSut(t)
|
||||
|
||||
rc := contextmodel.ReqContext{
|
||||
Context: &web.Context{
|
||||
Req: &http.Request{},
|
||||
},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgRole: tesCase.role,
|
||||
OrgID: 1,
|
||||
Permissions: tesCase.permissions,
|
||||
OrgID: 1,
|
||||
},
|
||||
}
|
||||
|
||||
@ -653,18 +596,15 @@ func TestRouteCreateSilence(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func createSut(t *testing.T, accessControl accesscontrol.AccessControl) AlertmanagerSrv {
|
||||
func createSut(t *testing.T) AlertmanagerSrv {
|
||||
t.Helper()
|
||||
|
||||
mam := createMultiOrgAlertmanager(t)
|
||||
if accessControl == nil {
|
||||
accessControl = acMock.New().WithDisabled()
|
||||
}
|
||||
log := log.NewNopLogger()
|
||||
return AlertmanagerSrv{
|
||||
mam: mam,
|
||||
crypto: mam.Crypto,
|
||||
ac: accessControl,
|
||||
ac: acimpl.ProvideAccessControl(setting.NewCfg()),
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon
|
||||
return response.JSON(http.StatusInternalServerError, ruleResponse)
|
||||
}
|
||||
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
|
||||
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator)
|
||||
return accesscontrol.HasAccess(srv.ac, c)(evaluator)
|
||||
}
|
||||
|
||||
// Group rules together by Namespace and Rule Group. Rules are also grouped by Org ID,
|
||||
|
@ -10,24 +10,25 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
alertingModels "github.com/grafana/alerting/models"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
alertingModels "github.com/grafana/alerting/models"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
@ -94,7 +95,7 @@ func TestRouteGetAlertStatuses(t *testing.T) {
|
||||
orgID := int64(1)
|
||||
|
||||
t.Run("with no alerts", func(t *testing.T) {
|
||||
_, _, _, api := setupAPI(t)
|
||||
_, _, api := setupAPI(t)
|
||||
req, err := http.NewRequest("GET", "/api/v1/alerts", nil)
|
||||
require.NoError(t, err)
|
||||
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID}}
|
||||
@ -112,7 +113,7 @@ func TestRouteGetAlertStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with two alerts", func(t *testing.T) {
|
||||
_, fakeAIM, _, api := setupAPI(t)
|
||||
_, fakeAIM, api := setupAPI(t)
|
||||
fakeAIM.GenerateAlertInstances(1, util.GenerateShortUID(), 2)
|
||||
req, err := http.NewRequest("GET", "/api/v1/alerts", nil)
|
||||
require.NoError(t, err)
|
||||
@ -154,7 +155,7 @@ func TestRouteGetAlertStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with two firing alerts", func(t *testing.T) {
|
||||
_, fakeAIM, _, api := setupAPI(t)
|
||||
_, fakeAIM, api := setupAPI(t)
|
||||
fakeAIM.GenerateAlertInstances(1, util.GenerateShortUID(), 2, withAlertingState())
|
||||
req, err := http.NewRequest("GET", "/api/v1/alerts", nil)
|
||||
require.NoError(t, err)
|
||||
@ -196,7 +197,7 @@ func TestRouteGetAlertStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with the inclusion of internal labels", func(t *testing.T) {
|
||||
_, fakeAIM, _, api := setupAPI(t)
|
||||
_, fakeAIM, api := setupAPI(t)
|
||||
fakeAIM.GenerateAlertInstances(orgID, util.GenerateShortUID(), 2)
|
||||
req, err := http.NewRequest("GET", "/api/v1/alerts?includeInternalLabels=true", nil)
|
||||
require.NoError(t, err)
|
||||
@ -283,13 +284,14 @@ func withLabels(labels data.Labels) forEachState {
|
||||
func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
timeNow = func() time.Time { return time.Date(2022, 3, 10, 14, 0, 0, 0, time.UTC) }
|
||||
orgID := int64(1)
|
||||
queryPermissions := map[int64]map[string][]string{1: {datasources.ActionQuery: {datasources.ScopeAll}}}
|
||||
|
||||
req, err := http.NewRequest("GET", "/api/v1/rules", nil)
|
||||
require.NoError(t, err)
|
||||
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer}}
|
||||
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: queryPermissions}}
|
||||
|
||||
t.Run("with no rules", func(t *testing.T) {
|
||||
_, _, _, api := setupAPI(t)
|
||||
_, _, api := setupAPI(t)
|
||||
r := api.RouteGetRuleStatuses(c)
|
||||
|
||||
require.JSONEq(t, `
|
||||
@ -303,7 +305,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with a rule that only has one query", func(t *testing.T) {
|
||||
fakeStore, fakeAIM, _, api := setupAPI(t)
|
||||
fakeStore, fakeAIM, api := setupAPI(t)
|
||||
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery())
|
||||
folder := fakeStore.Folders[orgID][0]
|
||||
|
||||
@ -362,13 +364,13 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with the inclusion of internal Labels", func(t *testing.T) {
|
||||
fakeStore, fakeAIM, _, api := setupAPI(t)
|
||||
fakeStore, fakeAIM, api := setupAPI(t)
|
||||
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery())
|
||||
folder := fakeStore.Folders[orgID][0]
|
||||
|
||||
req, err := http.NewRequest("GET", "/api/v1/rules?includeInternalLabels=true", nil)
|
||||
require.NoError(t, err)
|
||||
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer}}
|
||||
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: queryPermissions}}
|
||||
|
||||
r := api.RouteGetRuleStatuses(c)
|
||||
require.Equal(t, http.StatusOK, r.Status())
|
||||
@ -428,7 +430,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with a rule that has multiple queries", func(t *testing.T) {
|
||||
fakeStore, fakeAIM, _, api := setupAPI(t)
|
||||
fakeStore, fakeAIM, api := setupAPI(t)
|
||||
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withExpressionsMultiQuery())
|
||||
folder := fakeStore.Folders[orgID][0]
|
||||
|
||||
@ -538,15 +540,16 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
ruleStore.PutRule(context.Background(), rules...)
|
||||
ruleStore.PutRule(context.Background(), ngmodels.GenerateAlertRules(rand.Intn(4)+2, ngmodels.AlertRuleGen(withOrgID(orgID)))...)
|
||||
|
||||
acMock := acmock.New().WithPermissions(createPermissionsForRules(rules))
|
||||
|
||||
api := PrometheusSrv{
|
||||
log: log.NewNopLogger(),
|
||||
manager: fakeAIM,
|
||||
store: ruleStore,
|
||||
ac: acMock,
|
||||
ac: acimpl.ProvideAccessControl(setting.NewCfg()),
|
||||
}
|
||||
|
||||
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: createPermissionsForRules(rules, orgID)}}
|
||||
|
||||
//c.SignedInUser.Permissions[1] = createPermissionsForRules(rules)
|
||||
response := api.RouteGetRuleStatuses(c)
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
result := &apimodels.RuleResponse{}
|
||||
@ -568,7 +571,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test totals are expected", func(t *testing.T) {
|
||||
fakeStore, fakeAIM, _, api := setupAPI(t)
|
||||
fakeStore, fakeAIM, api := setupAPI(t)
|
||||
// Create rules in the same Rule Group to keep assertions simple
|
||||
rules := ngmodels.GenerateAlertRules(3, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{
|
||||
Title: "Folder-1",
|
||||
@ -593,8 +596,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -628,7 +631,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test time of first firing alert", func(t *testing.T) {
|
||||
fakeStore, fakeAIM, _, api := setupAPI(t)
|
||||
fakeStore, fakeAIM, api := setupAPI(t)
|
||||
// Create rules in the same Rule Group to keep assertions simple
|
||||
rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID)))
|
||||
fakeStore.PutRule(context.Background(), rules...)
|
||||
@ -639,8 +642,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -684,7 +687,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test with limit on Rule Groups", func(t *testing.T) {
|
||||
fakeStore, _, _, api := setupAPI(t)
|
||||
fakeStore, _, api := setupAPI(t)
|
||||
|
||||
rules := ngmodels.GenerateAlertRules(2, ngmodels.AlertRuleGen(withOrgID(orgID)))
|
||||
fakeStore.PutRule(context.Background(), rules...)
|
||||
@ -695,8 +698,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -720,8 +723,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -744,8 +747,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -757,7 +760,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test with limit rules", func(t *testing.T) {
|
||||
fakeStore, _, _, api := setupAPI(t)
|
||||
fakeStore, _, api := setupAPI(t)
|
||||
rules := ngmodels.GenerateAlertRules(2, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1")))
|
||||
fakeStore.PutRule(context.Background(), rules...)
|
||||
|
||||
@ -767,8 +770,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -792,8 +795,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -816,8 +819,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -830,7 +833,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test with limit alerts", func(t *testing.T) {
|
||||
fakeStore, fakeAIM, _, api := setupAPI(t)
|
||||
fakeStore, fakeAIM, api := setupAPI(t)
|
||||
rules := ngmodels.GenerateAlertRules(2, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1")))
|
||||
fakeStore.PutRule(context.Background(), rules...)
|
||||
// create a normal and firing alert for each rule
|
||||
@ -845,8 +848,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -873,8 +876,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -903,8 +906,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -918,7 +921,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test with filters on state", func(t *testing.T) {
|
||||
fakeStore, fakeAIM, _, api := setupAPI(t)
|
||||
fakeStore, fakeAIM, api := setupAPI(t)
|
||||
// create two rules in the same Rule Group to keep assertions simple
|
||||
rules := ngmodels.GenerateAlertRules(3, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{
|
||||
Title: "Folder-1",
|
||||
@ -944,8 +947,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -961,8 +964,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -997,8 +1000,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -1036,8 +1039,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -1075,7 +1078,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test with matcher on labels", func(t *testing.T) {
|
||||
fakeStore, fakeAIM, _, api := setupAPI(t)
|
||||
fakeStore, fakeAIM, api := setupAPI(t)
|
||||
// create two rules in the same Rule Group to keep assertions simple
|
||||
rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{
|
||||
Title: "Folder-1",
|
||||
@ -1094,8 +1097,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -1111,8 +1114,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -1132,8 +1135,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -1158,8 +1161,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -1180,8 +1183,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -1202,8 +1205,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -1224,8 +1227,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: r},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: orgID,
|
||||
OrgRole: org.RoleViewer,
|
||||
OrgID: orgID,
|
||||
Permissions: queryPermissions,
|
||||
},
|
||||
}
|
||||
resp := api.RouteGetRuleStatuses(c)
|
||||
@ -1246,19 +1249,18 @@ func TestRouteGetRuleStatuses(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, *acmock.Mock, PrometheusSrv) {
|
||||
func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, PrometheusSrv) {
|
||||
fakeStore := fakes.NewRuleStore(t)
|
||||
fakeAIM := NewFakeAlertInstanceManager(t)
|
||||
acMock := acmock.New().WithDisabled()
|
||||
|
||||
api := PrometheusSrv{
|
||||
log: log.NewNopLogger(),
|
||||
manager: fakeAIM,
|
||||
store: fakeStore,
|
||||
ac: acMock,
|
||||
ac: acimpl.ProvideAccessControl(setting.NewCfg()),
|
||||
}
|
||||
|
||||
return fakeStore, fakeAIM, acMock, api
|
||||
return fakeStore, fakeAIM, api
|
||||
}
|
||||
|
||||
func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAlertInstanceManager, fakeStore *fakes.RuleStore, query func(r *ngmodels.AlertRule)) {
|
||||
|
@ -67,7 +67,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceT
|
||||
logger := srv.log.New(loggerCtx...)
|
||||
|
||||
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
|
||||
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqOrgAdminOrEditor, evaluator)
|
||||
return accesscontrol.HasAccess(srv.ac, c)(evaluator)
|
||||
}
|
||||
|
||||
provenances, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgID, (&ngmodels.AlertRule{}).ResourceType())
|
||||
@ -165,7 +165,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *contextmodel.ReqContext, nam
|
||||
result := apimodels.NamespaceConfigResponse{}
|
||||
|
||||
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
|
||||
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator)
|
||||
return accesscontrol.HasAccess(srv.ac, c)(evaluator)
|
||||
}
|
||||
|
||||
provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgID, (&ngmodels.AlertRule{}).ResourceType())
|
||||
@ -207,7 +207,7 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *contextmodel.ReqContext, namespa
|
||||
}
|
||||
|
||||
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
|
||||
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator)
|
||||
return accesscontrol.HasAccess(srv.ac, c)(evaluator)
|
||||
}
|
||||
|
||||
provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgID, (&ngmodels.AlertRule{}).ResourceType())
|
||||
@ -265,7 +265,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Res
|
||||
}
|
||||
|
||||
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
|
||||
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator)
|
||||
return accesscontrol.HasAccess(srv.ac, c)(evaluator)
|
||||
}
|
||||
|
||||
provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgID, (&ngmodels.AlertRule{}).ResourceType())
|
||||
@ -339,7 +339,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey
|
||||
// if RBAC is disabled the permission are limited to folder access that is done upstream
|
||||
if !srv.ac.IsDisabled() {
|
||||
err = authorizeRuleChanges(groupChanges, func(evaluator accesscontrol.Evaluator) bool {
|
||||
return hasAccess(accesscontrol.ReqOrgAdminOrEditor, evaluator)
|
||||
return hasAccess(evaluator)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -14,8 +14,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
@ -24,8 +23,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
@ -68,67 +67,15 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
return ruleStore
|
||||
}
|
||||
|
||||
t.Run("when fine-grained access is disabled", func(t *testing.T) {
|
||||
ac := acMock.New().WithDisabled()
|
||||
t.Run("viewer should not be authorized", func(t *testing.T) {
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||
|
||||
request := createRequestContext(orgID, org.RoleViewer, nil)
|
||||
response := createService(ac, ruleStore).RouteDeleteAlertRules(request, folder.Title, "")
|
||||
require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body()))
|
||||
|
||||
require.Empty(t, getRecordedCommand(ruleStore))
|
||||
})
|
||||
t.Run("editor should be able to delete all non-provisioned rules in folder", func(t *testing.T) {
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
rulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
|
||||
ruleStore.PutRule(context.Background(), rulesInFolder...)
|
||||
|
||||
request := createRequestContext(orgID, org.RoleEditor, nil)
|
||||
response := createService(ac, ruleStore).RouteDeleteAlertRules(request, folder.Title, "")
|
||||
|
||||
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||
})
|
||||
t.Run("editor should be able to delete rules group if it is not provisioned", func(t *testing.T) {
|
||||
groupName := util.GenerateShortUID()
|
||||
rulesInFolderInGroup := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))
|
||||
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
ruleStore.PutRule(context.Background(), rulesInFolderInGroup...)
|
||||
// rules in different groups but in the same namespace
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||
// rules in the same group but different folder
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withGroup(groupName)))...)
|
||||
|
||||
request := createRequestContext(orgID, org.RoleEditor, nil)
|
||||
response := createService(ac, ruleStore).RouteDeleteAlertRules(request, folder.Title, groupName)
|
||||
|
||||
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||
assertRulesDeleted(t, rulesInFolderInGroup, ruleStore)
|
||||
})
|
||||
t.Run("should return 202 if folder is empty", func(t *testing.T) {
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
|
||||
requestCtx := createRequestContext(orgID, org.RoleEditor, nil)
|
||||
response := createService(ac, ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
|
||||
|
||||
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||
require.Empty(t, getRecordedCommand(ruleStore))
|
||||
})
|
||||
})
|
||||
t.Run("when fine-grained access is enabled", func(t *testing.T) {
|
||||
requestCtx := createRequestContext(orgID, "None", nil)
|
||||
|
||||
t.Run("and group argument is empty", func(t *testing.T) {
|
||||
t.Run("return 401 if user is not authorized to access any group in the folder", func(t *testing.T) {
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||
|
||||
ac := acMock.New()
|
||||
request := createRequestContext(orgID, "None", nil)
|
||||
request := createRequestContextWithPerms(orgID, map[int64]map[string][]string{}, nil)
|
||||
|
||||
response := createService(ac, ruleStore).RouteDeleteAlertRules(request, folder.Title, "")
|
||||
response := createService(ruleStore).RouteDeleteAlertRules(request, folder.Title, "")
|
||||
require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body()))
|
||||
|
||||
require.Empty(t, getRecordedCommand(ruleStore))
|
||||
@ -148,9 +95,10 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
// more rules in the same namespace but user does not have access to them
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup("unauthz"+util.GenerateShortUID())))...)
|
||||
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(append(authorizedRulesInFolder, provisionedRulesInFolder...)))
|
||||
permissions := createPermissionsForRules(append(authorizedRulesInFolder, provisionedRulesInFolder...), orgID)
|
||||
requestCtx := createRequestContextWithPerms(orgID, permissions, nil)
|
||||
|
||||
response := createServiceWithProvenanceStore(ac, ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
|
||||
response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
|
||||
|
||||
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||
assertRulesDeleted(t, authorizedRulesInFolder, ruleStore)
|
||||
@ -167,13 +115,23 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
// more rules in the same namespace but user does not have access to them
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(util.GenerateShortUID())))...)
|
||||
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(provisionedRulesInFolder))
|
||||
permissions := createPermissionsForRules(provisionedRulesInFolder, orgID)
|
||||
requestCtx := createRequestContextWithPerms(orgID, permissions, nil)
|
||||
|
||||
response := createServiceWithProvenanceStore(ac, ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
|
||||
response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
|
||||
|
||||
require.Equalf(t, 400, response.Status(), "Expected 400 but got %d: %v", response.Status(), string(response.Body()))
|
||||
require.Empty(t, getRecordedCommand(ruleStore))
|
||||
})
|
||||
t.Run("should return 202 if folder is empty", func(t *testing.T) {
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
|
||||
requestCtx := createRequestContext(orgID, nil)
|
||||
response := createService(ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
|
||||
|
||||
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||
require.Empty(t, getRecordedCommand(ruleStore))
|
||||
})
|
||||
})
|
||||
t.Run("and group argument is not empty", func(t *testing.T) {
|
||||
groupName := util.GenerateShortUID()
|
||||
@ -185,9 +143,10 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
// more rules in the same group but user is not authorized to access them
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))...)
|
||||
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(authorizedRulesInGroup))
|
||||
permissions := createPermissionsForRules(authorizedRulesInGroup, orgID)
|
||||
requestCtx := createRequestContextWithPerms(orgID, permissions, nil)
|
||||
|
||||
response := createService(ac, ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
|
||||
response := createService(ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
|
||||
|
||||
require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body()))
|
||||
deleteCommands := getRecordedCommand(ruleStore)
|
||||
@ -203,9 +162,10 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
|
||||
ruleStore.PutRule(context.Background(), provisionedRulesInFolder...)
|
||||
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(provisionedRulesInFolder))
|
||||
permissions := createPermissionsForRules(provisionedRulesInFolder, orgID)
|
||||
requestCtx := createRequestContextWithPerms(orgID, permissions, nil)
|
||||
|
||||
response := createServiceWithProvenanceStore(ac, ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
|
||||
response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
|
||||
|
||||
require.Equalf(t, 400, response.Status(), "Expected 400 but got %d: %v", response.Status(), string(response.Body()))
|
||||
deleteCommands := getRecordedCommand(ruleStore)
|
||||
@ -225,45 +185,11 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
|
||||
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(expectedRules))
|
||||
|
||||
req := createRequestContext(orgID, "", nil)
|
||||
response := createService(ac, ruleStore).RouteGetNamespaceRulesConfig(req, folder.Title)
|
||||
permissions := createPermissionsForRules(expectedRules, orgID)
|
||||
req := createRequestContextWithPerms(orgID, permissions, nil)
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
result := &apimodels.NamespaceConfigResponse{}
|
||||
require.NoError(t, json.Unmarshal(response.Body(), result))
|
||||
require.NotNil(t, result)
|
||||
for namespace, groups := range *result {
|
||||
require.Equal(t, folder.Title, namespace)
|
||||
for _, group := range groups {
|
||||
grouploop:
|
||||
for _, actualRule := range group.Rules {
|
||||
for i, expected := range expectedRules {
|
||||
if actualRule.GrafanaManagedAlert.UID == expected.UID {
|
||||
expectedRules = append(expectedRules[:i], expectedRules[i+1:]...)
|
||||
continue grouploop
|
||||
}
|
||||
}
|
||||
assert.Failf(t, "rule in a group was not found in expected", "rule %s group %s", actualRule.GrafanaManagedAlert.Title, group.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.Emptyf(t, expectedRules, "not all expected rules were returned")
|
||||
})
|
||||
})
|
||||
t.Run("fine-grained access is disabled", func(t *testing.T) {
|
||||
t.Run("should return all rules from folder", func(t *testing.T) {
|
||||
orgID := rand.Int63()
|
||||
folder := randFolder()
|
||||
ruleStore := fakes.NewRuleStore(t)
|
||||
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
|
||||
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
req := createRequestContext(orgID, org.RoleViewer, nil)
|
||||
response := createService(ac, ruleStore).RouteGetNamespaceRulesConfig(req, folder.Title)
|
||||
response := createService(ruleStore).RouteGetNamespaceRulesConfig(req, folder.Title)
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
result := &apimodels.NamespaceConfigResponse{}
|
||||
@ -294,9 +220,8 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
|
||||
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
|
||||
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
svc := createService(ac, ruleStore)
|
||||
svc := createService(ruleStore)
|
||||
|
||||
// add provenance to the first generated rule
|
||||
rule := &models.AlertRule{
|
||||
@ -305,7 +230,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
|
||||
err := svc.provenanceStore.SetProvenance(context.Background(), rule, orgID, models.ProvenanceAPI)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := createRequestContext(orgID, org.RoleViewer, nil)
|
||||
req := createRequestContext(orgID, nil)
|
||||
response := svc.RouteGetNamespaceRulesConfig(req, folder.Title)
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
@ -338,9 +263,9 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
|
||||
|
||||
expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex()))
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
response := createService(ac, ruleStore).RouteGetNamespaceRulesConfig(createRequestContext(orgID, org.RoleViewer, nil), folder.Title)
|
||||
req := createRequestContext(orgID, nil)
|
||||
response := createService(ruleStore).RouteGetNamespaceRulesConfig(req, folder.Title)
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
result := &apimodels.NamespaceConfigResponse{}
|
||||
@ -389,10 +314,11 @@ func TestRouteGetRulesConfig(t *testing.T) {
|
||||
group2 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group2Key)))
|
||||
ruleStore.PutRule(context.Background(), append(group1, group2...)...)
|
||||
|
||||
request := createRequestContext(orgID, "", nil)
|
||||
t.Run("and do not return group if user does not have access to one of rules", func(t *testing.T) {
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(append(group1, group2[1:]...)))
|
||||
response := createService(ac, ruleStore).RouteGetRulesConfig(request)
|
||||
permissions := createPermissionsForRules(append(group1, group2[1:]...), orgID)
|
||||
request := createRequestContextWithPerms(orgID, permissions, nil)
|
||||
|
||||
response := createService(ruleStore).RouteGetRulesConfig(request)
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
|
||||
result := &apimodels.NamespaceConfigResponse{}
|
||||
@ -420,9 +346,9 @@ func TestRouteGetRulesConfig(t *testing.T) {
|
||||
|
||||
expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex()))
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
response := createService(ac, ruleStore).RouteGetRulesConfig(createRequestContext(orgID, org.RoleViewer, nil))
|
||||
req := createRequestContext(orgID, nil)
|
||||
response := createService(ruleStore).RouteGetRulesConfig(req)
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
result := &apimodels.NamespaceConfigResponse{}
|
||||
@ -466,20 +392,23 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
|
||||
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(groupKey)))
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
|
||||
request := createRequestContext(orgID, "", map[string]string{
|
||||
":Namespace": folder.Title,
|
||||
":Groupname": groupKey.RuleGroup,
|
||||
})
|
||||
|
||||
t.Run("and return 401 if user does not have access one of rules", func(t *testing.T) {
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(expectedRules[1:]))
|
||||
response := createService(ac, ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
|
||||
permissions := createPermissionsForRules(expectedRules[1:], orgID)
|
||||
request := createRequestContextWithPerms(orgID, permissions, map[string]string{
|
||||
":Namespace": folder.Title,
|
||||
":Groupname": groupKey.RuleGroup,
|
||||
})
|
||||
response := createService(ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
|
||||
require.Equal(t, http.StatusUnauthorized, response.Status())
|
||||
})
|
||||
|
||||
t.Run("and return rules if user has access to all of them", func(t *testing.T) {
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(expectedRules))
|
||||
response := createService(ac, ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
|
||||
permissions := createPermissionsForRules(expectedRules, orgID)
|
||||
request := createRequestContextWithPerms(orgID, permissions, map[string]string{
|
||||
":Namespace": folder.Title,
|
||||
":Groupname": groupKey.RuleGroup,
|
||||
})
|
||||
response := createService(ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
result := &apimodels.RuleGroupConfigResponse{}
|
||||
@ -500,9 +429,9 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
|
||||
|
||||
expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex()))
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
response := createService(ac, ruleStore).RouteGetRulesGroupConfig(createRequestContext(orgID, org.RoleViewer, nil), folder.Title, groupKey.RuleGroup)
|
||||
req := createRequestContext(orgID, nil)
|
||||
response := createService(ruleStore).RouteGetRulesGroupConfig(req, folder.Title, groupKey.RuleGroup)
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
result := &apimodels.RuleGroupConfigResponse{}
|
||||
@ -589,13 +518,13 @@ func TestVerifyProvisionedRulesNotAffected(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func createServiceWithProvenanceStore(ac *acMock.Mock, store *fakes.RuleStore, provenanceStore provisioning.ProvisioningStore) *RulerSrv {
|
||||
svc := createService(ac, store)
|
||||
func createServiceWithProvenanceStore(store *fakes.RuleStore, provenanceStore provisioning.ProvisioningStore) *RulerSrv {
|
||||
svc := createService(store)
|
||||
svc.provenanceStore = provenanceStore
|
||||
return svc
|
||||
}
|
||||
|
||||
func createService(ac *acMock.Mock, store *fakes.RuleStore) *RulerSrv {
|
||||
func createService(store *fakes.RuleStore) *RulerSrv {
|
||||
return &RulerSrv{
|
||||
xactManager: store,
|
||||
store: store,
|
||||
@ -603,11 +532,16 @@ func createService(ac *acMock.Mock, store *fakes.RuleStore) *RulerSrv {
|
||||
provenanceStore: provisioning.NewFakeProvisioningStore(),
|
||||
log: log.New("test"),
|
||||
cfg: nil,
|
||||
ac: ac,
|
||||
ac: acimpl.ProvideAccessControl(setting.NewCfg()),
|
||||
}
|
||||
}
|
||||
|
||||
func createRequestContext(orgID int64, role org.RoleType, params map[string]string) *contextmodel.ReqContext {
|
||||
func createRequestContext(orgID int64, params map[string]string) *contextmodel.ReqContext {
|
||||
defaultPerms := map[int64]map[string][]string{orgID: {datasources.ActionQuery: []string{datasources.ScopeAll}}}
|
||||
return createRequestContextWithPerms(orgID, defaultPerms, params)
|
||||
}
|
||||
|
||||
func createRequestContextWithPerms(orgID int64, permissions map[int64]map[string][]string, params map[string]string) *contextmodel.ReqContext {
|
||||
uri, _ := url.Parse("http://localhost")
|
||||
ctx := web.Context{Req: &http.Request{
|
||||
URL: uri,
|
||||
@ -619,23 +553,21 @@ func createRequestContext(orgID int64, role org.RoleType, params map[string]stri
|
||||
return &contextmodel.ReqContext{
|
||||
IsSignedIn: true,
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgRole: role,
|
||||
OrgID: orgID,
|
||||
Permissions: permissions,
|
||||
OrgID: orgID,
|
||||
},
|
||||
Context: &ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func createPermissionsForRules(rules []*models.AlertRule) []accesscontrol.Permission {
|
||||
var permissions []accesscontrol.Permission
|
||||
func createPermissionsForRules(rules []*models.AlertRule, orgID int64) map[int64]map[string][]string {
|
||||
permissions := map[string][]string{}
|
||||
for _, rule := range rules {
|
||||
for _, query := range rule.Data {
|
||||
permissions = append(permissions, accesscontrol.Permission{
|
||||
Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID),
|
||||
})
|
||||
permissions[datasources.ActionQuery] = append(permissions[datasources.ActionQuery], datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID))
|
||||
}
|
||||
}
|
||||
return permissions
|
||||
return map[int64]map[string][]string{orgID: permissions}
|
||||
}
|
||||
|
||||
func withOrgID(orgId int64) func(rule *models.AlertRule) {
|
||||
|
@ -43,7 +43,7 @@ func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *contextmodel.ReqContext,
|
||||
queries := AlertQueriesFromApiAlertQueries(body.GrafanaManagedCondition.Data)
|
||||
|
||||
if !authorizeDatasourceAccessForRule(&ngmodels.AlertRule{Data: queries}, func(evaluator accesscontrol.Evaluator) bool {
|
||||
return accesscontrol.HasAccess(srv.accessControl, c)(accesscontrol.ReqSignedIn, evaluator)
|
||||
return accesscontrol.HasAccess(srv.accessControl, c)(evaluator)
|
||||
}) {
|
||||
return errorToResponse(fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization))
|
||||
}
|
||||
@ -116,7 +116,7 @@ func (srv TestingApiSrv) RouteTestRuleConfig(c *contextmodel.ReqContext, body ap
|
||||
func (srv TestingApiSrv) RouteEvalQueries(c *contextmodel.ReqContext, cmd apimodels.EvalQueriesPayload) response.Response {
|
||||
queries := AlertQueriesFromApiAlertQueries(cmd.Data)
|
||||
if !authorizeDatasourceAccessForRule(&ngmodels.AlertRule{Data: queries}, func(evaluator accesscontrol.Evaluator) bool {
|
||||
return accesscontrol.HasAccess(srv.accessControl, c)(accesscontrol.ReqSignedIn, evaluator)
|
||||
return accesscontrol.HasAccess(srv.accessControl, c)(evaluator)
|
||||
}) {
|
||||
return ErrResp(http.StatusUnauthorized, fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization), "")
|
||||
}
|
||||
@ -174,7 +174,7 @@ func (srv TestingApiSrv) BacktestAlertRule(c *contextmodel.ReqContext, cmd apimo
|
||||
|
||||
queries := AlertQueriesFromApiAlertQueries(cmd.Data)
|
||||
if !authorizeDatasourceAccessForRule(&ngmodels.AlertRule{Data: queries}, func(evaluator accesscontrol.Evaluator) bool {
|
||||
return accesscontrol.HasAccess(srv.accessControl, c)(accesscontrol.ReqSignedIn, evaluator)
|
||||
return accesscontrol.HasAccess(srv.accessControl, c)(evaluator)
|
||||
}) {
|
||||
return errorToResponse(fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization))
|
||||
}
|
||||
|
@ -93,61 +93,6 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
||||
evaluator.AssertCalled(t, "Evaluate", mock.Anything, currentTime)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when fine-grained access is disabled", func(t *testing.T) {
|
||||
rc := &contextmodel.ReqContext{
|
||||
Context: &web.Context{
|
||||
Req: &http.Request{},
|
||||
},
|
||||
IsSignedIn: false,
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
},
|
||||
}
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
t.Run("should require user to be signed in", func(t *testing.T) {
|
||||
data1 := models.GenerateAlertQuery()
|
||||
|
||||
ds := &fakes.FakeCacheService{DataSources: []*datasources.DataSource{
|
||||
{UID: data1.DatasourceUID},
|
||||
}}
|
||||
currentTime := time.Now()
|
||||
|
||||
evaluator := &eval_mocks.ConditionEvaluatorMock{}
|
||||
var result []eval.Result
|
||||
evaluator.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(result, nil)
|
||||
|
||||
srv := createTestingApiSrv(ds, ac, eval_mocks.NewEvaluatorFactory(evaluator))
|
||||
|
||||
response := srv.RouteTestGrafanaRuleConfig(rc, definitions.TestRulePayload{
|
||||
Expr: "",
|
||||
GrafanaManagedCondition: &definitions.EvalAlertConditionCommand{
|
||||
Condition: data1.RefID,
|
||||
Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1}),
|
||||
Now: currentTime,
|
||||
},
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusUnauthorized, response.Status())
|
||||
evaluator.AssertNotCalled(t, "Evaluate", mock.Anything, currentTime)
|
||||
|
||||
rc.IsSignedIn = true
|
||||
|
||||
response = srv.RouteTestGrafanaRuleConfig(rc, definitions.TestRulePayload{
|
||||
Expr: "",
|
||||
GrafanaManagedCondition: &definitions.EvalAlertConditionCommand{
|
||||
Condition: data1.RefID,
|
||||
Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1}),
|
||||
Now: currentTime,
|
||||
},
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
|
||||
evaluator.AssertCalled(t, "Evaluate", mock.Anything, currentTime)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestRouteEvalQueries(t *testing.T) {
|
||||
@ -220,61 +165,6 @@ func TestRouteEvalQueries(t *testing.T) {
|
||||
evaluator.AssertCalled(t, "EvaluateRaw", mock.Anything, currentTime)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when fine-grained access is disabled", func(t *testing.T) {
|
||||
rc := &contextmodel.ReqContext{
|
||||
Context: &web.Context{
|
||||
Req: &http.Request{},
|
||||
},
|
||||
IsSignedIn: false,
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
},
|
||||
}
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
t.Run("should require user to be signed in", func(t *testing.T) {
|
||||
data1 := models.GenerateAlertQuery()
|
||||
|
||||
ds := &fakes.FakeCacheService{DataSources: []*datasources.DataSource{
|
||||
{UID: data1.DatasourceUID},
|
||||
}}
|
||||
|
||||
currentTime := time.Now()
|
||||
|
||||
evaluator := &eval_mocks.ConditionEvaluatorMock{}
|
||||
result := &backend.QueryDataResponse{
|
||||
Responses: map[string]backend.DataResponse{
|
||||
"test": {
|
||||
Frames: nil,
|
||||
Error: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
evaluator.EXPECT().EvaluateRaw(mock.Anything, mock.Anything).Return(result, nil)
|
||||
|
||||
srv := createTestingApiSrv(ds, ac, eval_mocks.NewEvaluatorFactory(evaluator))
|
||||
|
||||
response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
|
||||
Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1}),
|
||||
Now: currentTime,
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusUnauthorized, response.Status())
|
||||
evaluator.AssertNotCalled(t, "EvaluateRaw", mock.Anything, mock.Anything)
|
||||
|
||||
rc.IsSignedIn = true
|
||||
|
||||
response = srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
|
||||
Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1}),
|
||||
Now: currentTime,
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
|
||||
evaluator.AssertCalled(t, "EvaluateRaw", mock.Anything, currentTime)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func createTestingApiSrv(ds *fakes.FakeCacheService, ac *acMock.Mock, evaluator eval.EvaluatorFactory) *TestingApiSrv {
|
||||
|
Loading…
Reference in New Issue
Block a user