Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions modules/admin-gui/resources/languages/languagefile.en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5194,11 +5194,23 @@ DATABASEMAINTENANCEWORKER = Database Maintenance Worker

DATABASEMAINTENANCEWORKERSETTINGS = Database Maintenance Worker Settings

DATABASEMAINTENANCEWORKER_DELETE_EXPIRED_CERTS = Delete Expired Certificates
DATABASEMAINTENANCEWORKER_DELETION_MODE_HEADER = Certificate deletion mode

DATABASEMAINTENANCEWORKER_DELETE_EXPIRED_CRLS = Delete Expired CRLs
DATABASEMAINTENANCEWORKER_PANEL_EXPIRED_HEADER = When "Delete expired certificates" is selected:

DATABASEMAINTENANCEWORKER_DELAY_AFTER_EXPIRATION = Delay After Expiration
DATABASEMAINTENANCEWORKER_PANEL_REVOKED_HEADER = When "Delete revoked certificates" is selected:

DATABASEMAINTENANCEWORKER_DELAY_AFTER_EXPIRATION = Delay after expiration

DATABASEMAINTENANCEWORKER_DELAY_AFTER_REVOCATION = Delay after revocation

DATABASEMAINTENANCEWORKER_REVOCATION_REASONS = Revocation reasons

DATABASEMAINTENANCEWORKER_REVOCATION_REASONS_HELP = Revocation reasons to match. Only certificates whose reason is in this set are reaped under "Delete revoked certificates" mode. Hold Ctrl (Cmd on macOS) to select multiple.

DATABASEMAINTENANCEWORKER_CRL_CRITERIA_HEADER = CRL match criteria

DATABASEMAINTENANCEWORKER_DELETE_EXPIRED_CRLS = Match expired CRLs

DATABASEMAINTENANCEWORKER_BATCH_SIZE = Entries to delete per run

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or any later version. *
* *
* See terms of license at gnu.org. *
* *
*************************************************************************/
-->
</ui:remove>
Expand Down Expand Up @@ -46,10 +44,43 @@
</h:selectManyListbox>
</h:panelGroup>

<!-- ============================================================== -->
<!-- Certificate deletion mode (radio). -->
<!-- Three mutually-exclusive choices. The sub-fields below for -->
<!-- each mode are always visible (no grey-out / no hide) but only -->
<!-- the selected mode's settings drive the worker at runtime. -->
<!-- ============================================================== -->
<h:panelGroup>
<h:outputText value="#{web.text.DATABASEMAINTENANCEWORKER_DELAY_AFTER_EXPIRATION}" />
<h:outputText value="#{web.text.DATABASEMAINTENANCEWORKER_DELETION_MODE_HEADER}"
style="font-weight:bold;" />
</h:panelGroup>
<h:panelGroup>&#xA0;</h:panelGroup>

<h:panelGroup>&#xA0;</h:panelGroup>
<h:panelGroup>
<h:selectOneRadio id="databaseMaintenanceCertDeletionMode"
value="#{editService.databaseMaintenanceWorkerType.certDeletionMode}"
layout="pageDirection"
style="font-size:14px;"
disabled="#{not editService.hasEditRights}">
<f:selectItems value="#{editService.databaseMaintenanceWorkerType.availableCertDeletionModes}" />
</h:selectOneRadio>
</h:panelGroup>

<!-- Yellow panel for MODE_EXPIRED: header sits ABOVE the Delay row,
in the right (field) column where it visually leads the sub-field. -->
<h:panelGroup styleClass="dbmsPanelCell dbmsPanelTop">
<h:outputText value="&#160;" escape="false" />
</h:panelGroup>
<h:panelGroup styleClass="dbmsPanelCell dbmsPanelTop">
<h:outputText value="#{web.text.DATABASEMAINTENANCEWORKER_PANEL_EXPIRED_HEADER}"
style="font-weight:700; color:#1f3a68; font-size:14px;" />
</h:panelGroup>

