`AdministrativeMonitorsDecorator` cleanup; `ManageJenkinsAction.getBadge` optimization (#10855)

This commit is contained in:
Jesse Glick 2025-08-15 11:30:04 -04:00 committed by GitHub
parent 3bf037ca07
commit 00c6707c45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 112 additions and 290 deletions

View File

@ -66,6 +66,11 @@ public class ReverseProxySetupMonitor extends AdministrativeMonitor {
return true; return true;
} }
@Override
public boolean isActivationFake() {
return true;
}
@Restricted(DoNotUse.class) // WebOnly @Restricted(DoNotUse.class) // WebOnly
@RestrictedSince("2.235") @RestrictedSince("2.235")
public HttpResponse doTest(StaplerRequest2 request, @QueryParameter boolean testWithContext) { public HttpResponse doTest(StaplerRequest2 request, @QueryParameter boolean testWithContext) {

View File

@ -160,6 +160,11 @@ public abstract class AdministrativeMonitor extends AbstractModelObject implemen
*/ */
public abstract boolean isActivated(); public abstract boolean isActivated();
@Restricted(NoExternalUse.class)
public boolean isActivationFake() {
return false;
}
/** /**
* Returns true if this monitor is security related. * Returns true if this monitor is security related.
* *
@ -186,8 +191,7 @@ public abstract class AdministrativeMonitor extends AbstractModelObject implemen
* By default {@link Jenkins#ADMINISTER}, but {@link Jenkins#SYSTEM_READ} or {@link Jenkins#MANAGE} are also supported. * By default {@link Jenkins#ADMINISTER}, but {@link Jenkins#SYSTEM_READ} or {@link Jenkins#MANAGE} are also supported.
* <p> * <p>
* Changing this permission check to return {@link Jenkins#SYSTEM_READ} will make the active * Changing this permission check to return {@link Jenkins#SYSTEM_READ} will make the active
* administrative monitor appear on {@code manage.jelly} and on the globally visible * administrative monitor appear on {@link ManageJenkinsAction} to users without Administer permission.
* {@link jenkins.management.AdministrativeMonitorsDecorator} to users without Administer permission.
* {@link #doDisable(StaplerRequest2, StaplerResponse2)} will still always require Administer permission. * {@link #doDisable(StaplerRequest2, StaplerResponse2)} will still always require Administer permission.
* </p> * </p>
* <p> * <p>

View File

@ -26,11 +26,11 @@ package hudson.model;
import hudson.Extension; import hudson.Extension;
import hudson.Util; import hudson.Util;
import hudson.util.HudsonIsLoading;
import hudson.util.HudsonIsRestarting;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.logging.Level;
import java.util.Collections; import java.util.logging.Logger;
import java.util.Optional;
import jenkins.management.AdministrativeMonitorsDecorator;
import jenkins.management.Badge; import jenkins.management.Badge;
import jenkins.model.Jenkins; import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithContextMenu; import jenkins.model.ModelObjectWithContextMenu;
@ -50,6 +50,9 @@ import org.kohsuke.stapler.StaplerResponse2;
*/ */
@Extension(ordinal = 998) @Symbol("manageJenkins") @Extension(ordinal = 998) @Symbol("manageJenkins")
public class ManageJenkinsAction implements RootAction, StaplerFallback, ModelObjectWithContextMenu { public class ManageJenkinsAction implements RootAction, StaplerFallback, ModelObjectWithContextMenu {
private static final Logger LOGGER = Logger.getLogger(ManageJenkinsAction.class.getName());
@Override @Override
public String getIconFileName() { public String getIconFileName() {
if (Jenkins.get().hasAnyPermission(Jenkins.MANAGE, Jenkins.SYSTEM_READ)) if (Jenkins.get().hasAnyPermission(Jenkins.MANAGE, Jenkins.SYSTEM_READ))
@ -98,28 +101,36 @@ public class ManageJenkinsAction implements RootAction, StaplerFallback, ModelOb
menu.add("manage/" + url, icon, iconXml, text, post, requiresConfirmation, badge, message); menu.add("manage/" + url, icon, iconXml, text, post, requiresConfirmation, badge, message);
} }
/** Unlike {@link Jenkins#getActiveAdministrativeMonitors} this checks for activation lazily. */
@Override @Override
public Badge getBadge() { public Badge getBadge() {
Jenkins jenkins = Jenkins.get(); if (!(AdministrativeMonitor.hasPermissionToDisplay())) {
AdministrativeMonitorsDecorator decorator = jenkins.getExtensionList(PageDecorator.class)
.get(AdministrativeMonitorsDecorator.class);
if (decorator == null) {
return null; return null;
} }
Collection<AdministrativeMonitor> activeAdministrativeMonitors = Optional.ofNullable(decorator.getMonitorsToDisplay()).orElse(Collections.emptyList()); var app = Jenkins.get().getServletContext().getAttribute("app");
boolean anySecurity = activeAdministrativeMonitors.stream().anyMatch(AdministrativeMonitor::isSecurity); if (app instanceof HudsonIsLoading || app instanceof HudsonIsRestarting) {
if (activeAdministrativeMonitors.isEmpty()) {
return null; return null;
} }
int size = activeAdministrativeMonitors.size(); if (Jenkins.get().administrativeMonitors.stream().anyMatch(m -> m.isSecurity() && isActive(m))) {
String tooltip = size > 1 ? Messages.ManageJenkinsAction_notifications(size) : Messages.ManageJenkinsAction_notification(size); return new Badge("1+", Messages.ManageJenkinsAction_notifications(),
Badge.Severity.DANGER);
return new Badge(String.valueOf(size), } else if (Jenkins.get().administrativeMonitors.stream().anyMatch(m -> !m.isSecurity() && isActive(m))) {
tooltip, return new Badge("1+", Messages.ManageJenkinsAction_notifications(),
anySecurity ? Badge.Severity.DANGER : Badge.Severity.WARNING); Badge.Severity.WARNING);
} else {
return null;
}
} }
private static boolean isActive(AdministrativeMonitor m) {
try {
return !m.isActivationFake() && m.hasRequiredPermission() && m.isEnabled() && m.isActivated();
} catch (Throwable x) {
LOGGER.log(Level.WARNING, null, x);
return false;
}
}
} }

View File

@ -31,6 +31,11 @@ public class URICheckEncodingMonitor extends AdministrativeMonitor {
return true; return true;
} }
@Override
public boolean isActivationFake() {
return true;
}
@Override @Override
public String getDisplayName() { public String getDisplayName() {
return Messages.URICheckEncodingMonitor_DisplayName(); return Messages.URICheckEncodingMonitor_DisplayName();

View File

@ -1,50 +0,0 @@
package jenkins.management;
import hudson.Extension;
import hudson.model.PageDecorator;
import hudson.model.RootAction;
import jakarta.servlet.ServletException;
import java.io.IOException;
import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.StaplerRequest2;
import org.kohsuke.stapler.StaplerResponse2;
import org.kohsuke.stapler.verb.GET;
@Extension
@Restricted(NoExternalUse.class)
public class AdministrativeMonitorsApi implements RootAction {
@GET
public void doNonSecurityPopupContent(StaplerRequest2 req, StaplerResponse2 resp) throws IOException, ServletException {
AdministrativeMonitorsApiData viewData = new AdministrativeMonitorsApiData(getDecorator().getNonSecurityAdministrativeMonitors());
req.getView(viewData, "monitorsList.jelly").forward(req, resp);
}
@GET
public void doSecurityPopupContent(StaplerRequest2 req, StaplerResponse2 resp) throws IOException, ServletException {
AdministrativeMonitorsApiData viewData = new AdministrativeMonitorsApiData(getDecorator().getSecurityAdministrativeMonitors());
req.getView(viewData, "monitorsList.jelly").forward(req, resp);
}
@Override
public String getIconFileName() {
return null;
}
@Override
public String getDisplayName() {
return null;
}
@Override
public String getUrlName() {
return "administrativeMonitorsApi";
}
private AdministrativeMonitorsDecorator getDecorator() {
return Jenkins.get()
.getExtensionList(PageDecorator.class)
.get(AdministrativeMonitorsDecorator.class);
}
}

View File

@ -1,24 +0,0 @@
package jenkins.management;
import hudson.model.AdministrativeMonitor;
import java.util.ArrayList;
import java.util.List;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@Restricted(NoExternalUse.class)
public class AdministrativeMonitorsApiData {
private final List<AdministrativeMonitor> monitorsList = new ArrayList<>();
AdministrativeMonitorsApiData(List<AdministrativeMonitor> monitors) {
monitorsList.addAll(monitors);
}
public List<AdministrativeMonitor> getMonitorsList() {
return this.monitorsList;
}
public boolean hasActiveMonitors() {
return !this.monitorsList.isEmpty();
}
}

View File

@ -1,173 +0,0 @@
/*
* The MIT License
*
* Copyright (c) 2016, Daniel Beck, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.management;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.diagnosis.ReverseProxySetupMonitor;
import hudson.model.AdministrativeMonitor;
import hudson.model.PageDecorator;
import hudson.util.HudsonIsLoading;
import hudson.util.HudsonIsRestarting;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import jenkins.diagnostics.URICheckEncodingMonitor;
import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest2;
/**
* Show notifications and popups for active administrative monitors on all pages.
*/
@Extension
@Restricted(NoExternalUse.class)
public class AdministrativeMonitorsDecorator extends PageDecorator {
private final Collection<String> ignoredJenkinsRestOfUrls = new ArrayList<>();
public AdministrativeMonitorsDecorator() {
// otherwise this would be added to every internal context menu building request
ignoredJenkinsRestOfUrls.add("contextMenu");
}
@NonNull
@Override
public String getDisplayName() {
return Messages.AdministrativeMonitorsDecorator_DisplayName();
}
// Used by Jelly
public Collection<AdministrativeMonitor> filterNonSecurityAdministrativeMonitors(Collection<AdministrativeMonitor> activeMonitors) {
return this.filterActiveAdministrativeMonitors(activeMonitors, false);
}
// Used by Jelly
public Collection<AdministrativeMonitor> filterSecurityAdministrativeMonitors(Collection<AdministrativeMonitor> activeMonitors) {
return this.filterActiveAdministrativeMonitors(activeMonitors, true);
}
/**
* Prevent us to compute multiple times the {@link AdministrativeMonitor#isActivated()} by re-using the same list
*/
private Collection<AdministrativeMonitor> filterActiveAdministrativeMonitors(Collection<AdministrativeMonitor> activeMonitors, boolean isSecurity) {
Collection<AdministrativeMonitor> active = new ArrayList<>();
for (AdministrativeMonitor am : activeMonitors) {
if (am.isSecurity() == isSecurity) {
active.add(am);
}
}
return active;
}
// Used by API
public List<AdministrativeMonitor> getNonSecurityAdministrativeMonitors() {
Collection<AdministrativeMonitor> allowedMonitors = getMonitorsToDisplay();
if (allowedMonitors == null) {
return Collections.emptyList();
}
return allowedMonitors.stream()
.filter(administrativeMonitor -> !administrativeMonitor.isSecurity())
.collect(Collectors.toList());
}
// Used by API
public List<AdministrativeMonitor> getSecurityAdministrativeMonitors() {
Collection<AdministrativeMonitor> allowedMonitors = getMonitorsToDisplay();
if (allowedMonitors == null) {
return Collections.emptyList();
}
return allowedMonitors.stream()
.filter(AdministrativeMonitor::isSecurity)
.collect(Collectors.toList());
}
private Collection<AdministrativeMonitor> getAllActiveAdministrativeMonitors() {
Collection<AdministrativeMonitor> active = new ArrayList<>();
for (AdministrativeMonitor am : Jenkins.get().getActiveAdministrativeMonitors()) {
if (am instanceof ReverseProxySetupMonitor) {
// TODO make reverse proxy monitor work when shown on any URL
continue;
}
if (am instanceof URICheckEncodingMonitor) {
// TODO make URI encoding monitor work when shown on any URL
continue;
}
active.add(am);
}
return active;
}
/**
* Compute the administrative monitors that are active and should be shown.
* This is done only when the instance is currently running and the user has the permission to read them.
*
* @return the list of active monitors if we should display them, otherwise null.
*/
public Collection<AdministrativeMonitor> getMonitorsToDisplay() {
if (!(AdministrativeMonitor.hasPermissionToDisplay())) {
return null;
}
StaplerRequest2 req = Stapler.getCurrentRequest2();
if (req == null) {
return null;
}
List<Ancestor> ancestors = req.getAncestors();
if (ancestors == null || ancestors.isEmpty()) {
// ???
return null;
}
Ancestor a = ancestors.get(ancestors.size() - 1);
Object o = a.getObject();
// don't show while Jenkins is loading
if (o instanceof HudsonIsLoading || o instanceof HudsonIsRestarting) {
return null;
}
// don't show for some URLs served directly by Jenkins
if (o instanceof Jenkins) {
String url = a.getRestOfUrl();
if (ignoredJenkinsRestOfUrls.contains(url)) {
return null;
}
}
return getAllActiveAdministrativeMonitors();
}
}

View File

@ -66,7 +66,6 @@ public class Badge {
* *
* @param text The text to be shown in the badge. * @param text The text to be shown in the badge.
* Keep it short, ideally just a number. More than 6 or 7 characters do not look good. Avoid spaces as they will lead to line breaks. * Keep it short, ideally just a number. More than 6 or 7 characters do not look good. Avoid spaces as they will lead to line breaks.
* as this might lead to line breaks.
* @param tooltip The tooltip to show for the badge. * @param tooltip The tooltip to show for the badge.
* Do not include html tags. * Do not include html tags.
* @param severity The severity of the badge (danger, warning, info) * @param severity The severity of the badge (danger, warning, info)

View File

@ -199,8 +199,7 @@ LabelExpression.LabelLink=<a href="{0}{2}">Label {1}</a> matches {3,choice,0#no
LabelExpression.NoMatch=No agent/cloud matches this label expression. LabelExpression.NoMatch=No agent/cloud matches this label expression.
LabelExpression.NoMatch_DidYouMean=No agent/cloud matches this label expression. Did you mean {1} instead of {0}? LabelExpression.NoMatch_DidYouMean=No agent/cloud matches this label expression. Did you mean {1} instead of {0}?
ManageJenkinsAction.DisplayName=Manage Jenkins ManageJenkinsAction.DisplayName=Manage Jenkins
ManageJenkinsAction.notification={0} notification ManageJenkinsAction.notifications=One or more notifications
ManageJenkinsAction.notifications={0} notifications
MultiStageTimeSeries.EMPTY_STRING= MultiStageTimeSeries.EMPTY_STRING=
ParametersDefinitionProperty.BuildButtonText=Build ParametersDefinitionProperty.BuildButtonText=Build
Queue.AllNodesOffline=All nodes of label {0} are offline Queue.AllNodesOffline=All nodes of label {0} are offline

View File

@ -66,5 +66,3 @@ ShutdownLink.Description=Stops executing new builds, so that the system can be e
ShutdownLink.ShuttingDownInProgressDescription=Jenkins is currently shutting down. New builds are not executing. ShutdownLink.ShuttingDownInProgressDescription=Jenkins is currently shutting down. New builds are not executing.
ShutdownLink.ShutDownReason_title=Reason ShutdownLink.ShutDownReason_title=Reason
ShutdownLink.ShutDownReason_update=Update reason ShutdownLink.ShutDownReason_update=Update reason
AdministrativeMonitorsDecorator.DisplayName=Administrative Monitors Notifier

View File

@ -74,6 +74,3 @@ ShutdownLink.Description=\
# Configure tools, their locations and automatic installers. # Configure tools, their locations and automatic installers.
ConfigureTools.Description=\ ConfigureTools.Description=\
Настройване на инструментите, местоположенията и автоматичното инсталиране. Настройване на инструментите, местоположенията и автоматичното инсталиране.
# Administrative Monitors Notifier
AdministrativeMonitorsDecorator.DisplayName=\
Известия за предупреждения

View File

@ -49,5 +49,4 @@ NodesLink.Description=Knoten hinzufügen, entfernen, steuern und überwachen, au
CliLink.Description=Jenkins aus der Kommandozeile oder skriptgesteuert nutzen und verwalten. CliLink.Description=Jenkins aus der Kommandozeile oder skriptgesteuert nutzen und verwalten.
CliLink.DisplayName=Jenkins CLI CliLink.DisplayName=Jenkins CLI
SystemLogLink.DisplayName=Systemlog SystemLogLink.DisplayName=Systemlog
AdministrativeMonitorsDecorator.DisplayName=Anzeige aktiver Administrator-Warnungen
ConfigureTools.Description=Hilfsprogramme, ihre Installationsverzeichnisse und Installationsverfahren konfigurieren ConfigureTools.Description=Hilfsprogramme, ihre Installationsverzeichnisse und Installationsverfahren konfigurieren

View File

@ -21,8 +21,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE. # THE SOFTWARE.
AdministrativeMonitorsDecorator.DisplayName=Componente di notifica monitor \
amministrativi
CliLink.Description=Accedi/gestisci Jenkins dal terminale o da uno script. CliLink.Description=Accedi/gestisci Jenkins dal terminale o da uno script.
CliLink.DisplayName=Interfaccia a riga di comando di Jenkins CliLink.DisplayName=Interfaccia a riga di comando di Jenkins
ConfigureLink.Description=Configura le impostazioni e i percorsi globali. ConfigureLink.Description=Configura le impostazioni e i percorsi globali.

View File

@ -45,7 +45,6 @@ NodesLink.Description=Adiciona, remove, controla e monitora o vários nós
CliLink.DisplayName=Interface de Linha de Commando do Jenkins (CLI) CliLink.DisplayName=Interface de Linha de Commando do Jenkins (CLI)
ShutdownLink.DisplayName_update=Atualizar preparação de desligamento ShutdownLink.DisplayName_update=Atualizar preparação de desligamento
ShutdownLink.ShutDownReason_update=Atualizar razão ShutdownLink.ShutDownReason_update=Atualizar razão
AdministrativeMonitorsDecorator.DisplayName=Notificador de monitorações administrativas
ConfigureTools.Description=Configurar ferramentas, suas localizações e instaladores automáticos. ConfigureTools.Description=Configurar ferramentas, suas localizações e instaladores automáticos.
ShutdownLink.ShuttingDownInProgressDescription=O Jenkins está sendo desligado no momento. Novas construções não serão executadas. ShutdownLink.ShuttingDownInProgressDescription=O Jenkins está sendo desligado no momento. Novas construções não serão executadas.
ShutdownLink.ShutDownReason_title=Razão ShutdownLink.ShutDownReason_title=Razão

View File

@ -41,4 +41,3 @@ NodesLink.Description=Позволяет добавлять, удалять, к
PluginsLink.Description=Добавить, удалить, отключить или включить плагины, расширяющие функционональные возможности Jenkins. PluginsLink.Description=Добавить, удалить, отключить или включить плагины, расширяющие функционональные возможности Jenkins.
ConfigureTools.Description=Конфигурация инструментов, их расположение и автоматическая инсталяция. ConfigureTools.Description=Конфигурация инструментов, их расположение и автоматическая инсталяция.
SystemLogLink.DisplayName=Системный журнал SystemLogLink.DisplayName=Системный журнал
AdministrativeMonitorsDecorator.DisplayName=Системные уведомления

View File

@ -66,5 +66,3 @@ ShutdownLink.Description=Slutar köra nya byggen så att systemet eventuellt kan
ShutdownLink.ShuttingDownInProgressDescription=Jenkins stängs ned för tillfället. Nya byggen körs inte. ShutdownLink.ShuttingDownInProgressDescription=Jenkins stängs ned för tillfället. Nya byggen körs inte.
ShutdownLink.ShutDownReason_title=Anledning ShutdownLink.ShutDownReason_title=Anledning
ShutdownLink.ShutDownReason_update=Uppdatera anledning ShutdownLink.ShutDownReason_update=Uppdatera anledning
AdministrativeMonitorsDecorator.DisplayName=Avisering om administrativ övervakning

View File

@ -58,5 +58,3 @@ ShutdownLink.Description=不再執行新的建置作業,讓系統可以安全
ShutdownLink.ShuttingDownInProgressDescription=Jenkins 正在停機,不會執行新的建置作業。 ShutdownLink.ShuttingDownInProgressDescription=Jenkins 正在停機,不會執行新的建置作業。
ShutdownLink.ShutDownReason_title=原因 ShutdownLink.ShutDownReason_title=原因
ShutdownLink.ShutDownReason_update=更新原因 ShutdownLink.ShutDownReason_update=更新原因
AdministrativeMonitorsDecorator.DisplayName=管理監視器通知

View File

@ -32,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import hudson.ExtensionList; import hudson.ExtensionList;
import hudson.model.AdministrativeMonitor; import hudson.model.AdministrativeMonitor;
import hudson.model.ManageJenkinsAction;
import hudson.model.User; import hudson.model.User;
import hudson.security.ACL; import hudson.security.ACL;
import hudson.security.Permission; import hudson.security.Permission;
@ -40,6 +41,7 @@ import org.jenkinsci.Symbol;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.jvnet.hudson.test.For;
import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy; import org.jvnet.hudson.test.MockAuthorizationStrategy;
@ -47,6 +49,7 @@ import org.jvnet.hudson.test.TestExtension;
import org.jvnet.hudson.test.junit.jupiter.WithJenkins; import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
@WithJenkins @WithJenkins
@For(ManageJenkinsAction.class) // the name is historical
class AdministrativeMonitorsDecoratorTest { class AdministrativeMonitorsDecoratorTest {
private String managePermission; private String managePermission;
@ -68,8 +71,15 @@ class AdministrativeMonitorsDecoratorTest {
} }
} }
private void deleteOtherImpls(ExtensionList<AdministrativeMonitor> extensionList) {
extensionList.removeAll(extensionList.stream().filter(m -> m.getClass().getEnclosingClass() != AdministrativeMonitorsDecoratorTest.class).toList());
}
@Test @Test
void ensureAdminMonitorsAreNotRunPerNonAdminPage() throws Exception { void ensureAdminMonitorsAreNotRunPerNonAdminPage() throws Exception {
ExtensionList<AdministrativeMonitor> extensionList = j.jenkins.getExtensionList(AdministrativeMonitor.class);
deleteOtherImpls(extensionList);
j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
String nonAdminLogin = "nonAdmin"; String nonAdminLogin = "nonAdmin";
User.getById(nonAdminLogin, true); User.getById(nonAdminLogin, true);
@ -81,7 +91,6 @@ class AdministrativeMonitorsDecoratorTest {
JenkinsRule.WebClient wc = j.createWebClient(); JenkinsRule.WebClient wc = j.createWebClient();
wc.login(nonAdminLogin); wc.login(nonAdminLogin);
ExtensionList<AdministrativeMonitor> extensionList = j.jenkins.getExtensionList(AdministrativeMonitor.class);
ExecutionCounterNonSecAdministrativeMonitor nonSecCounter = extensionList.get(ExecutionCounterNonSecAdministrativeMonitor.class); ExecutionCounterNonSecAdministrativeMonitor nonSecCounter = extensionList.get(ExecutionCounterNonSecAdministrativeMonitor.class);
ExecutionCounterSecAdministrativeMonitor secCounter = extensionList.get(ExecutionCounterSecAdministrativeMonitor.class); ExecutionCounterSecAdministrativeMonitor secCounter = extensionList.get(ExecutionCounterSecAdministrativeMonitor.class);
@ -92,6 +101,9 @@ class AdministrativeMonitorsDecoratorTest {
@Test @Test
@Issue("JENKINS-63977") @Issue("JENKINS-63977")
void ensureAdminMonitorsAreRunOnlyOncePerAdminPage() throws Exception { void ensureAdminMonitorsAreRunOnlyOncePerAdminPage() throws Exception {
ExtensionList<AdministrativeMonitor> extensionList = j.jenkins.getExtensionList(AdministrativeMonitor.class);
deleteOtherImpls(extensionList);
j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
String adminLogin = "admin"; String adminLogin = "admin";
User.getById(adminLogin, true); User.getById(adminLogin, true);
@ -103,15 +115,40 @@ class AdministrativeMonitorsDecoratorTest {
JenkinsRule.WebClient wc = j.createWebClient(); JenkinsRule.WebClient wc = j.createWebClient();
wc.login(adminLogin); wc.login(adminLogin);
ExtensionList<AdministrativeMonitor> extensionList = j.jenkins.getExtensionList(AdministrativeMonitor.class);
ExecutionCounterNonSecAdministrativeMonitor nonSecCounter = extensionList.get(ExecutionCounterNonSecAdministrativeMonitor.class); ExecutionCounterNonSecAdministrativeMonitor nonSecCounter = extensionList.get(ExecutionCounterNonSecAdministrativeMonitor.class);
ExecutionCounterSecAdministrativeMonitor secCounter = extensionList.get(ExecutionCounterSecAdministrativeMonitor.class); ExecutionCounterSecAdministrativeMonitor secCounter = extensionList.get(ExecutionCounterSecAdministrativeMonitor.class);
assertEquals(1, nonSecCounter.count); assertEquals(0, nonSecCounter.count);
assertEquals(1, secCounter.count); assertEquals(1, secCounter.count);
} }
@TestExtension({"ensureAdminMonitorsAreNotRunPerNonAdminPage", "ensureAdminMonitorsAreRunOnlyOncePerAdminPage"}) @Test
void ensureOnlyOneAdminMonitorPerTypeIsRunPerAdminPage() throws Exception {
ExtensionList<AdministrativeMonitor> extensionList = j.jenkins.getExtensionList(AdministrativeMonitor.class);
deleteOtherImpls(extensionList);
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
String adminLogin = "admin";
User.getById(adminLogin, true);
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
.grant(Jenkins.ADMINISTER).everywhere().to(adminLogin)
);
JenkinsRule.WebClient wc = j.createWebClient();
wc.login(adminLogin);
ExecutionCounterNonSecAdministrativeMonitor nonSecCounter = extensionList.get(ExecutionCounterNonSecAdministrativeMonitor.class);
ExecutionCounterNonSecAdministrativeMonitor2 nonSecCounter2 = extensionList.get(ExecutionCounterNonSecAdministrativeMonitor2.class);
ExecutionCounterSecAdministrativeMonitor secCounter = extensionList.get(ExecutionCounterSecAdministrativeMonitor.class);
assertEquals(0, nonSecCounter.count);
assertEquals(0, nonSecCounter2.count);
assertEquals(1, secCounter.count);
}
@TestExtension({"ensureAdminMonitorsAreNotRunPerNonAdminPage", "ensureAdminMonitorsAreRunOnlyOncePerAdminPage", "ensureOnlyOneAdminMonitorPerTypeIsRunPerAdminPage"})
@Symbol("non_sec_counting") @Symbol("non_sec_counting")
public static class ExecutionCounterNonSecAdministrativeMonitor extends AdministrativeMonitor { public static class ExecutionCounterNonSecAdministrativeMonitor extends AdministrativeMonitor {
@ -134,7 +171,30 @@ class AdministrativeMonitorsDecoratorTest {
} }
} }
@TestExtension({"ensureAdminMonitorsAreNotRunPerNonAdminPage", "ensureAdminMonitorsAreRunOnlyOncePerAdminPage"}) @TestExtension("ensureOnlyOneAdminMonitorPerTypeIsRunPerAdminPage")
@Symbol("non_sec_counting_2")
public static class ExecutionCounterNonSecAdministrativeMonitor2 extends AdministrativeMonitor {
public int count = 0;
@Override
public String getDisplayName() {
return "NonSecCounter2";
}
@Override
public boolean isActivated() {
count++;
return true;
}
@Override
public boolean isSecurity() {
return false;
}
}
@TestExtension({"ensureAdminMonitorsAreNotRunPerNonAdminPage", "ensureAdminMonitorsAreRunOnlyOncePerAdminPage", "ensureOnlyOneAdminMonitorPerTypeIsRunPerAdminPage"})
@Symbol("sec_counting") @Symbol("sec_counting")
public static class ExecutionCounterSecAdministrativeMonitor extends AdministrativeMonitor { public static class ExecutionCounterSecAdministrativeMonitor extends AdministrativeMonitor {