Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
defbedc
[SYNCOPE-1901] Implement PasswordModule for CAS password management i…
Aug 14, 2025
9817a35
[SYNCOPE-1901] Implement Neo4j persistence for PasswordModule in Syncope
Aug 14, 2025
24673c5
[SYNCOPE-1901] Unit tests and IT tests for PasswordModule. Only imple…
Aug 14, 2025
4bd557a
[SYNCOPE-1901] Implemented the UI for configuring password management…
Aug 18, 2025
78cdd06
[SYNCOPE-1901] Adding LDAPPasswordModuleConf to configure CAS passwor…
Aug 18, 2025
ec3c4ca
[SYNCOPE-1901] Checkstyle
Aug 18, 2025
8c1a0de
[SYNCOPE-1901] Adding JDBCPasswordModuleConf to configure CAS passwor…
Aug 21, 2025
13d817d
[SYNCOPE-1901] Add PasswordModule implementations and unit/IT tests f…
Aug 21, 2025
55d131e
[SYNCOPE-1901] allow defining a single instance of passwordChangeServ…
Aug 27, 2025
e7ef873
[SYNCOPE-1901] Fix PasswordModuleState for Neo4j persistence
Aug 29, 2025
9537135
Merge branch 'master' into SYNCOPE-1901
Aug 29, 2025
d634cc7
[SYNCOPE-1901] Refactoring PasswordModule to PasswordManagement
Sep 2, 2025
fd63810
[SYNCOPE-1901] Definition of a validator that checks if multiple pass…
Sep 4, 2025
fe7e34f
[SYNCOPE-1901] Add password management configuration for REST source
Sep 4, 2025
17f2d14
[SYNCOPE-1901] Update apache syncope documentation
Sep 4, 2025
40b5930
Merge branch 'master' into SYNCOPE-1901
Sep 4, 2025
46a603e
[SYNCOPE-1901] Checkstyle
Sep 4, 2025
74100e9
[SYNCOPE-1901] Fix CodeQL issues: remove unused parameter and replace…
Sep 4, 2025
a16fa80
[SYNCOPE-1901] Fix unresolved comments
Sep 5, 2025
a53d441
[SYNCOPE-1901] Restore ActionsPanel properties
Sep 5, 2025
542acda
Merge remote-tracking branch 'upstream/master' into SYNCOPE-1901
Oct 14, 2025
9fd496e
[SYNCOPE-1901] Reflow
Oct 14, 2025
8ae6aa4
[SYNCOPE-1901] Reflow
Oct 14, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.apache.syncope.client.console.rest.AuthProfileRestClient;
import org.apache.syncope.client.console.rest.ClientAppRestClient;
import org.apache.syncope.client.console.rest.OIDCJWKSRestClient;
import org.apache.syncope.client.console.rest.PasswordManagementRestClient;
import org.apache.syncope.client.console.rest.PolicyRestClient;
import org.apache.syncope.client.console.rest.SAML2IdPEntityRestClient;
import org.apache.syncope.client.console.rest.SRARouteRestClient;
Expand Down Expand Up @@ -63,6 +64,12 @@ public AccessPolicyConfProvider accessPolicyConfProvider() {
return new AMAccessPolicyConfProvider();
}

@ConditionalOnMissingBean
@Bean
public AuthModuleRestClient authModuleRestClient() {
return new AuthModuleRestClient();
}