<h:panelGroup styleClass="dbmsPanelCell dbmsPanelBottom">
<h:outputText value="#{web.text.DATABASEMAINTENANCEWORKER_DELAY_AFTER_EXPIRATION}" />
</h:panelGroup>
<h:panelGroup styleClass="dbmsPanelCell dbmsPanelBottom">
<h:inputText id="databaseMaintenanceDelayValue"
value="#{editService.databaseMaintenanceWorkerType.delayTimeValue}" size="5"
title="#{web.text.FORMAT_INTEGER}"
Expand All @@ -63,14 +94,56 @@
</h:selectOneMenu>
</h:panelGroup>

<h:panelGroup>
<h:outputText value="#{web.text.DATABASEMAINTENANCEWORKER_DELETE_EXPIRED_CERTS}" />
<!-- Yellow panel for MODE_REVOKED: header sits ABOVE the Delay row,
in the right (field) column where it visually leads the sub-fields. -->
<h:panelGroup styleClass="dbmsPanelCell dbmsPanelTop">
<h:outputText value="&#160;" escape="false" />
</h:panelGroup>
<h:panelGroup styleClass="dbmsPanelCell dbmsPanelTop">
<h:outputText value="#{web.text.DATABASEMAINTENANCEWORKER_PANEL_REVOKED_HEADER}"
style="font-weight:700; color:#1f3a68; font-size:14px;" />
</h:panelGroup>

<h:panelGroup styleClass="dbmsPanelCell">
<h:outputText value="#{web.text.DATABASEMAINTENANCEWORKER_DELAY_AFTER_REVOCATION}" />
</h:panelGroup>
<h:panelGroup styleClass="dbmsPanelCell">
<h:inputText id="databaseMaintenanceRevokeDelayValue"
value="#{editService.databaseMaintenanceWorkerType.revokeDelayTimeValue}" size="5"
title="#{web.text.FORMAT_INTEGER}"
disabled="#{not editService.hasEditRights}">
<f:validateLongRange minimum="0"/>
</h:inputText>
<h:selectOneMenu id="databaseMaintenanceRevokeDelayUnitSelect"
value="#{editService.databaseMaintenanceWorkerType.revokeDelayTimeUnit}"
disabled="#{not editService.hasEditRights}">
<f:selectItems value="#{editService.notifyingType.availableUnits}" />
</h:selectOneMenu>
</h:panelGroup>

<h:panelGroup styleClass="dbmsPanelCell dbmsPanelBottom">
<h:outputText value="#{web.text.DATABASEMAINTENANCEWORKER_REVOCATION_REASONS}" />
</h:panelGroup>
<h:panelGroup styleClass="dbmsPanelCell dbmsPanelBottom">
<h:selectManyListbox id="databaseMaintenanceRevocationReasons"
value="#{editService.databaseMaintenanceWorkerType.selectedRevocationReasons}"
size="6"
title="#{web.text.DATABASEMAINTENANCEWORKER_REVOCATION_REASONS_HELP}"
disabled="#{!editService.hasEditRights}">
<f:selectItems
value="#{editService.databaseMaintenanceWorkerType.availableRevocationReasons}" />
</h:selectManyListbox>
</h:panelGroup>

<!-- ============================================================== -->
<!-- CRL match criteria — independent of cert deletion mode. -->
<!-- ============================================================== -->
<h:panelGroup>
<h:selectBooleanCheckbox id="databaseMaintenanceDeleteExpiredCerts"
value="#{editService.databaseMaintenanceWorkerType.deleteExpiredCertificates}"
disabled="#{!editService.hasEditRights}" />
<h:outputText value="#{web.text.DATABASEMAINTENANCEWORKER_CRL_CRITERIA_HEADER}"
style="font-weight:bold;" />
</h:panelGroup>
<h:panelGroup>&#xA0;</h:panelGroup>

<h:panelGroup>
<h:outputText value="#{web.text.DATABASEMAINTENANCEWORKER_DELETE_EXPIRED_CRLS}" />
</h:panelGroup>
Expand Down
2 changes: 1 addition & 1 deletion modules/admin-gui/resources/services/editservice.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@

