Skip to content

Commit 2e0cc23

Browse files
committed
22101 fix(mon-hosts): apply legacy view permissions
The experimental "All hosts" page was reachable by any logged-in user, even though the sidebar entry and the classic view it replaces both require "view.allhosts". Require it on the page as well, so a user who may not see the classic view cannot reach the new one either. The availability dropdown now follows the classic view too: it is only offered to users who may see availability, instead of linking every user to a page that then refuses them. CMK-37891 Change-Id: I99475847b3eb9d4cb1bbef69432efe4fa941e563 (cherry picked from commit 8e775423f518d82f7784242c8b4ca7b08586b017)
1 parent 56fa018 commit 2e0cc23

3 files changed

Lines changed: 117 additions & 24 deletions

File tree

.werks/22101.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[//]: # (werk v3)
2+
# All hosts page requires the 'view.allhosts' permission
3+
4+
key | value
5+
---------- | ---
6+
date | 2026-08-10T11:01:37.906374+00:00
7+
version | 2.5.0p12
8+
class | fix
9+
edition | community
10+
component | multisite
11+
level | 1
12+
compatible | yes
13+
14+
The experimental "All hosts" page could be opened by any logged-in user via
15+
its URL, even though the sidebar entry and the classic "All hosts" view it
16+
replaces both require the <tt>view.allhosts</tt> permission. The page now
17+
requires that permission as well, so a user who may not see the classic view
18+
can no longer reach the new one either.
19+
20+
Note that this only concerns which view a user may open; the hosts listed on
21+
the page have always been restricted to those the user is a contact for.
22+
23+
In addition, the "Availability" menu is now only offered to users who hold the
24+
<tt>general.see_availability</tt> permission, instead of linking every user to
25+
a page that then refuses them.

cmk/gui/monitor/hosts/_pages/_monitor_all_hosts.py

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838

3939
_PAGE_TITLE = _("All hosts (experimental)")
4040

41+
_LEGACY_VIEW_NAME = "allhosts"
42+
_LEGACY_VIEW_PERMISSION = f"view.{_LEGACY_VIEW_NAME}"
43+
4144

4245
def _row_actions(config: Config) -> list[RowAction]:
4346
if not config.wato_enabled:
@@ -99,6 +102,8 @@ def __init__(self, commands: CommandRegistry) -> None:
99102

100103
@override
101104
def page(self, ctx: PageContext) -> None:
105+
user.need_permission(_LEGACY_VIEW_PERMISSION)
106+
102107
breadcrumb = _make_breadcrumb(ctx)
103108

104109
make_header(
@@ -121,7 +126,9 @@ def page(self, ctx: PageContext) -> None:
121126
may_ignore_hard_limit=user.may("general.ignore_hard_limit"),
122127
legacy_view_button=MonitoringPageLinkButton(
123128
url=makeuri_contextless(
124-
ctx.request, vars_=[("view_name", "allhosts")], filename="view.py"
129+
ctx.request,
130+
vars_=[("view_name", _LEGACY_VIEW_NAME)],
131+
filename="view.py",
125132
),
126133
title=_("Return to classic view"),
127134
),
@@ -148,35 +155,42 @@ def _make_breadcrumb(ctx: PageContext) -> Breadcrumb:
148155
return breadcrumb
149156

150157

151-
def _build_page_menu(breadcrumb: Breadcrumb) -> PageMenu:
158+
def _availability_dropdowns() -> list[PageMenuDropdown]:
159+
if not user.may("general.see_availability"):
160+
return []
161+
152162
availability_url = makeuri_contextless(
153163
request,
154-
[("view_name", "allhosts"), ("mode", "availability")],
164+
[("view_name", _LEGACY_VIEW_NAME), ("mode", "availability")],
155165
filename="view.py",
156166
)
157167

168+
return [
169+
PageMenuDropdown(
170+
name="availability",
171+
title=_("Availability"),
172+
topics=[
173+
PageMenuTopic(
174+
title=_("This view"),
175+
entries=[
176+
PageMenuEntry(
177+
title=_("Availability"),
178+
icon_name=StaticIcon(IconNames.availability),
179+
item=make_simple_link(availability_url),
180+
name="availability",
181+
is_shortcut=False,
182+
is_suggested=False,
183+
)
184+
],
185+
)
186+
],
187+
),
188+
]
189+
190+
191+
def _build_page_menu(breadcrumb: Breadcrumb) -> PageMenu:
158192
menu = PageMenu(
159-
dropdowns=[
160-
PageMenuDropdown(
161-
name="availability",
162-
title=_("Availability"),
163-
topics=[
164-
PageMenuTopic(
165-
title=_("This view"),
166-
entries=[
167-
PageMenuEntry(
168-
title=_("Availability"),
169-
icon_name=StaticIcon(IconNames.availability),
170-
item=make_simple_link(availability_url),
171-
name="availability",
172-
is_shortcut=False,
173-
is_suggested=False,
174-
)
175-
],
176-
)
177-
],
178-
),
179-
],
193+
dropdowns=_availability_dropdowns(),
180194
breadcrumb=breadcrumb,
181195
)
182196

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/usr/bin/env python3
2+
# Copyright (C) 2026 Checkmk GmbH - License: GNU General Public License v2
3+
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
4+
# conditions defined in the file COPYING, which is part of this source code package.
5+
from collections.abc import Iterator
6+
7+
import pytest
8+
9+
from cmk.ccc.user import UserId
10+
from cmk.gui import login
11+
from cmk.gui.config import Config
12+
from cmk.gui.exceptions import MKAuthException
13+
from cmk.gui.http import request
14+
from cmk.gui.monitor.hosts._pages._monitor_all_hosts import (
15+
_availability_dropdowns,
16+
MonitorAllHostsPage,
17+
)
18+
from cmk.gui.pages import PageContext
19+
from cmk.gui.permissions import permission_registry
20+
from cmk.gui.utils.roles import UserPermissions
21+
from cmk.gui.views.command.registry import command_registry
22+
from tests.unit.cmk.gui.users import create_and_destroy_user
23+
24+
25+
@pytest.fixture(name="user_without_permissions")
26+
def fixture_user_without_permissions(load_config: Config) -> Iterator[UserId]:
27+
with create_and_destroy_user(
28+
automation=False, role="no_permissions", config=load_config
29+
) as created:
30+
user_id = created[0]
31+
with login.TransactionIdContext(
32+
user_id,
33+
UserPermissions(
34+
load_config.roles, permission_registry, {user_id: ["no_permissions"]}, []
35+
),
36+
):
37+
yield user_id
38+
39+
40+
def test_page_denied_without_legacy_view_permission(user_without_permissions: UserId) -> None:
41+
page = MonitorAllHostsPage(command_registry)
42+
43+
with pytest.raises(MKAuthException):
44+
page.page(PageContext(config=Config(), request=request))
45+
46+
47+
def test_availability_dropdown_hidden_without_permission(
48+
user_without_permissions: UserId,
49+
) -> None:
50+
assert _availability_dropdowns() == []
51+
52+
53+
def test_availability_dropdown_shown_with_permission(with_user_login: UserId) -> None:
54+
assert [dropdown.name for dropdown in _availability_dropdowns()] == ["availability"]

0 commit comments

Comments
 (0)