@ConditionalOnMissingBean
@Bean
public AttrRepoRestClient attrRepoRestClient() {
Expand All @@ -71,8 +78,8 @@ public AttrRepoRestClient attrRepoRestClient() {

@ConditionalOnMissingBean
@Bean
public AuthModuleRestClient authModuleRestClient() {
return new AuthModuleRestClient();
public PasswordManagementRestClient passwordManagementRestClient() {
return new PasswordManagementRestClient();
}

@ConditionalOnMissingBean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.syncope.common.lib.attr.AttrRepoConf;
import org.apache.syncope.common.lib.auth.AuthModuleConf;
import org.apache.syncope.common.lib.clientapps.UsernameAttributeProviderConf;
import org.apache.syncope.common.lib.password.PasswordManagementConf;
import org.apache.syncope.common.lib.policy.AccessPolicyConf;
import org.apache.syncope.common.lib.policy.AttrReleasePolicyConf;
import org.apache.syncope.common.lib.policy.AuthPolicyConf;
Expand All @@ -36,6 +37,7 @@ public class AMClassPathScanImplementationContributor implements ClassPathScanIm
public void extend(final ClassPathScanningCandidateComponentProvider scanner) {
scanner.addIncludeFilter(new AssignableTypeFilter(AuthModuleConf.class));
scanner.addIncludeFilter(new AssignableTypeFilter(AttrRepoConf.class));
scanner.addIncludeFilter(new AssignableTypeFilter(PasswordManagementConf.class));
scanner.addIncludeFilter(new AssignableTypeFilter(AccessPolicyConf.class));
scanner.addIncludeFilter(new AssignableTypeFilter(AttrReleasePolicyConf.class));
scanner.addIncludeFilter(new AssignableTypeFilter(AuthPolicyConf.class));
Expand All @@ -50,6 +52,9 @@ public Optional<String> getLabel(final Class<?> clazz) {
if (AttrRepoConf.class.isAssignableFrom(clazz)) {
return Optional.of(AttrRepoConf.class.getName());
}
if (PasswordManagementConf.class.isAssignableFrom(clazz)) {
return Optional.of(PasswordManagementConf.class.getName());
}
if (AccessPolicyConf.class.isAssignableFrom(clazz)) {
return Optional.of(AccessPolicyConf.class.getName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@
import org.apache.syncope.client.console.panels.AttrRepoDirectoryPanel;
import org.apache.syncope.client.console.panels.AuthModuleDirectoryPanel;
import org.apache.syncope.client.console.panels.OIDC;
import org.apache.syncope.client.console.panels.PasswordManagementDirectoryPanel;
import org.apache.syncope.client.console.panels.SAML2IdPEntityDirectoryPanel;
import org.apache.syncope.client.console.panels.WAConfigDirectoryPanel;
import org.apache.syncope.client.console.panels.WAPushModalPanel;
import org.apache.syncope.client.console.rest.AttrRepoRestClient;
import org.apache.syncope.client.console.rest.AuthModuleRestClient;
import org.apache.syncope.client.console.rest.AuthProfileRestClient;
import org.apache.syncope.client.console.rest.ClientAppRestClient;
import org.apache.syncope.client.console.rest.PasswordManagementRestClient;
import org.apache.syncope.client.console.rest.SAML2IdPEntityRestClient;
import org.apache.syncope.client.console.rest.WAConfigRestClient;
import org.apache.syncope.client.console.rest.WASessionRestClient;
Expand Down Expand Up @@ -85,6 +87,9 @@ public class WA extends BasePage {
@SpringBean
protected AttrRepoRestClient attrRepoRestClient;

@SpringBean
protected PasswordManagementRestClient passwordManagementRestClient;

@SpringBean
protected SAML2IdPEntityRestClient saml2IdPEntityRestClient;

Expand Down Expand Up @@ -195,6 +200,19 @@ public Panel getPanel(final String panelId) {
});
}

if (SyncopeConsoleSession.get().owns(AMEntitlement.PASSWORD_MANAGEMENT_LIST)) {
tabs.add(new AbstractTab(new ResourceModel("passwordManagements")) {

private static final long serialVersionUID = 5211692813425391144L;

@Override
public Panel getPanel(final String panelId) {
return new PasswordManagementDirectoryPanel(
panelId, passwordManagementRestClient, getPageReference());
}
});
}

if (SyncopeConsoleSession.get().owns(AMEntitlement.CLIENTAPP_LIST)) {
tabs.add(new AbstractTab(new ResourceModel("clientApps")) {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.client.console.panels;

import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.client.console.SyncopeConsoleSession;
import org.apache.syncope.client.console.audit.AuditHistoryModal;
import org.apache.syncope.client.console.commons.AMConstants;
import org.apache.syncope.client.console.commons.DirectoryDataProvider;
import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
import org.apache.syncope.client.console.pages.BasePage;
import org.apache.syncope.client.console.panels.PasswordManagementDirectoryPanel.PasswordManagementProvider;
import org.apache.syncope.client.console.rest.AuditRestClient;
import org.apache.syncope.client.console.rest.PasswordManagementRestClient;
import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
import org.apache.syncope.client.console.wizards.PasswordManagementWizardBuilder;
import org.apache.syncope.client.ui.commons.Constants;
import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
import org.apache.syncope.common.lib.to.PasswordManagementTO;
import org.apache.syncope.common.lib.types.AMEntitlement;
import org.apache.syncope.common.lib.types.IdRepoEntitlement;
import org.apache.syncope.common.lib.types.OpEvent;
import org.apache.wicket.PageReference;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
import org.apache.wicket.event.Broadcast;
import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.spring.injection.annot.SpringBean;

public class PasswordManagementDirectoryPanel extends DirectoryPanel<
PasswordManagementTO, PasswordManagementTO, PasswordManagementProvider, PasswordManagementRestClient> {

private static final long serialVersionUID = 1005345990563741296L;

@SpringBean
protected AuditRestClient auditRestClient;

protected final BaseModal<Serializable> historyModal;

public PasswordManagementDirectoryPanel(
final String id,
final PasswordManagementRestClient restClient,
final PageReference pageRef) {

super(id, restClient, pageRef);

disableCheckBoxes();

addNewItemPanelBuilder(new PasswordManagementWizardBuilder(
new PasswordManagementTO(), restClient, pageRef), true);

MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, AMEntitlement.AUTH_MODULE_CREATE);

modal.size(Modal.Size.Extra_large);
initResultTable();

historyModal = new BaseModal<>(Constants.OUTER);
historyModal.size(Modal.Size.Large);
addOuterObject(historyModal);
}

@Override
protected PasswordManagementProvider dataProvider() {
return new PasswordManagementProvider(rows);
}

@Override
protected String paginatorRowsKey() {
return AMConstants.PREF_AUTHMODULE_PAGINATOR_ROWS;
}

@Override
protected Collection<ActionLink.ActionType> getBatches() {
return List.of();
}

@Override
protected List<IColumn<PasswordManagementTO, String>> getColumns() {
List<IColumn<PasswordManagementTO, String>> columns = new ArrayList<>();
columns.add(new PropertyColumn<>(
new StringResourceModel(Constants.KEY_FIELD_NAME, this),
Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME));
columns.add(new PropertyColumn<>(new ResourceModel(Constants.DESCRIPTION_FIELD_NAME),
Constants.DESCRIPTION_FIELD_NAME, Constants.DESCRIPTION_FIELD_NAME));
columns.add(new PropertyColumn<>(new ResourceModel("type"), "conf") {

private static final long serialVersionUID = -1822504503325964706L;

@Override
public void populateItem(
final Item<ICellPopulator<PasswordManagementTO>> item,
final String componentId,
final IModel<PasswordManagementTO> rowModel) {

item.add(new Label(componentId, rowModel.getObject().getConf() == null
? StringUtils.EMPTY
: StringUtils.substringBefore(
rowModel.getObject().getConf().getClass().getSimpleName(), "PasswordManagementConf")));
}
});
columns.add(new BooleanPropertyColumn<>(
new StringResourceModel("enabled", this),
"enabled",
"enabled"));
return columns;
}

@Override
public ActionsPanel<PasswordManagementTO> getActions(final IModel<PasswordManagementTO> model) {
ActionsPanel<PasswordManagementTO> panel = super.getActions(model);

panel.add(new ActionLink<>() {

private static final long serialVersionUID = -3722207913631435501L;

@Override
public void onClick(final AjaxRequestTarget target, final PasswordManagementTO ignore) {
send(PasswordManagementDirectoryPanel.this, Broadcast.EXACT, new AjaxWizard.EditItemActionEvent<>(
restClient.read(model.getObject().getKey()), target));
}
}, ActionLink.ActionType.EDIT, AMEntitlement.PASSWORD_MANAGEMENT_UPDATE);

panel.add(new ActionLink<>() {

private static final long serialVersionUID = -3722207913631435501L;

@Override
public void onClick(final AjaxRequestTarget target, final PasswordManagementTO ignore) {
try {
model.setObject(restClient.read(model.getObject().getKey()));
boolean enabled = model.getObject().isEnabled();
model.getObject().setEnabled(!enabled);
restClient.update(model.getObject());

SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
target.add(container);
} catch (Exception e) {
LOG.error("While actioning object {}", model.getObject().getKey(), e);
SyncopeConsoleSession.get().onException(e);
}
((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
}
}, ActionLink.ActionType.ENABLE, AMEntitlement.PASSWORD_MANAGEMENT_UPDATE);

panel.add(new ActionLink<>() {

private static final long serialVersionUID = -5432034353017728756L;

@Override
public void onClick(final AjaxRequestTarget target, final PasswordManagementTO ignore) {
model.setObject(restClient.read(model.getObject().getKey()));

target.add(historyModal.setContent(new AuditHistoryModal<>(
OpEvent.CategoryType.LOGIC,
"PasswordManagementLogic",
model.getObject(),
AMEntitlement.PASSWORD_MANAGEMENT_UPDATE,
auditRestClient) {

private static final long serialVersionUID = -3712506022627033822L;

@Override
protected void restore(final String json, final AjaxRequestTarget target) {
try {
PasswordManagementTO updated = MAPPER.readValue(json, PasswordManagementTO.class);
restClient.update(updated);

SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
} catch (Exception e) {
LOG.error("While restoring AuthModule {}", model.getObject().getKey(), e);
SyncopeConsoleSession.get().onException(e);
}
((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
}
}));

historyModal.header(new Model<>(getString("auditHistory.title", new Model<>(model.getObject()))));

historyModal.show(true);
}
}, ActionLink.ActionType.VIEW_AUDIT_HISTORY, String.format("%s,%s", AMEntitlement.PASSWORD_MANAGEMENT_READ,
IdRepoEntitlement.AUDIT_LIST));

panel.add(new ActionLink<>() {

private static final long serialVersionUID = -3722207913631435501L;

@Override
public void onClick(final AjaxRequestTarget target, final PasswordManagementTO ignore) {
try {
restClient.delete(model.getObject().getKey());

SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
target.add(container);
} catch (Exception e) {
LOG.error("While deleting {}", model.getObject().getKey(), e);
SyncopeConsoleSession.get().onException(e);
}
((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
}
}, ActionLink.ActionType.DELETE, AMEntitlement.PASSWORD_MANAGEMENT_DELETE, true);

return panel;
}

protected final class PasswordManagementProvider extends DirectoryDataProvider<PasswordManagementTO> {

private static final long serialVersionUID = 4972693840864025955L;

private final SortableDataProviderComparator<PasswordManagementTO> comparator;

private PasswordManagementProvider(final int paginatorRows) {
super(paginatorRows);
comparator = new SortableDataProviderComparator<>(this);
}

@Override
public Iterator<PasswordManagementTO> iterator(final long first, final long count) {
List<PasswordManagementTO> passwordManagements = restClient.list();
passwordManagements.sort(comparator);
return passwordManagements.subList((int) first, (int) first + (int) count).iterator();
}

@Override
public long size() {
return restClient.list().size();
}

@Override
public IModel<PasswordManagementTO> model(final PasswordManagementTO object) {
return new CompoundPropertyModel<>(object);
}
}
}
Loading
Loading