<h:panelGrid styleClass="edit-bottom" width="100%" columns="2" rowClasses="Row0,Row1" columnClasses="editColumn1 label,editColumn2 field">
<h:panelGroup>
<h:outputText value="#{web.text.GENERALSETTINGS}" style="font-weight:bold;"/>
<h:outputText value="#{web.text.GENERALSETTINGS}" style="font-weight:bold;"/>
</h:panelGroup>
<h:panelGroup>&#xA0;</h:panelGroup>

Expand Down
22 changes: 22 additions & 0 deletions modules/admin-gui/resources/themes/default_theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,28 @@ table.edit tr:last-child td {
border-bottom: 3px solid #FEDD9A;
color: #A00;
}
/* Database Maintenance Worker — yellow panel for the selected radio mode's
* sub-fields. Two panels are emitted (one for MODE_EXPIRED, one for
* MODE_REVOKED); both stay visible in the live form with a header label
* telling the operator which mode the panel belongs to. The radio button
* determines which mode actually runs at tick time.
*
* Marker class .dbmsPanelCell goes on h:panelGroup in the field column of
* each panel row; the :has() rule then colors the parent <tr>'s last <td>
* directly. Modern browsers only (Safari 15.4+, Chrome 105+, Firefox 121+).
*/
table.edit-ctnd tr:has(.dbmsPanelCell) > td:last-child {
background-color: #FFF3D6;
padding-left: 30px;
}
/* Extra yellow margin at top of a panel (marker on first row). */
table.edit-ctnd tr:has(.dbmsPanelTop) > td:last-child {
padding-top: 14px;
}
/* Extra yellow margin at bottom of a panel (marker on last row). */
table.edit-ctnd tr:has(.dbmsPanelBottom) > td:last-child {
padding-bottom: 14px;
}
/* Titles */
table.edit-top tr.title td,
table.edit-bottom tr.title td,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@
*************************************************************************/
package org.ejbca.ui.web.admin.services.servicetypes;

import org.cesecore.certificates.crl.RevocationReasons;
import org.cesecore.util.PropertyTools;
import org.ejbca.core.model.services.workers.DatabaseMaintenanceWorkerConstants;

import jakarta.faces.model.SelectItem;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;

/**
Expand All @@ -30,10 +37,13 @@ public class DatabaseMaintenanceWorkerType extends BaseWorkerType {

private static final String WORKER_SUB_PAGE = "databasemaintenanceworker.xhtml";

private String certDeletionMode = DatabaseMaintenanceWorkerConstants.DEFAULT_CERT_DELETION_MODE;
private String delayTimeUnit = DatabaseMaintenanceWorkerConstants.DEFAULT_DELAY_TIMEUNIT;
private int delayTimeValue = DatabaseMaintenanceWorkerConstants.DEFAULT_DELAY_TIMEVALUE;
private boolean deleteExpiredCertificates = true;
private String revokeDelayTimeUnit = DatabaseMaintenanceWorkerConstants.DEFAULT_REVOKE_DELAY_TIMEUNIT;
private int revokeDelayTimeValue = DatabaseMaintenanceWorkerConstants.DEFAULT_REVOKE_DELAY_TIMEVALUE;
private boolean deleteExpiredCrls = true;
private String revocationReasons = DatabaseMaintenanceWorkerConstants.DEFAULT_REVOCATION_REASONS;
private int batchSize = DatabaseMaintenanceWorkerConstants.DEFAULT_BATCH_SIZE;

public DatabaseMaintenanceWorkerType() {
Expand All @@ -48,11 +58,21 @@ public DatabaseMaintenanceWorkerType() {
@Override
public Properties getProperties(final ArrayList<String> errorMessages) throws IOException {
Properties ret = super.getProperties(errorMessages);
ret.setProperty(DatabaseMaintenanceWorkerConstants.PROP_CERT_DELETION_MODE, certDeletionMode);
ret.setProperty(DatabaseMaintenanceWorkerConstants.PROP_DELAY_TIMEUNIT, delayTimeUnit);
ret.setProperty(DatabaseMaintenanceWorkerConstants.PROP_DELAY_TIMEVALUE, Integer.toString(delayTimeValue));
ret.setProperty(DatabaseMaintenanceWorkerConstants.PROP_DELETE_EXPIRED_CERTIFICATES, Boolean.toString(deleteExpiredCertificates));
ret.setProperty(DatabaseMaintenanceWorkerConstants.PROP_REVOKE_DELAY_TIMEUNIT, revokeDelayTimeUnit);
ret.setProperty(DatabaseMaintenanceWorkerConstants.PROP_REVOKE_DELAY_TIMEVALUE, Integer.toString(revokeDelayTimeValue));
ret.setProperty(DatabaseMaintenanceWorkerConstants.PROP_DELETE_EXPIRED_CRLS, Boolean.toString(deleteExpiredCrls));
ret.setProperty(DatabaseMaintenanceWorkerConstants.PROP_REVOCATION_REASONS, revocationReasons != null ? revocationReasons : "");
ret.setProperty(DatabaseMaintenanceWorkerConstants.PROP_BATCH_SIZE, Integer.toString(batchSize));
// Sync the legacy boolean flags from the radio mode so older
// worker code paths and downstream tools that read the booleans
// see a consistent picture.
ret.setProperty(DatabaseMaintenanceWorkerConstants.PROP_DELETE_EXPIRED_CERTIFICATES,
Boolean.toString(DatabaseMaintenanceWorkerConstants.MODE_EXPIRED.equals(certDeletionMode)));
ret.setProperty(DatabaseMaintenanceWorkerConstants.PROP_DELETE_REVOKED_CERTIFICATES,
Boolean.toString(DatabaseMaintenanceWorkerConstants.MODE_REVOKED.equals(certDeletionMode)));
return ret;
}

Expand All @@ -61,9 +81,44 @@ public void setProperties(final Properties properties) throws IOException {
super.setProperties(properties);
delayTimeValue = PropertyTools.get(properties, DatabaseMaintenanceWorkerConstants.PROP_DELAY_TIMEVALUE, delayTimeValue);
delayTimeUnit = properties.getProperty(DatabaseMaintenanceWorkerConstants.PROP_DELAY_TIMEUNIT, delayTimeUnit);
deleteExpiredCertificates = PropertyTools.get(properties, DatabaseMaintenanceWorkerConstants.PROP_DELETE_EXPIRED_CERTIFICATES, deleteExpiredCertificates);
revokeDelayTimeValue = PropertyTools.get(properties, DatabaseMaintenanceWorkerConstants.PROP_REVOKE_DELAY_TIMEVALUE, revokeDelayTimeValue);
revokeDelayTimeUnit = properties.getProperty(DatabaseMaintenanceWorkerConstants.PROP_REVOKE_DELAY_TIMEUNIT, revokeDelayTimeUnit);
deleteExpiredCrls = PropertyTools.get(properties, DatabaseMaintenanceWorkerConstants.PROP_DELETE_EXPIRED_CRLS, deleteExpiredCrls);
revocationReasons = properties.getProperty(DatabaseMaintenanceWorkerConstants.PROP_REVOCATION_REASONS, revocationReasons);
batchSize = PropertyTools.get(properties, DatabaseMaintenanceWorkerConstants.PROP_BATCH_SIZE, batchSize);
// Resolve cert-deletion mode: explicit property wins; otherwise
// derive from the legacy boolean flags for backward compatibility.
final String explicitMode = properties.getProperty(DatabaseMaintenanceWorkerConstants.PROP_CERT_DELETION_MODE);
if (explicitMode != null && !explicitMode.trim().isEmpty()) {
certDeletionMode = explicitMode.trim();
} else if (PropertyTools.get(properties, DatabaseMaintenanceWorkerConstants.PROP_DELETE_EXPIRED_CERTIFICATES, false)) {
certDeletionMode = DatabaseMaintenanceWorkerConstants.MODE_EXPIRED;
} else if (PropertyTools.get(properties, DatabaseMaintenanceWorkerConstants.PROP_DELETE_REVOKED_CERTIFICATES, false)) {
certDeletionMode = DatabaseMaintenanceWorkerConstants.MODE_REVOKED;
}
// else: keep the existing default (MODE_NONE)
}

public String getCertDeletionMode() {
return certDeletionMode;
}

public void setCertDeletionMode(final String certDeletionMode) {
this.certDeletionMode = certDeletionMode;
}

/**
* JSF accessor — the available radio-button options for cert deletion mode.
*/
public List<SelectItem> getAvailableCertDeletionModes() {
final List<SelectItem> items = new ArrayList<>();
items.add(new SelectItem(DatabaseMaintenanceWorkerConstants.MODE_EXPIRED,
"Delete expired certificates (ELT: E + R)"));
items.add(new SelectItem(DatabaseMaintenanceWorkerConstants.MODE_REVOKED,
"Delete revoked certificates (ELT: r + R)"));
items.add(new SelectItem(DatabaseMaintenanceWorkerConstants.MODE_NONE,
"None (CRL deletions only)"));
return items;
}

public String getDelayTimeUnit() {
Expand All @@ -82,12 +137,20 @@ public void setDelayTimeValue(final int delayTimeValue) {
this.delayTimeValue = delayTimeValue;
}

public boolean isDeleteExpiredCertificates() {
return deleteExpiredCertificates;
public String getRevokeDelayTimeUnit() {
return revokeDelayTimeUnit;
}

public void setRevokeDelayTimeUnit(final String revokeDelayTimeUnit) {
this.revokeDelayTimeUnit = revokeDelayTimeUnit;
}

public int getRevokeDelayTimeValue() {
return revokeDelayTimeValue;
}

public void setDeleteExpiredCertificates(final boolean deleteExpiredCertificates) {
this.deleteExpiredCertificates = deleteExpiredCertificates;
public void setRevokeDelayTimeValue(final int revokeDelayTimeValue) {
this.revokeDelayTimeValue = revokeDelayTimeValue;
}

public boolean isDeleteExpiredCrls() {
Expand All @@ -98,6 +161,65 @@ public void setDeleteExpiredCrls(final boolean deleteExpiredCrls) {
this.deleteExpiredCrls = deleteExpiredCrls;
}

public String getRevocationReasons() {
return revocationReasons;
}

public void setRevocationReasons(final String revocationReasons) {
this.revocationReasons = revocationReasons;
}

/**
* JSF accessor — current selection for the multi-select listbox.
*
* <p>Backed by the same comma-separated {@link #revocationReasons} string
* the worker reads, so values set via the GUI listbox and values set via
* {@code ejbca.sh service edit worker.revocationReasons=SUPERSEDED,...}
* round-trip through the same property without conversion.
*/
public List<String> getSelectedRevocationReasons() {
if (revocationReasons == null || revocationReasons.isEmpty()) {
return Collections.emptyList();
}
return Arrays.asList(revocationReasons.split("\\s*,\\s*"));
}

public void setSelectedRevocationReasons(final List<String> selected) {
if (selected == null || selected.isEmpty()) {
this.revocationReasons = "";
} else {
// Preserve order, deduplicate.
final LinkedHashSet<String> unique = new LinkedHashSet<>(selected);
this.revocationReasons = String.join(",", unique);
}
}

/**
* JSF accessor — the available revocation reasons to show in the listbox.
*
* <p>Matches the curated {@code reasonableRevocationReasons} set already
* defined in {@link RevocationReasons} (excludes NOT_REVOKED, both
* CA-compromise variants, CERTIFICATE_HOLD, and REMOVE_FROM_CRL, which
* aren't meaningful as "delete revoked certs" filter criteria for an
* operator scheduling a cleanup job). Each {@link SelectItem} uses the
* RFC 5280 string form (e.g. "SUPERSEDED") as the value and the
* enum's {@code humanReadable} label as the display text.
*/
public List<SelectItem> getAvailableRevocationReasons() {
final List<SelectItem> items = new ArrayList<>();
for (final RevocationReasons r : new RevocationReasons[] {
RevocationReasons.UNSPECIFIED,
RevocationReasons.KEYCOMPROMISE,
RevocationReasons.AFFILIATIONCHANGED,
RevocationReasons.SUPERSEDED,
RevocationReasons.CESSATIONOFOPERATION,
RevocationReasons.PRIVILEGESWITHDRAWN,
}) {
items.add(new SelectItem(r.getStringValue(), r.getHumanReadable()));
}
return items;
}

public int getBatchSize() {
return batchSize;
}
Expand Down
Loading