diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/AMConsoleContext.java b/client/am/console/src/main/java/org/apache/syncope/client/console/AMConsoleContext.java index 8d8b53f3bc3..0bbfee1e3b8 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/AMConsoleContext.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/AMConsoleContext.java @@ -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; @@ -63,6 +64,12 @@ public AccessPolicyConfProvider accessPolicyConfProvider() { return new AMAccessPolicyConfProvider(); } + @ConditionalOnMissingBean + @Bean + public AuthModuleRestClient authModuleRestClient() { + return new AuthModuleRestClient(); + } + @ConditionalOnMissingBean @Bean public AttrRepoRestClient attrRepoRestClient() { @@ -71,8 +78,8 @@ public AttrRepoRestClient attrRepoRestClient() { @ConditionalOnMissingBean @Bean - public AuthModuleRestClient authModuleRestClient() { - return new AuthModuleRestClient(); + public PasswordManagementRestClient passwordManagementRestClient() { + return new PasswordManagementRestClient(); } @ConditionalOnMissingBean diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationContributor.java b/client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationContributor.java index f995e5de2ad..c5c00fec217 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationContributor.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationContributor.java @@ -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; @@ -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)); @@ -50,6 +52,9 @@ public Optional 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()); } diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java b/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java index 39b0bd8562f..944dc94d3fe 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java @@ -39,6 +39,7 @@ 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; @@ -46,6 +47,7 @@ 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; @@ -85,6 +87,9 @@ public class WA extends BasePage { @SpringBean protected AttrRepoRestClient attrRepoRestClient; + @SpringBean + protected PasswordManagementRestClient passwordManagementRestClient; + @SpringBean protected SAML2IdPEntityRestClient saml2IdPEntityRestClient; @@ -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")) { diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel.java b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel.java new file mode 100644 index 00000000000..21bca4d97a8 --- /dev/null +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel.java @@ -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 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 getBatches() { + return List.of(); + } + + @Override + protected List> getColumns() { + List> 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> item, + final String componentId, + final IModel 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 getActions(final IModel model) { + ActionsPanel 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 { + + private static final long serialVersionUID = 4972693840864025955L; + + private final SortableDataProviderComparator comparator; + + private PasswordManagementProvider(final int paginatorRows) { + super(paginatorRows); + comparator = new SortableDataProviderComparator<>(this); + } + + @Override + public Iterator iterator(final long first, final long count) { + List 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 model(final PasswordManagementTO object) { + return new CompoundPropertyModel<>(object); + } + } +} diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/PasswordManagementRestClient.java b/client/am/console/src/main/java/org/apache/syncope/client/console/rest/PasswordManagementRestClient.java new file mode 100644 index 00000000000..667d13e8b7a --- /dev/null +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/rest/PasswordManagementRestClient.java @@ -0,0 +1,48 @@ +/* + * 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.rest; + +import java.util.List; +import org.apache.syncope.common.lib.to.PasswordManagementTO; +import org.apache.syncope.common.rest.api.service.PasswordManagementService; + +public class PasswordManagementRestClient extends BaseRestClient { + + private static final long serialVersionUID = -3961377654788868099L; + + public List list() { + return getService(PasswordManagementService.class).list(); + } + + public void create(final PasswordManagementTO passwordManagementTO) { + getService(PasswordManagementService.class).create(passwordManagementTO); + } + + public PasswordManagementTO read(final String key) { + return getService(PasswordManagementService.class).read(key); + } + + public void update(final PasswordManagementTO passwordManagementTO) { + getService(PasswordManagementService.class).update(passwordManagementTO); + } + + public void delete(final String key) { + getService(PasswordManagementService.class).delete(key); + } +} diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder.java b/client/am/console/src/main/java/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder.java new file mode 100644 index 00000000000..54f459ee531 --- /dev/null +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder.java @@ -0,0 +1,159 @@ +/* + * 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.wizards; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.syncope.client.console.SyncopeWebApplication; +import org.apache.syncope.client.console.panels.BeanPanel; +import org.apache.syncope.client.console.rest.PasswordManagementRestClient; +import org.apache.syncope.client.ui.commons.Constants; +import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel; +import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel; +import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel; +import org.apache.syncope.client.ui.commons.wizards.AjaxWizard; +import org.apache.syncope.common.lib.password.PasswordManagementConf; +import org.apache.syncope.common.lib.to.PasswordManagementTO; +import org.apache.wicket.PageReference; +import org.apache.wicket.ajax.AjaxEventBehavior; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.wizard.WizardModel; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.model.LoadableDetachableModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.PropertyModel; +import org.springframework.util.ClassUtils; + +public class PasswordManagementWizardBuilder extends BaseAjaxWizardBuilder { + + private static final long serialVersionUID = -6355254150868269721L; + + protected final LoadableDetachableModel> passwordManagementConfs = new LoadableDetachableModel<>() { + + private static final long serialVersionUID = 5275935387613157437L; + + @Override + protected List load() { + return SyncopeWebApplication.get().getLookup().getClasses(PasswordManagementConf.class).stream(). + map(Class::getName).sorted().collect(Collectors.toList()); + } + }; + + protected final PasswordManagementRestClient passwordManagementRestClient; + + protected final Model> passwordManagementConfClass = Model.of(); + + public PasswordManagementWizardBuilder( + final PasswordManagementTO defaultItem, + final PasswordManagementRestClient passwordManagementRestClient, + final PageReference pageRef) { + + super(defaultItem, pageRef); + this.passwordManagementRestClient = passwordManagementRestClient; + } + + @Override + protected Serializable onApplyInternal(final PasswordManagementTO modelObject) { + if (mode == AjaxWizard.Mode.CREATE) { + passwordManagementRestClient.create(modelObject); + } else { + passwordManagementRestClient.update(modelObject); + } + + return modelObject; + } + + @Override + protected WizardModel buildModelSteps(final PasswordManagementTO modelObject, final WizardModel wizardModel) { + wizardModel.add(new Profile(modelObject, passwordManagementConfs, passwordManagementConfClass)); + wizardModel.add(new Configuration(modelObject)); + return wizardModel; + } + + protected static class Profile extends WizardStep { + + private static final long serialVersionUID = -3043839139187792810L; + + Profile( + final PasswordManagementTO passwordManagement, + final LoadableDetachableModel> passwordManagementConfs, + final Model> passwordManagementConfClass) { + + boolean isNew = passwordManagement.getConf() == null; + if (!isNew) { + passwordManagementConfClass.setObject(passwordManagement.getConf().getClass()); + } + + AjaxTextFieldPanel key = new AjaxTextFieldPanel( + Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME, + new PropertyModel<>(passwordManagement, Constants.KEY_FIELD_NAME)); + key.addRequiredLabel(); + key.setEnabled(isNew); + add(key); + + AjaxTextFieldPanel description = new AjaxTextFieldPanel( + Constants.DESCRIPTION_FIELD_NAME, getString(Constants.DESCRIPTION_FIELD_NAME), + new PropertyModel<>(passwordManagement, Constants.DESCRIPTION_FIELD_NAME)); + add(description); + + AjaxCheckBoxPanel isEnabled = new AjaxCheckBoxPanel( + "enabled", "enabled", new PropertyModel<>(passwordManagement, "enabled")); + add(isEnabled); + + AjaxDropDownChoicePanel conf = new AjaxDropDownChoicePanel<>("conf", getString("type"), isNew + ? Model.of() + : Model.of(passwordManagement.getConf().getClass().getName())); + conf.setChoices(passwordManagementConfs.getObject()); + conf.addRequiredLabel(); + conf.setNullValid(false); + conf.setEnabled(isNew); + conf.add(new AjaxEventBehavior(Constants.ON_CHANGE) { + + private static final long serialVersionUID = -7133385027739964990L; + + @SuppressWarnings("unchecked") + @Override + protected void onEvent(final AjaxRequestTarget target) { + try { + Class clazz = + (Class) ClassUtils.resolveClassName( + conf.getModelObject(), ClassUtils.getDefaultClassLoader()); + + passwordManagement.setConf(clazz.getConstructor().newInstance()); + passwordManagementConfClass.setObject(clazz); + } catch (Exception e) { + LOG.error("Cannot instantiate {}", conf.getModelObject(), e); + } + } + }); + add(conf); + } + } + + protected class Configuration extends WizardStep { + + private static final long serialVersionUID = -785981096328637758L; + + Configuration(final PasswordManagementTO passwordManagement) { + add(new BeanPanel<>("bean", new PropertyModel<>(passwordManagement, "conf"), pageRef, + "ldap", "keystore", "serviceProviderMetadata").setRenderBodyOnly(true)); + } + } +} diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA.properties index cdd3184c2bf..fe17d6fa4fd 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA.properties +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA.properties @@ -16,6 +16,7 @@ # under the License. wa=WA authModules=Authentication Modules +passwordManagements=Password Management clientApps=Client Applications config=Parameters sessions=Sessions diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_fr_CA.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_fr_CA.properties index cdd3184c2bf..fe17d6fa4fd 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_fr_CA.properties +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_fr_CA.properties @@ -16,6 +16,7 @@ # under the License. wa=WA authModules=Authentication Modules +passwordManagements=Password Management clientApps=Client Applications config=Parameters sessions=Sessions diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_it.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_it.properties index 672aa66750e..561eca964d2 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_it.properties +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_it.properties @@ -16,6 +16,7 @@ # under the License. wa=WA authModules=Moduli di Autenticazione +passwordManagements=Gestione Password clientApps=Applicazioni Client config=Parametri sessions=Sessioni diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_ja.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_ja.properties index cdd3184c2bf..fe17d6fa4fd 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_ja.properties +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_ja.properties @@ -16,6 +16,7 @@ # under the License. wa=WA authModules=Authentication Modules +passwordManagements=Password Management clientApps=Client Applications config=Parameters sessions=Sessions diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_pt_BR.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_pt_BR.properties index cdd3184c2bf..fe17d6fa4fd 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_pt_BR.properties +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_pt_BR.properties @@ -16,6 +16,7 @@ # under the License. wa=WA authModules=Authentication Modules +passwordManagements=Password Management clientApps=Client Applications config=Parameters sessions=Sessions diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_ru.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_ru.properties index cdd3184c2bf..fe17d6fa4fd 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_ru.properties +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/pages/WA_ru.properties @@ -16,6 +16,7 @@ # under the License. wa=WA authModules=Authentication Modules +passwordManagements=Password Management clientApps=Client Applications config=Parameters sessions=Sessions diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel.properties new file mode 100644 index 00000000000..acbd8a9ef84 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel.properties @@ -0,0 +1,23 @@ +# 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. +any.new=New Password Management +any.edit=Edit Password Management +description=Description +type=Type +enabled=Enabled +order=Order +menu.history=Configuration history diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_fr.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_fr.properties new file mode 100644 index 00000000000..acbd8a9ef84 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_fr.properties @@ -0,0 +1,23 @@ +# 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. +any.new=New Password Management +any.edit=Edit Password Management +description=Description +type=Type +enabled=Enabled +order=Order +menu.history=Configuration history diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_fr_CA.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_fr_CA.properties new file mode 100644 index 00000000000..b5c7096ac86 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_fr_CA.properties @@ -0,0 +1,24 @@ +# 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. +any.new=New Password Management +any.edit=Edit Password Management + +description=Description +type=Type +enabled=Enabled +order=Order +menu.history=Configuration history diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_it.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_it.properties new file mode 100644 index 00000000000..f5be2349ce6 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_it.properties @@ -0,0 +1,23 @@ +# 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. +any.new=Nuovo Modulo di Gestione Password +any.edit=Aggiorna Modulo di Gestione Password +description=Descrizione +type=Tipo +enabled=Attiva +order=Ordinamento +auditHistory.title=Storico delle configurazioni diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_ja.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_ja.properties new file mode 100644 index 00000000000..b5c7096ac86 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_ja.properties @@ -0,0 +1,24 @@ +# 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. +any.new=New Password Management +any.edit=Edit Password Management + +description=Description +type=Type +enabled=Enabled +order=Order +menu.history=Configuration history diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_pt_BR.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_pt_BR.properties new file mode 100644 index 00000000000..acbd8a9ef84 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_pt_BR.properties @@ -0,0 +1,23 @@ +# 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. +any.new=New Password Management +any.edit=Edit Password Management +description=Description +type=Type +enabled=Enabled +order=Order +menu.history=Configuration history diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_ru.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_ru.properties new file mode 100644 index 00000000000..acbd8a9ef84 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/PasswordManagementDirectoryPanel_ru.properties @@ -0,0 +1,23 @@ +# 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. +any.new=New Password Management +any.edit=Edit Password Management +description=Description +type=Type +enabled=Enabled +order=Order +menu.history=Configuration history diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Configuration.html b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Configuration.html new file mode 100644 index 00000000000..38545ff1d34 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Configuration.html @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile.html b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile.html new file mode 100644 index 00000000000..5351f400b25 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile.html @@ -0,0 +1,26 @@ + + + +
[enabled]
+
[key]
+
[description]
+
[conf]
+
+ \ No newline at end of file diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile.properties new file mode 100644 index 00000000000..18cf102c424 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile.properties @@ -0,0 +1,19 @@ +# 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. +description=Description +configuration=Configuration +type=Type diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_fr.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_fr.properties new file mode 100644 index 00000000000..18cf102c424 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_fr.properties @@ -0,0 +1,19 @@ +# 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. +description=Description +configuration=Configuration +type=Type diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_fr_CA.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_fr_CA.properties new file mode 100644 index 00000000000..18cf102c424 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_fr_CA.properties @@ -0,0 +1,19 @@ +# 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. +description=Description +configuration=Configuration +type=Type diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_it.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_it.properties new file mode 100644 index 00000000000..98514ae0b53 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_it.properties @@ -0,0 +1,19 @@ +# 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. +description=Descrizione +configuration=Configurazione +type=Tipo diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_ja.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_ja.properties new file mode 100644 index 00000000000..18cf102c424 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_ja.properties @@ -0,0 +1,19 @@ +# 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. +description=Description +configuration=Configuration +type=Type diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_pt_BR.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_pt_BR.properties new file mode 100644 index 00000000000..18cf102c424 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_pt_BR.properties @@ -0,0 +1,19 @@ +# 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. +description=Description +configuration=Configuration +type=Type diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_ru.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_ru.properties new file mode 100644 index 00000000000..18cf102c424 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/wizards/PasswordManagementWizardBuilder$Profile_ru.properties @@ -0,0 +1,19 @@ +# 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. +description=Description +configuration=Configuration +type=Type diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/JDBCPasswordManagementConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/JDBCPasswordManagementConf.java new file mode 100644 index 00000000000..3aa832abf93 --- /dev/null +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/JDBCPasswordManagementConf.java @@ -0,0 +1,137 @@ +/* + * 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.common.lib.password; + +import java.util.Map; +import org.apache.syncope.common.lib.AbstractJDBCConf; +import org.apache.syncope.common.lib.to.PasswordManagementTO; + +public class JDBCPasswordManagementConf extends AbstractJDBCConf implements PasswordManagementConf { + + private static final long serialVersionUID = 1335379501740158360L; + + /** + * SQL query to change the password and update. + */ + private String sqlChangePassword; + + /** + * SQL query to locate the user email address. + */ + private String sqlFindEmail; + + /** + * SQL query to locate the user phone number. + */ + private String sqlFindPhone; + + /** + * SQL query to locate the user via email. + */ + private String sqlFindUser; + + /** + * SQL query to locate security questions for the account, if any. + */ + private String sqlGetSecurityQuestions; + + /** + * SQL query to update security questions for the account, if any. + */ + private String sqlUpdateSecurityQuestions; + + /** + * SQL query to unlock accounts. + */ + private String sqlUnlockAccount; + + /** + * SQL query to delete security questions for the account, if any. + */ + private String sqlDeleteSecurityQuestions; + + public String getSqlChangePassword() { + return sqlChangePassword; + } + + public void setSqlChangePassword(final String sqlChangePassword) { + this.sqlChangePassword = sqlChangePassword; + } + + public String getSqlFindEmail() { + return sqlFindEmail; + } + + public void setSqlFindEmail(final String sqlFindEmail) { + this.sqlFindEmail = sqlFindEmail; + } + + public String getSqlFindPhone() { + return sqlFindPhone; + } + + public void setSqlFindPhone(final String sqlFindPhone) { + this.sqlFindPhone = sqlFindPhone; + } + + public String getSqlFindUser() { + return sqlFindUser; + } + + public void setSqlFindUser(final String sqlFindUser) { + this.sqlFindUser = sqlFindUser; + } + + public String getSqlGetSecurityQuestions() { + return sqlGetSecurityQuestions; + } + + public void setSqlGetSecurityQuestions(final String sqlGetSecurityQuestions) { + this.sqlGetSecurityQuestions = sqlGetSecurityQuestions; + } + + public String getSqlUpdateSecurityQuestions() { + return sqlUpdateSecurityQuestions; + } + + public void setSqlUpdateSecurityQuestions(final String sqlUpdateSecurityQuestions) { + this.sqlUpdateSecurityQuestions = sqlUpdateSecurityQuestions; + } + + public String getSqlUnlockAccount() { + return sqlUnlockAccount; + } + + public void setSqlUnlockAccount(final String sqlUnlockAccount) { + this.sqlUnlockAccount = sqlUnlockAccount; + } + + public String getSqlDeleteSecurityQuestions() { + return sqlDeleteSecurityQuestions; + } + + public void setSqlDeleteSecurityQuestions(final String sqlDeleteSecurityQuestions) { + this.sqlDeleteSecurityQuestions = sqlDeleteSecurityQuestions; + } + + @Override + public Map map(final PasswordManagementTO passwordManagementTO, final Mapper mapper) { + return mapper.map(passwordManagementTO, this); + } +} diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/LDAPPasswordManagementConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/LDAPPasswordManagementConf.java new file mode 100644 index 00000000000..06d51382d80 --- /dev/null +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/LDAPPasswordManagementConf.java @@ -0,0 +1,46 @@ +/* + * 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.common.lib.password; + +import java.util.Map; +import org.apache.syncope.common.lib.AbstractLDAPConf; +import org.apache.syncope.common.lib.to.PasswordManagementTO; + +public class LDAPPasswordManagementConf extends AbstractLDAPConf implements PasswordManagementConf { + + private static final long serialVersionUID = 3721321025567652223L; + + /** + * Username attribute required by LDAP. + */ + private String usernameAttribute = "uid"; + + public String getUsernameAttribute() { + return usernameAttribute; + } + + public void setUsernameAttribute(final String usernameAttribute) { + this.usernameAttribute = usernameAttribute; + } + + @Override + public Map map(final PasswordManagementTO passwordManagementTO, final Mapper mapper) { + return mapper.map(passwordManagementTO, this); + } +} diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/PasswordManagementConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/PasswordManagementConf.java new file mode 100644 index 00000000000..81756e41905 --- /dev/null +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/PasswordManagementConf.java @@ -0,0 +1,43 @@ +/* + * 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.common.lib.password; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import java.util.Map; +import org.apache.syncope.common.lib.BaseBean; +import org.apache.syncope.common.lib.to.PasswordManagementTO; + +@FunctionalInterface +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "_class") +public interface PasswordManagementConf extends BaseBean { + + interface Mapper { + + Map map(PasswordManagementTO passwordManagementTO, SyncopePasswordManagementConf conf); + + Map map(PasswordManagementTO passwordManagementTO, LDAPPasswordManagementConf conf); + + Map map(PasswordManagementTO passwordManagementTO, JDBCPasswordManagementConf conf); + + Map map(PasswordManagementTO passwordManagementTO, RESTPasswordManagementConf conf); + + } + + Map map(PasswordManagementTO passwordManagementTO, Mapper mapper); +} diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/RESTPasswordManagementConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/RESTPasswordManagementConf.java new file mode 100644 index 00000000000..efdeaa635f4 --- /dev/null +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/RESTPasswordManagementConf.java @@ -0,0 +1,186 @@ +/* + * 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.common.lib.password; + +import java.util.HashMap; +import java.util.Map; +import org.apache.syncope.common.lib.to.PasswordManagementTO; + +public class RESTPasswordManagementConf implements PasswordManagementConf { + + private static final long serialVersionUID = 7262269548010007815L; + + /** + * Password for Basic-Auth at the password management endpoints. + */ + private String endpointPassword; + + /** + * Endpoint URL to use when unlocking account. + */ + private String endpointUrlAccountUnlock; + + /** + * Endpoint URL to use when updating passwords. + */ + private String endpointUrlChange; + + /** + * Endpoint URL to use when locating email addresses. + */ + private String endpointUrlEmail; + + /** + * Endpoint URL to use when locating phone numbers. + */ + private String endpointUrlPhone; + + /** + * Endpoint URL to use when locating security questions. + */ + private String endpointUrlSecurityQuestions; + + /** + * Endpoint URL to use when locating user names. + */ + private String endpointUrlUser; + + /** + * Endpoint URL to use when locating usernames. + */ + private String endpointUsername; + + /** + * Field name for oldPassword field when password change requests are submitted. + */ + private String fieldNamePasswordOld = "oldPassword"; + + /** + * Field name for password field when password change requests are submitted. + */ + private String fieldNamePassword = "password"; + + /** + * Field name for username field when password change requests are submitted. + */ + private String fieldNameUser = "username"; + + /** + * Additional headers to be included in REST API calls for password management. + * The map keys are header names and the corresponding values are header values. + */ + private final Map headers = new HashMap<>(); + + public String getEndpointPassword() { + return endpointPassword; + } + + public void setEndpointPassword(final String endpointPassword) { + this.endpointPassword = endpointPassword; + } + + public String getEndpointUrlAccountUnlock() { + return endpointUrlAccountUnlock; + } + + public void setEndpointUrlAccountUnlock(final String endpointUrlAccountUnlock) { + this.endpointUrlAccountUnlock = endpointUrlAccountUnlock; + } + + public String getEndpointUrlChange() { + return endpointUrlChange; + } + + public void setEndpointUrlChange(final String endpointUrlChange) { + this.endpointUrlChange = endpointUrlChange; + } + + public String getEndpointUrlEmail() { + return endpointUrlEmail; + } + + public void setEndpointUrlEmail(final String endpointUrlEmail) { + this.endpointUrlEmail = endpointUrlEmail; + } + + public String getEndpointUrlPhone() { + return endpointUrlPhone; + } + + public void setEndpointUrlPhone(final String endpointUrlPhone) { + this.endpointUrlPhone = endpointUrlPhone; + } + + public String getEndpointUrlSecurityQuestions() { + return endpointUrlSecurityQuestions; + } + + public void setEndpointUrlSecurityQuestions(final String endpointUrlSecurityQuestions) { + this.endpointUrlSecurityQuestions = endpointUrlSecurityQuestions; + } + + public String getEndpointUrlUser() { + return endpointUrlUser; + } + + public void setEndpointUrlUser(final String endpointUrlUser) { + this.endpointUrlUser = endpointUrlUser; + } + + public String getEndpointUsername() { + return endpointUsername; + } + + public void setEndpointUsername(final String endpointUsername) { + this.endpointUsername = endpointUsername; + } + + public String getFieldNamePasswordOld() { + return fieldNamePasswordOld; + } + + public void setFieldNamePasswordOld(final String fieldNamePasswordOld) { + this.fieldNamePasswordOld = fieldNamePasswordOld; + } + + public String getFieldNamePassword() { + return fieldNamePassword; + } + + public void setFieldNamePassword(final String fieldNamePassword) { + this.fieldNamePassword = fieldNamePassword; + } + + public String getFieldNameUser() { + return fieldNameUser; + } + + public void setFieldNameUser(final String fieldNameUser) { + this.fieldNameUser = fieldNameUser; + } + + public Map getHeaders() { + return headers; + } + + @Override + public Map map(final PasswordManagementTO passwordManagementTO, final Mapper mapper) { + return mapper.map(passwordManagementTO, this); + } +} diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/SyncopePasswordManagementConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/SyncopePasswordManagementConf.java new file mode 100644 index 00000000000..85bcfe922e8 --- /dev/null +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/SyncopePasswordManagementConf.java @@ -0,0 +1,95 @@ +/* + * 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.common.lib.password; + +import java.util.HashMap; +import java.util.Map; +import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.to.PasswordManagementTO; + +public class SyncopePasswordManagementConf implements PasswordManagementConf { + + private static final long serialVersionUID = -8203692328056109683L; + + private String domain = SyncopeConstants.MASTER_DOMAIN; + + /** + * User FIQL filter to use for searching. + */ + protected String searchFilter; + + /** + * Specify the username for REST authentication. + */ + private String basicAuthUsername; + + /** + * Specify the password for REST authentication. + */ + private String basicAuthPassword; + + /** + * Headers, defined as a Map, to include in the request when making the REST call. + * Will overwrite any header that CAS is pre-defined to + * send and include in the request. Key in the map should be the header name + * and the value in the map should be the header value. + */ + private final Map headers = new HashMap<>(); + + public String getDomain() { + return domain; + } + + public void setDomain(final String domain) { + this.domain = domain; + } + + public String getSearchFilter() { + return searchFilter; + } + + public void setSearchFilter(final String searchFilter) { + this.searchFilter = searchFilter; + } + + public String getBasicAuthUsername() { + return basicAuthUsername; + } + + public void setBasicAuthUsername(final String basicAuthUsername) { + this.basicAuthUsername = basicAuthUsername; + } + + public String getBasicAuthPassword() { + return basicAuthPassword; + } + + public void setBasicAuthPassword(final String basicAuthPassword) { + this.basicAuthPassword = basicAuthPassword; + } + + public Map getHeaders() { + return headers; + } + + @Override + public Map map(final PasswordManagementTO passwordManagementTO, final Mapper mapper) { + return mapper.map(passwordManagementTO, this); + } +} diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/PasswordManagementTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/PasswordManagementTO.java new file mode 100644 index 00000000000..8e4b23b6a44 --- /dev/null +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/PasswordManagementTO.java @@ -0,0 +1,101 @@ +/* + * 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.common.lib.to; + +import jakarta.ws.rs.PathParam; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.syncope.common.lib.password.PasswordManagementConf; + +public class PasswordManagementTO implements EntityTO { + + private static final long serialVersionUID = -7203295929825782174L; + + private String key; + + private String description; + + private boolean enabled = false; + + private PasswordManagementConf conf; + + @Override + public String getKey() { + return key; + } + + @PathParam("key") + @Override + public void setKey(final String key) { + this.key = key; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + public PasswordManagementConf getConf() { + return conf; + } + + public void setConf(final PasswordManagementConf conf) { + this.conf = conf; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PasswordManagementTO other = (PasswordManagementTO) obj; + return new EqualsBuilder(). + append(key, other.key). + append(description, other.description). + append(enabled, other.enabled). + append(conf, other.conf). + build(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(). + append(key). + append(description). + append(conf). + build(); + } +} diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java index f9fd0d65083..12b334ec42a 100644 --- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java @@ -68,6 +68,16 @@ public final class AMEntitlement { public static final String ATTR_REPO_DELETE = "ATTR_REPO_DELETE"; + public static final String PASSWORD_MANAGEMENT_LIST = "PASSWORD_MANAGEMENT_LIST"; + + public static final String PASSWORD_MANAGEMENT_CREATE = "PASSWORD_MANAGEMENT_CREATE"; + + public static final String PASSWORD_MANAGEMENT_READ = "PASSWORD_MANAGEMENT_READ"; + + public static final String PASSWORD_MANAGEMENT_UPDATE = "PASSWORD_MANAGEMENT_UPDATE"; + + public static final String PASSWORD_MANAGEMENT_DELETE = "PASSWORD_MANAGEMENT_DELETE"; + public static final String SAML2_IDP_ENTITY_SET = "SAML2_IDP_ENTITY_SET"; public static final String SAML2_IDP_ENTITY_LIST = "SAML2_IDP_ENTITY_LIST"; diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/PasswordManagementService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/PasswordManagementService.java new file mode 100644 index 00000000000..36e3166ea22 --- /dev/null +++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/PasswordManagementService.java @@ -0,0 +1,122 @@ +/* + * 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.common.rest.api.service; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.security.SecurityRequirements; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.List; +import org.apache.syncope.common.lib.to.PasswordManagementTO; +import org.apache.syncope.common.rest.api.RESTHeaders; + +/** + * REST operations for password management modules. + */ +@Tag(name = "PasswordManagement") +@SecurityRequirements({ + @SecurityRequirement(name = "BasicAuthentication"), + @SecurityRequirement(name = "Bearer") }) +@Path("passwordManagement") +public interface PasswordManagementService extends JAXRSService { + + /** + * Returns the password management module matching the given key. + * + * @param key key of requested password management module + * @return password management module with matching id + */ + @GET + @Path("{key}") + @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML }) + PasswordManagementTO read(@NotNull @PathParam("key") String key); + + /** + * Returns a list of password management modules. + * + * @return list of password management modules + */ + @GET + @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML }) + List list(); + + /** + * Create a new password management module. + * + * @param passwordManagementTO PasswordManagement to be created. + * @return Response object featuring Location header of created password management module + */ + @ApiResponses( + @ApiResponse(responseCode = "201", + description = "PasswordManagement successfully created", headers = { + @Header(name = RESTHeaders.RESOURCE_KEY, schema = + @Schema(type = "string"), + description = "UUID generated for the entity created"), + @Header(name = HttpHeaders.LOCATION, schema = + @Schema(type = "string"), + description = "URL of the entity created") })) + @POST + @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML }) + @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML }) + Response create(@NotNull PasswordManagementTO passwordManagementTO); + + /** + * Updates password management module matching the given key. + * + * @param passwordManagementTO PasswordManagement to replace existing password management module + */ + @Parameter(name = "key", description = "PasswordManagement's key", in = ParameterIn.PATH, schema = + @Schema(type = "string")) + @ApiResponses( + @ApiResponse(responseCode = "204", description = "Operation was successful")) + @PUT + @Path("{key}") + @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML }) + @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML }) + void update(@NotNull PasswordManagementTO passwordManagementTO); + + /** + * Delete password management module matching the given key. + * + * @param key key of password management module to be deleted + */ + @ApiResponses( + @ApiResponse(responseCode = "204", description = "Operation was successful")) + @DELETE + @Path("{key}") + @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML }) + void delete(@NotNull @PathParam("key") String key); +} diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java index 1a179cba6b8..439cca4e817 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java @@ -44,7 +44,8 @@ public enum EntityViolationType { InvalidUsername, InvalidValueList, InvalidRemediation, - MoreThanOneNonNull; + MoreThanOneNonNull, + MoreThanOneEnabled; private String message; diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/AMLogicContext.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/AMLogicContext.java index bdff58a04eb..fa90c9a81b2 100644 --- a/core/am/logic/src/main/java/org/apache/syncope/core/logic/AMLogicContext.java +++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/AMLogicContext.java @@ -33,6 +33,7 @@ import org.apache.syncope.core.persistence.api.dao.CASSPClientAppDAO; import org.apache.syncope.core.persistence.api.dao.OIDCJWKSDAO; import org.apache.syncope.core.persistence.api.dao.OIDCRPClientAppDAO; +import org.apache.syncope.core.persistence.api.dao.PasswordManagementDAO; import org.apache.syncope.core.persistence.api.dao.SAML2IdPEntityDAO; import org.apache.syncope.core.persistence.api.dao.SAML2SPClientAppDAO; import org.apache.syncope.core.persistence.api.dao.SRARouteDAO; @@ -44,6 +45,7 @@ import org.apache.syncope.core.provisioning.api.data.AuthProfileDataBinder; import org.apache.syncope.core.provisioning.api.data.ClientAppDataBinder; import org.apache.syncope.core.provisioning.api.data.OIDCJWKSDataBinder; +import org.apache.syncope.core.provisioning.api.data.PasswordManagementDataBinder; import org.apache.syncope.core.provisioning.api.data.SAML2IdPEntityDataBinder; import org.apache.syncope.core.provisioning.api.data.SRARouteDataBinder; import org.apache.syncope.core.provisioning.api.data.WAConfigDataBinder; @@ -80,6 +82,15 @@ public AttrRepoLogic attrRepoLogic( return new AttrRepoLogic(binder, attrRepoDAO); } + @ConditionalOnMissingBean + @Bean + public PasswordManagementLogic passwordManagementLogic( + final PasswordManagementDataBinder passwordManagementDataBinder, + final PasswordManagementDAO passwordManagementDAO) { + + return new PasswordManagementLogic(passwordManagementDataBinder, passwordManagementDAO); + } + @ConditionalOnMissingBean @Bean public AuthProfileLogic authProfileLogic( diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/PasswordManagementLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/PasswordManagementLogic.java new file mode 100644 index 00000000000..29951b52a1a --- /dev/null +++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/PasswordManagementLogic.java @@ -0,0 +1,115 @@ +/* + * 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.core.logic; + +import java.lang.reflect.Method; +import java.util.List; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.syncope.common.lib.to.AuthModuleTO; +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.core.persistence.api.dao.NotFoundException; +import org.apache.syncope.core.persistence.api.dao.PasswordManagementDAO; +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; +import org.apache.syncope.core.provisioning.api.data.PasswordManagementDataBinder; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; + +public class PasswordManagementLogic extends AbstractTransactionalLogic { + + protected final PasswordManagementDataBinder binder; + + protected final PasswordManagementDAO passwordManagementDAO; + + public PasswordManagementLogic( + final PasswordManagementDataBinder passwordManagementDataBinder, + final PasswordManagementDAO passwordManagementDAO) { + + this.binder = passwordManagementDataBinder; + this.passwordManagementDAO = passwordManagementDAO; + } + + @PreAuthorize("hasRole('" + AMEntitlement.PASSWORD_MANAGEMENT_CREATE + "')") + public PasswordManagementTO create(final PasswordManagementTO passwordManagementTO) { + return binder.getPasswordManagementTO(passwordManagementDAO.save(binder.create(passwordManagementTO))); + } + + @PreAuthorize("hasRole('" + AMEntitlement.PASSWORD_MANAGEMENT_UPDATE + "')") + public PasswordManagementTO update(final PasswordManagementTO passwordManagementTO) { + PasswordManagement passwordManagement = passwordManagementDAO.findById(passwordManagementTO.getKey()). + orElseThrow(() -> new NotFoundException("PasswordManagement " + passwordManagementTO.getKey())); + + return binder.getPasswordManagementTO( + passwordManagementDAO.save(binder.update(passwordManagement, passwordManagementTO))); + } + + @PreAuthorize("hasRole('" + AMEntitlement.PASSWORD_MANAGEMENT_LIST + "') or hasRole('" + + IdRepoEntitlement.ANONYMOUS + "')") + @Transactional(readOnly = true) + public List list() { + return passwordManagementDAO.findAll().stream() + .map(binder::getPasswordManagementTO).toList(); + } + + @PreAuthorize("hasRole('" + AMEntitlement.PASSWORD_MANAGEMENT_READ + "')") + @Transactional(readOnly = true) + public PasswordManagementTO read(final String key) { + PasswordManagement passwordManagement = passwordManagementDAO.findById(key). + orElseThrow(() -> new NotFoundException("PasswordManagement " + key)); + + return binder.getPasswordManagementTO(passwordManagement); + } + + @PreAuthorize("hasRole('" + AMEntitlement.PASSWORD_MANAGEMENT_DELETE + "')") + public PasswordManagementTO delete(final String key) { + PasswordManagement passwordManagement = passwordManagementDAO.findById(key). + orElseThrow(() -> new NotFoundException("PasswordManagement " + key)); + + PasswordManagementTO deleted = binder.getPasswordManagementTO(passwordManagement); + passwordManagementDAO.delete(passwordManagement); + + return deleted; + } + + @Override + protected PasswordManagementTO resolveReference(final Method method, final Object... args) + throws UnresolvedReferenceException { + if (ArrayUtils.isEmpty(args)) { + throw new UnresolvedReferenceException(); + } + + final String key; + + if (args[0] instanceof String string) { + key = string; + } else if (args[0] instanceof AuthModuleTO authModuleTO) { + key = authModuleTO.getKey(); + } else { + throw new UnresolvedReferenceException(); + } + + try { + return binder.getPasswordManagementTO(passwordManagementDAO.findById(key).orElseThrow()); + } catch (Throwable ignore) { + LOG.debug("Unresolved reference", ignore); + throw new UnresolvedReferenceException(ignore); + } + } +} diff --git a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/AMRESTCXFContext.java b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/AMRESTCXFContext.java index e419126b1ce..41bcebdf7d5 100644 --- a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/AMRESTCXFContext.java +++ b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/AMRESTCXFContext.java @@ -24,6 +24,7 @@ import org.apache.syncope.common.rest.api.service.AuthProfileService; import org.apache.syncope.common.rest.api.service.ClientAppService; import org.apache.syncope.common.rest.api.service.OIDCJWKSService; +import org.apache.syncope.common.rest.api.service.PasswordManagementService; import org.apache.syncope.common.rest.api.service.SAML2IdPEntityService; import org.apache.syncope.common.rest.api.service.SRARouteService; import org.apache.syncope.common.rest.api.service.wa.GoogleMfaAuthAccountService; @@ -39,6 +40,7 @@ import org.apache.syncope.core.logic.AuthProfileLogic; import org.apache.syncope.core.logic.ClientAppLogic; import org.apache.syncope.core.logic.OIDCJWKSLogic; +import org.apache.syncope.core.logic.PasswordManagementLogic; import org.apache.syncope.core.logic.SAML2IdPEntityLogic; import org.apache.syncope.core.logic.SRARouteLogic; import org.apache.syncope.core.logic.wa.GoogleMfaAuthAccountLogic; @@ -54,6 +56,7 @@ import org.apache.syncope.core.rest.cxf.service.AuthProfileServiceImpl; import org.apache.syncope.core.rest.cxf.service.ClientAppServiceImpl; import org.apache.syncope.core.rest.cxf.service.OIDCJWKSServiceImpl; +import org.apache.syncope.core.rest.cxf.service.PasswordManagementServiceImpl; import org.apache.syncope.core.rest.cxf.service.SAML2IdPEntityServiceImpl; import org.apache.syncope.core.rest.cxf.service.SRARouteServiceImpl; import org.apache.syncope.core.rest.cxf.service.wa.GoogleMfaAuthAccountServiceImpl; @@ -83,6 +86,12 @@ public AttrRepoService attrRepoService(final AttrRepoLogic attrRepoLogic) { return new AttrRepoServiceImpl(attrRepoLogic); } + @ConditionalOnMissingBean + @Bean + public PasswordManagementService passwordManagementService(final PasswordManagementLogic passwordManagementLogic) { + return new PasswordManagementServiceImpl(passwordManagementLogic); + } + @ConditionalOnMissingBean @Bean public AuthProfileService authProfileService(final AuthProfileLogic authProfileLogic) { diff --git a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/PasswordManagementServiceImpl.java b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/PasswordManagementServiceImpl.java new file mode 100644 index 00000000000..5627c66548b --- /dev/null +++ b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/PasswordManagementServiceImpl.java @@ -0,0 +1,65 @@ +/* + * 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.core.rest.cxf.service; + +import jakarta.ws.rs.core.Response; +import java.net.URI; +import java.util.List; +import org.apache.syncope.common.lib.to.PasswordManagementTO; +import org.apache.syncope.common.rest.api.RESTHeaders; +import org.apache.syncope.common.rest.api.service.PasswordManagementService; +import org.apache.syncope.core.logic.PasswordManagementLogic; + +public class PasswordManagementServiceImpl extends AbstractService implements PasswordManagementService { + + protected final PasswordManagementLogic logic; + + public PasswordManagementServiceImpl(final PasswordManagementLogic passwordManagementLogic) { + this.logic = passwordManagementLogic; + } + + @Override + public PasswordManagementTO read(final String key) { + return logic.read(key); + } + + @Override + public List list() { + return logic.list(); + } + + @Override + public Response create(final PasswordManagementTO passwordManagementTO) { + PasswordManagementTO passwordManagement = logic.create(passwordManagementTO); + URI location = uriInfo.getAbsolutePathBuilder().path(passwordManagement.getKey()).build(); + return Response.created(location). + header(RESTHeaders.RESOURCE_KEY, passwordManagement.getKey()). + build(); + } + + @Override + public void update(final PasswordManagementTO passwordManagementTO) { + logic.update(passwordManagementTO); + } + + @Override + public void delete(final String key) { + logic.delete(key); + } +} diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PasswordManagementDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PasswordManagementDAO.java new file mode 100644 index 00000000000..3af82e9ea0a --- /dev/null +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PasswordManagementDAO.java @@ -0,0 +1,26 @@ +/* + * 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.core.persistence.api.dao; + +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; + +public interface PasswordManagementDAO extends DAO { + + boolean isAnotherInstanceEnabled(String key); +} diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/PasswordManagement.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/PasswordManagement.java new file mode 100644 index 00000000000..248489487bc --- /dev/null +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/PasswordManagement.java @@ -0,0 +1,37 @@ +/* + * 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.core.persistence.api.entity.am; + +import org.apache.syncope.common.lib.password.PasswordManagementConf; +import org.apache.syncope.core.persistence.api.entity.ProvidedKeyEntity; + +public interface PasswordManagement extends ProvidedKeyEntity { + + String getDescription(); + + void setDescription(String description); + + boolean isEnabled(); + + void setEnabled(boolean enabled); + + PasswordManagementConf getConf(); + + void setConf(PasswordManagementConf conf); +} diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/PasswordManagementCheck.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/PasswordManagementCheck.java new file mode 100644 index 00000000000..09ee5f069fe --- /dev/null +++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/PasswordManagementCheck.java @@ -0,0 +1,40 @@ +/* + * 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.core.persistence.common.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = PasswordManagementValidator.class) +@Documented +public @interface PasswordManagementCheck { + + String message() default "{org.apache.syncope.core.persistence.validation.passwordmanagement}"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/PasswordManagementValidator.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/PasswordManagementValidator.java new file mode 100644 index 00000000000..662eb656096 --- /dev/null +++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/PasswordManagementValidator.java @@ -0,0 +1,47 @@ +/* + * 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.core.persistence.common.validation; + +import jakarta.validation.ConstraintValidatorContext; +import org.apache.syncope.common.lib.types.EntityViolationType; +import org.apache.syncope.core.persistence.api.ApplicationContextProvider; +import org.apache.syncope.core.persistence.api.dao.PasswordManagementDAO; +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; + +public class PasswordManagementValidator extends AbstractValidator { + + @Override + public boolean isValid(final PasswordManagement passwordManagement, final ConstraintValidatorContext context) { + if (!passwordManagement.isEnabled()) { + return true; + } + + PasswordManagementDAO passwordManagementDAO = + ApplicationContextProvider.getApplicationContext().getBean(PasswordManagementDAO.class); + context.disableDefaultConstraintViolation(); + + boolean isValid = !passwordManagementDAO.isAnotherInstanceEnabled(passwordManagement.getKey()); + if (!isValid) { + context.buildConstraintViolationWithTemplate( + getTemplate(EntityViolationType.MoreThanOneEnabled, "More than one enabled instance found")). + addPropertyNode(PasswordManagement.class.getSimpleName()).addConstraintViolation(); + } + return isValid; + } +} diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java index 4d31d9e4f04..61378d2195d 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java @@ -57,6 +57,7 @@ import org.apache.syncope.core.persistence.api.dao.NotificationDAO; import org.apache.syncope.core.persistence.api.dao.OIDCJWKSDAO; import org.apache.syncope.core.persistence.api.dao.OIDCRPClientAppDAO; +import org.apache.syncope.core.persistence.api.dao.PasswordManagementDAO; import org.apache.syncope.core.persistence.api.dao.PersistenceInfoDAO; import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; import org.apache.syncope.core.persistence.api.dao.PolicyDAO; @@ -154,6 +155,9 @@ import org.apache.syncope.core.persistence.jpa.dao.repo.OIDCRPClientAppRepo; import org.apache.syncope.core.persistence.jpa.dao.repo.OIDCRPClientAppRepoExt; import org.apache.syncope.core.persistence.jpa.dao.repo.OIDCRPClientAppRepoExtImpl; +import org.apache.syncope.core.persistence.jpa.dao.repo.PasswordManagementRepo; +import org.apache.syncope.core.persistence.jpa.dao.repo.PasswordManagementRepoExt; +import org.apache.syncope.core.persistence.jpa.dao.repo.PasswordManagementRepoExtImpl; import org.apache.syncope.core.persistence.jpa.dao.repo.PlainSchemaRepo; import org.apache.syncope.core.persistence.jpa.dao.repo.PlainSchemaRepoExt; import org.apache.syncope.core.persistence.jpa.dao.repo.RelationshipTypeRepo; @@ -491,6 +495,21 @@ public AttrRepoRepoExt attrRepoRepoExt(final EntityManager entityManager) { return new AttrRepoRepoExtImpl(entityManager); } + @ConditionalOnMissingBean + @Bean + public AuthModuleRepoExt authModuleRepoExt(final PolicyDAO policyDAO, final EntityManager entityManager) { + return new AuthModuleRepoExtImpl(policyDAO, entityManager); + } + + @ConditionalOnMissingBean + @Bean + public AuthModuleDAO authModuleDAO( + final JpaRepositoryFactory jpaRepositoryFactory, + final AuthModuleRepoExt authModuleRepoExt) { + + return jpaRepositoryFactory.getRepository(AuthModuleRepo.class, authModuleRepoExt); + } + @ConditionalOnMissingBean @Bean public AttrRepoDAO attrRepoDAO( @@ -502,17 +521,17 @@ public AttrRepoDAO attrRepoDAO( @ConditionalOnMissingBean @Bean - public AuthModuleRepoExt authModuleRepoExt(final PolicyDAO policyDAO, final EntityManager entityManager) { - return new AuthModuleRepoExtImpl(policyDAO, entityManager); + public PasswordManagementRepoExt passwordManagementRepoExt(final EntityManager entityManager) { + return new PasswordManagementRepoExtImpl(entityManager); } @ConditionalOnMissingBean @Bean - public AuthModuleDAO authModuleDAO( + public PasswordManagementDAO passwordManagementDAO( final JpaRepositoryFactory jpaRepositoryFactory, - final AuthModuleRepoExt authModuleRepoExt) { + final PasswordManagementRepoExt passwordManagementRepoExt) { - return jpaRepositoryFactory.getRepository(AuthModuleRepo.class, authModuleRepoExt); + return jpaRepositoryFactory.getRepository(PasswordManagementRepo.class, passwordManagementRepoExt); } @ConditionalOnMissingBean diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/PasswordManagementRepo.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/PasswordManagementRepo.java new file mode 100644 index 00000000000..8c0ccc9e257 --- /dev/null +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/PasswordManagementRepo.java @@ -0,0 +1,28 @@ +/* + * 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.core.persistence.jpa.dao.repo; + +import org.apache.syncope.core.persistence.api.dao.PasswordManagementDAO; +import org.apache.syncope.core.persistence.jpa.entity.am.JPAPasswordManagement; +import org.springframework.data.repository.ListCrudRepository; + +public interface PasswordManagementRepo + extends ListCrudRepository, PasswordManagementRepoExt, PasswordManagementDAO { + +} diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/PasswordManagementRepoExt.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/PasswordManagementRepoExt.java new file mode 100644 index 00000000000..4dd3d9a880b --- /dev/null +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/PasswordManagementRepoExt.java @@ -0,0 +1,24 @@ +/* + * 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.core.persistence.jpa.dao.repo; + +public interface PasswordManagementRepoExt { + + boolean isAnotherInstanceEnabled(String key); +} diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/PasswordManagementRepoExtImpl.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/PasswordManagementRepoExtImpl.java new file mode 100644 index 00000000000..c848af12376 --- /dev/null +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/PasswordManagementRepoExtImpl.java @@ -0,0 +1,42 @@ +/* + * 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.core.persistence.jpa.dao.repo; + +import jakarta.persistence.EntityManager; +import org.apache.syncope.core.persistence.jpa.entity.am.JPAPasswordManagement; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +public class PasswordManagementRepoExtImpl implements PasswordManagementRepoExt { + + protected final EntityManager entityManager; + + public PasswordManagementRepoExtImpl(final EntityManager entityManager) { + this.entityManager = entityManager; + } + + @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) + @Override + public boolean isAnotherInstanceEnabled(final String key) { + Long count = entityManager.createQuery( + "SELECT COUNT(pm) FROM " + JPAPasswordManagement.class.getSimpleName() + " pm " + + "WHERE pm.enabled = 'true' AND pm.id <> :id", Long.class).setParameter("id", key).getSingleResult(); + return count > 0; + } +} diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractEntityFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractEntityFactory.java index c1089baa2bd..1dfbc28225e 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractEntityFactory.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractEntityFactory.java @@ -53,6 +53,7 @@ import org.apache.syncope.core.persistence.api.entity.am.CASSPClientApp; import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS; import org.apache.syncope.core.persistence.api.entity.am.OIDCRPClientApp; +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; import org.apache.syncope.core.persistence.api.entity.am.SAML2IdPEntity; import org.apache.syncope.core.persistence.api.entity.am.SAML2SPClientApp; import org.apache.syncope.core.persistence.api.entity.am.WAConfigEntry; @@ -99,6 +100,7 @@ import org.apache.syncope.core.persistence.jpa.entity.am.JPACASSPClientApp; import org.apache.syncope.core.persistence.jpa.entity.am.JPAOIDCJWKS; import org.apache.syncope.core.persistence.jpa.entity.am.JPAOIDCRPClientApp; +import org.apache.syncope.core.persistence.jpa.entity.am.JPAPasswordManagement; import org.apache.syncope.core.persistence.jpa.entity.am.JPASAML2IdPEntity; import org.apache.syncope.core.persistence.jpa.entity.am.JPASAML2SPClientApp; import org.apache.syncope.core.persistence.jpa.entity.am.JPAWAConfigEntry; @@ -264,6 +266,8 @@ public E newEntity(final Class reference) { result = (E) new JPAAuthModule(); } else if (reference.equals(AttrRepo.class)) { result = (E) new JPAAttrRepo(); + } else if (reference.equals(PasswordManagement.class)) { + result = (E) new JPAPasswordManagement(); } else if (reference.equals(AuthPolicy.class)) { result = (E) new JPAAuthPolicy(); } else if (reference.equals(AccessPolicy.class)) { diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPAPasswordManagement.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPAPasswordManagement.java new file mode 100644 index 00000000000..416e16e9703 --- /dev/null +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPAPasswordManagement.java @@ -0,0 +1,84 @@ +/* + * 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.core.persistence.jpa.entity.am; + +import jakarta.persistence.Entity; +import jakarta.persistence.Lob; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.password.PasswordManagementConf; +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; +import org.apache.syncope.core.persistence.common.validation.PasswordManagementCheck; +import org.apache.syncope.core.persistence.jpa.entity.AbstractProvidedKeyEntity; +import org.apache.syncope.core.provisioning.api.serialization.POJOHelper; + +@Entity +@Table(name = JPAPasswordManagement.TABLE) +@PasswordManagementCheck +public class JPAPasswordManagement extends AbstractProvidedKeyEntity implements PasswordManagement { + + private static final long serialVersionUID = 5457779846065079998L; + + public static final String TABLE = "PasswordManagement"; + + private String description; + + @NotNull + private boolean enabled; + + @Lob + private String jsonConf; + + @Override + public String getDescription() { + return description; + } + + @Override + public void setDescription(final String description) { + this.description = description; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + + } + + @Override + public PasswordManagementConf getConf() { + PasswordManagementConf conf = null; + if (!StringUtils.isBlank(jsonConf)) { + conf = POJOHelper.deserialize(jsonConf, PasswordManagementConf.class); + } + + return conf; + } + + @Override + public void setConf(final PasswordManagementConf conf) { + jsonConf = POJOHelper.serialize(conf); + } +} diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PasswordManagementTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PasswordManagementTest.java new file mode 100644 index 00000000000..967bedac437 --- /dev/null +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PasswordManagementTest.java @@ -0,0 +1,238 @@ +/* + * 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.core.persistence.jpa.inner; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.apache.commons.lang3.ClassUtils; +import org.apache.syncope.common.lib.password.JDBCPasswordManagementConf; +import org.apache.syncope.common.lib.password.LDAPPasswordManagementConf; +import org.apache.syncope.common.lib.password.PasswordManagementConf; +import org.apache.syncope.common.lib.password.RESTPasswordManagementConf; +import org.apache.syncope.common.lib.password.SyncopePasswordManagementConf; +import org.apache.syncope.core.persistence.api.dao.PasswordManagementDAO; +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; +import org.apache.syncope.core.persistence.jpa.AbstractTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +public class PasswordManagementTest extends AbstractTest { + + private static boolean isSpecificConf( + final PasswordManagementConf conf, + final Class clazz) { + + return ClassUtils.isAssignable(clazz, conf.getClass()); + } + + @Autowired + private PasswordManagementDAO passwordManagementDAO; + + @Test + public void findAll() { + List modules = passwordManagementDAO.findAll(); + assertNotNull(modules); + assertFalse(modules.isEmpty()); + assertEquals(4, modules.size()); + } + + @Test + public void find() { + PasswordManagement passwordManagement = passwordManagementDAO.findById("DefaultSyncopePasswordManagement") + .orElseThrow(); + assertInstanceOf(SyncopePasswordManagementConf.class, passwordManagement.getConf()); + + passwordManagement = passwordManagementDAO.findById("DefaultLDAPPasswordManagement").orElseThrow(); + assertInstanceOf(LDAPPasswordManagementConf.class, passwordManagement.getConf()); + + passwordManagement = passwordManagementDAO.findById("DefaultJDBCPasswordManagement").orElseThrow(); + assertInstanceOf(JDBCPasswordManagementConf.class, passwordManagement.getConf()); + + passwordManagement = passwordManagementDAO.findById("DefaultRESTPasswordManagement").orElseThrow(); + assertInstanceOf(RESTPasswordManagementConf.class, passwordManagement.getConf()); + } + + @Test + public void findByType() { + List passwordManagements = passwordManagementDAO.findAll(); + assertTrue(passwordManagements.stream().anyMatch( + passwordManagement -> isSpecificConf(passwordManagement.getConf(), + SyncopePasswordManagementConf.class) + && passwordManagement.getKey().equals("DefaultSyncopePasswordManagement"))); + assertTrue(passwordManagements.stream().anyMatch( + passwordManagement -> isSpecificConf(passwordManagement.getConf(), + LDAPPasswordManagementConf.class) + && passwordManagement.getKey().equals("DefaultLDAPPasswordManagement"))); + assertTrue(passwordManagements.stream().anyMatch( + passwordManagement -> isSpecificConf(passwordManagement.getConf(), + JDBCPasswordManagementConf.class) + && passwordManagement.getKey().equals("DefaultJDBCPasswordManagement"))); + assertTrue(passwordManagements.stream().anyMatch( + passwordManagement -> isSpecificConf(passwordManagement.getConf(), + RESTPasswordManagementConf.class) + && passwordManagement.getKey().equals("DefaultRESTPasswordManagement"))); + } + + private void savePasswordManagement(final String key, final PasswordManagementConf conf) { + PasswordManagement module = entityFactory.newEntity(PasswordManagement.class); + module.setKey(key); + module.setDescription("A password management module"); + module.setConf(conf); + module.setEnabled(false); + module = passwordManagementDAO.save(module); + + assertNotNull(module); + assertNotNull(module.getKey()); + assertEquals(module, passwordManagementDAO.findById(module.getKey()).orElseThrow()); + } + + @Test + public void saveWithSyncopeModule() { + SyncopePasswordManagementConf conf = new SyncopePasswordManagementConf(); + conf.setDomain("Master"); + + savePasswordManagement("SyncopePasswordManagementTest", conf); + } + + @Test + public void saveWithLDAPModule() { + LDAPPasswordManagementConf conf = new LDAPPasswordManagementConf(); + conf.setBaseDn("dc=example,dc=org"); + conf.setSearchFilter("cn={user}"); + conf.setSubtreeSearch(true); + conf.setLdapUrl("ldap://localhost:1389"); + conf.setUsernameAttribute("uid"); + conf.setBindCredential("Password"); + + savePasswordManagement("LDAPPasswordManagementTest", conf); + } + + @Test + public void saveWithJDBCModule() { + JDBCPasswordManagementConf conf = new JDBCPasswordManagementConf(); + conf.setSqlFindEmail("SELECT email from users_table where name=?"); + conf.setSqlFindPhone("SELECT phoneNumber from users_table where name=?"); + conf.setSqlFindUser("SELECT * from users_table where name=?"); + conf.setSqlChangePassword("UPDATE users_table SET password=? WHERE name=?"); + + savePasswordManagement("JDBCPasswordManagementTest", conf); + } + + @Test + public void saveWithRESTModule() { + RESTPasswordManagementConf conf = new RESTPasswordManagementConf(); + conf.setEndpointPassword("password"); + conf.setEndpointUrlAccountUnlock("http://localhost:9443/syncope-fit-build-tools/cxf/rest/unlockAccount"); + conf.setEndpointUrlChange("http://localhost:9443/syncope-fit-build-tools/cxf/rest/changePassword"); + conf.setEndpointUrlEmail("http://localhost:9443/syncope-fit-build-tools/cxf/rest/findEmail"); + conf.setEndpointUrlPhone("http://localhost:9443/syncope-fit-build-tools/cxf/rest/findPhone"); + conf.setEndpointUrlSecurityQuestions( + "http://localhost:9443/syncope-fit-build-tools/cxf/rest/securityQuestions"); + conf.setEndpointUsername("http://localhost:9443/syncope-fit-build-tools/cxf/rest/findUser"); + conf.setFieldNamePasswordOld("oldPassword"); + conf.setFieldNamePassword("password"); + conf.setEndpointUsername("username"); + + savePasswordManagement("RESTPasswordManagementTest", conf); + } + + @Test + public void updateWithSyncopeModule() { + PasswordManagement module = passwordManagementDAO.findById("DefaultSyncopePasswordManagement").orElseThrow(); + + PasswordManagementConf conf = module.getConf(); + SyncopePasswordManagementConf.class.cast(conf).setDomain("Two"); + module.setConf(conf); + + module = passwordManagementDAO.save(module); + assertNotNull(module); + assertNotNull(module.getKey()); + + PasswordManagement found = passwordManagementDAO.findById(module.getKey()).orElseThrow(); + assertEquals("Two", SyncopePasswordManagementConf.class.cast(found.getConf()).getDomain()); + } + + @Test + public void updateWithLDAPModule() { + PasswordManagement module = passwordManagementDAO.findById("DefaultLDAPPasswordManagement").orElseThrow(); + + PasswordManagementConf conf = module.getConf(); + LDAPPasswordManagementConf.class.cast(conf).setBaseDn("dc=example2,dc=org"); + LDAPPasswordManagementConf.class.cast(conf).setSearchFilter("cn={user2}"); + module.setConf(conf); + + module = passwordManagementDAO.save(module); + assertNotNull(module); + assertNotNull(module.getKey()); + + PasswordManagement found = passwordManagementDAO.findById(module.getKey()).orElseThrow(); + assertEquals("dc=example2,dc=org", LDAPPasswordManagementConf.class.cast(found.getConf()).getBaseDn()); + assertEquals("cn={user2}", LDAPPasswordManagementConf.class.cast(found.getConf()).getSearchFilter()); + } + + @Test + public void updateWithJDBCModule() { + PasswordManagement module = passwordManagementDAO.findById("DefaultJDBCPasswordManagement").orElseThrow(); + + PasswordManagementConf conf = module.getConf(); + JDBCPasswordManagementConf.class.cast(conf).setSqlFindUser("SELECT * from other_table where name=?"); + module.setConf(conf); + + module = passwordManagementDAO.save(module); + assertNotNull(module); + assertNotNull(module.getKey()); + + PasswordManagement found = passwordManagementDAO.findById(module.getKey()).orElseThrow(); + assertEquals("SELECT * from other_table where name=?", + JDBCPasswordManagementConf.class.cast(found.getConf()).getSqlFindUser()); + } + + @Test + public void updateWithRESTModule() { + PasswordManagement module = passwordManagementDAO.findById("DefaultRESTPasswordManagement").orElseThrow(); + + PasswordManagementConf conf = module.getConf(); + RESTPasswordManagementConf.class.cast(conf).setFieldNamePasswordOld("changedOldPassword"); + module.setConf(conf); + + module = passwordManagementDAO.save(module); + assertNotNull(module); + assertNotNull(module.getKey()); + + PasswordManagement found = passwordManagementDAO.findById(module.getKey()).orElseThrow(); + assertEquals("changedOldPassword", + RESTPasswordManagementConf.class.cast(found.getConf()).getFieldNamePasswordOld()); + } + + @Test + public void delete() { + assertTrue(passwordManagementDAO.findById("DefaultSyncopePasswordManagement").isPresent()); + + passwordManagementDAO.deleteById("DefaultSyncopePasswordManagement"); + + assertTrue(passwordManagementDAO.findById("DefaultSyncopePasswordManagement").isEmpty()); + } +} diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml index 2f7eaebc486..247abcb59f3 100644 --- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml +++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml @@ -90,6 +90,20 @@ under the License. + + + + + + @@ -112,7 +126,7 @@ under the License. - + @@ -158,7 +172,7 @@ under the License. - - + @@ -199,7 +213,7 @@ under the License. + mandatoryCondition="false" multivalue="0" uniqueConstraint="0" readonly="0"/> @@ -208,7 +222,7 @@ under the License. mandatoryCondition="false" multivalue="0" uniqueConstraint="0" readonly="0"/> + mandatoryCondition="false" multivalue="0" uniqueConstraint="0" readonly="0"/> @@ -238,7 +252,7 @@ under the License. mandatoryCondition="false" multivalue="0" uniqueConstraint="0" readonly="0"/> + mandatoryCondition="false" multivalue="0" uniqueConstraint="0" readonly="0"/> @@ -246,9 +260,9 @@ under the License. - - + @@ -258,29 +272,29 @@ under the License. - - - + @@ -291,13 +305,13 @@ under the License. - + - + @@ -336,83 +350,83 @@ under the License. - + - + - + + creator="admin" lastModifier="admin" + creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/> @@ -420,9 +434,9 @@ under the License. - + @@ -441,13 +455,13 @@ under the License. user_id="74cd8ece-715a-44a4-a736-e17b46c4e7e6" group_id="29f96485-729e-4d31-88a1-6fc60e4677f3"/> - + - @@ -456,7 +470,7 @@ under the License. - - + - + - - - - - + - + - - - - - + - + @@ -683,7 +697,7 @@ under the License. - + - + - - - + + diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java index 9544c68fcd3..1acec809aac 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java @@ -59,6 +59,7 @@ import org.apache.syncope.core.persistence.api.dao.NotificationDAO; import org.apache.syncope.core.persistence.api.dao.OIDCJWKSDAO; import org.apache.syncope.core.persistence.api.dao.OIDCRPClientAppDAO; +import org.apache.syncope.core.persistence.api.dao.PasswordManagementDAO; import org.apache.syncope.core.persistence.api.dao.PersistenceInfoDAO; import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; import org.apache.syncope.core.persistence.api.dao.PolicyDAO; @@ -160,6 +161,9 @@ import org.apache.syncope.core.persistence.neo4j.dao.repo.OIDCRPClientAppRepo; import org.apache.syncope.core.persistence.neo4j.dao.repo.OIDCRPClientAppRepoExt; import org.apache.syncope.core.persistence.neo4j.dao.repo.OIDCRPClientAppRepoExtImpl; +import org.apache.syncope.core.persistence.neo4j.dao.repo.PasswordManagementRepo; +import org.apache.syncope.core.persistence.neo4j.dao.repo.PasswordManagementRepoExt; +import org.apache.syncope.core.persistence.neo4j.dao.repo.PasswordManagementRepoExtImpl; import org.apache.syncope.core.persistence.neo4j.dao.repo.PlainSchemaRepo; import org.apache.syncope.core.persistence.neo4j.dao.repo.PlainSchemaRepoExt; import org.apache.syncope.core.persistence.neo4j.dao.repo.PlainSchemaRepoExtImpl; @@ -682,6 +686,25 @@ public AuthModuleDAO authModuleDAO( return neo4jRepositoryFactory.getRepository(AuthModuleRepo.class, authModuleRepoExt); } + @ConditionalOnMissingBean + @Bean + public PasswordManagementRepoExt passwordManagementRepoExt( + final Neo4jTemplate neo4jTemplate, + final Neo4jClient neo4jClient, + final NodeValidator nodeValidator) { + + return new PasswordManagementRepoExtImpl(neo4jTemplate, neo4jClient, nodeValidator); + } + + @ConditionalOnMissingBean + @Bean + public PasswordManagementDAO passwordManagementDAO( + final SyncopeNeo4jRepositoryFactory neo4jRepositoryFactory, + final PasswordManagementRepoExt passwordManagementRepoExt) { + + return neo4jRepositoryFactory.getRepository(PasswordManagementRepo.class, passwordManagementRepoExt); + } + @ConditionalOnMissingBean @Bean public AuthProfileDAO authProfileDAO(final SyncopeNeo4jRepositoryFactory neo4jRepositoryFactory) { diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/PasswordManagementRepo.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/PasswordManagementRepo.java new file mode 100644 index 00000000000..c545718ff16 --- /dev/null +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/PasswordManagementRepo.java @@ -0,0 +1,27 @@ +/* + * 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.core.persistence.neo4j.dao.repo; + +import org.apache.syncope.core.persistence.api.dao.PasswordManagementDAO; +import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4JPasswordManagement; +import org.springframework.data.repository.ListCrudRepository; + +public interface PasswordManagementRepo + extends ListCrudRepository, PasswordManagementRepoExt, PasswordManagementDAO { +} diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/PasswordManagementRepoExt.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/PasswordManagementRepoExt.java new file mode 100644 index 00000000000..e9137492cb2 --- /dev/null +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/PasswordManagementRepoExt.java @@ -0,0 +1,30 @@ +/* + * 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.core.persistence.neo4j.dao.repo; + +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; + +public interface PasswordManagementRepoExt { + + boolean isAnotherInstanceEnabled(String key); + + PasswordManagement save(PasswordManagement passwordManagement); + + void delete(PasswordManagement passwordManagement); +} diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/PasswordManagementRepoExtImpl.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/PasswordManagementRepoExtImpl.java new file mode 100644 index 00000000000..674cf0f1247 --- /dev/null +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/PasswordManagementRepoExtImpl.java @@ -0,0 +1,71 @@ +/* + * 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.core.persistence.neo4j.dao.repo; + +import java.util.Map; +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; +import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4JPasswordManagement; +import org.apache.syncope.core.persistence.neo4j.spring.NodeValidator; +import org.springframework.data.neo4j.core.Neo4jClient; +import org.springframework.data.neo4j.core.Neo4jTemplate; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +public class PasswordManagementRepoExtImpl implements PasswordManagementRepoExt { + + protected final Neo4jTemplate neo4jTemplate; + + protected final Neo4jClient neo4jClient; + + protected final NodeValidator nodeValidator; + + public PasswordManagementRepoExtImpl( + final Neo4jTemplate neo4jTemplate, + final Neo4jClient neo4jClient, + final NodeValidator nodeValidator) { + + this.neo4jTemplate = neo4jTemplate; + this.neo4jClient = neo4jClient; + this.nodeValidator = nodeValidator; + } + + @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) + @Override + public boolean isAnotherInstanceEnabled(final String key) { + Long count = neo4jClient.query( + "MATCH (pm:" + Neo4JPasswordManagement.NODE + ") " + + "WHERE pm.enabled = 'true' AND pm.id <> $id " + + "RETURN count(pm) AS cnt"). + bindAll(Map.of("id", key)).fetch().one(). + map(record -> ((Number) record.get("cnt")).longValue()). + orElse(0L); + + return count > 0; + } + + @Override + public PasswordManagement save(final PasswordManagement passwordManagement) { + return neo4jTemplate.save(nodeValidator.validate(passwordManagement)); + } + + @Override + public void delete(final PasswordManagement passwordManagement) { + neo4jTemplate.deleteById(passwordManagement.getKey(), Neo4JPasswordManagement.class); + } +} diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jEntityFactory.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jEntityFactory.java index b8f71ea9130..5cf942a10f7 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jEntityFactory.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jEntityFactory.java @@ -54,6 +54,7 @@ import org.apache.syncope.core.persistence.api.entity.am.CASSPClientApp; import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS; import org.apache.syncope.core.persistence.api.entity.am.OIDCRPClientApp; +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; import org.apache.syncope.core.persistence.api.entity.am.SAML2IdPEntity; import org.apache.syncope.core.persistence.api.entity.am.SAML2SPClientApp; import org.apache.syncope.core.persistence.api.entity.am.WAConfigEntry; @@ -94,6 +95,7 @@ import org.apache.syncope.core.persistence.api.entity.user.UMembership; import org.apache.syncope.core.persistence.api.entity.user.URelationship; import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4JPasswordManagement; import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jAttrRepo; import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jAuthModule; import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jAuthProfile; @@ -265,6 +267,8 @@ public E newEntity(final Class reference) { result = (E) new Neo4jAuthModule(); } else if (reference.equals(AttrRepo.class)) { result = (E) new Neo4jAttrRepo(); + } else if (reference.equals(PasswordManagement.class)) { + result = (E) new Neo4JPasswordManagement(); } else if (reference.equals(AuthPolicy.class)) { result = (E) new Neo4jAuthPolicy(); } else if (reference.equals(AccessPolicy.class)) { diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4JPasswordManagement.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4JPasswordManagement.java new file mode 100644 index 00000000000..e1bcca8581e --- /dev/null +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4JPasswordManagement.java @@ -0,0 +1,79 @@ +/* + * 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.core.persistence.neo4j.entity.am; + +import jakarta.validation.constraints.NotNull; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.password.PasswordManagementConf; +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; +import org.apache.syncope.core.persistence.common.validation.PasswordManagementCheck; +import org.apache.syncope.core.persistence.neo4j.entity.AbstractProvidedKeyNode; +import org.apache.syncope.core.provisioning.api.serialization.POJOHelper; +import org.springframework.data.neo4j.core.schema.Node; + +@Node(Neo4JPasswordManagement.NODE) +@PasswordManagementCheck +public class Neo4JPasswordManagement extends AbstractProvidedKeyNode implements PasswordManagement { + + private static final long serialVersionUID = 5457779846065079998L; + + public static final String NODE = "PasswordManagement"; + + private String description; + + @NotNull + private boolean enabled; + + private String jsonConf; + + @Override + public String getDescription() { + return description; + } + + @Override + public void setDescription(final String description) { + this.description = description; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + @Override + public PasswordManagementConf getConf() { + PasswordManagementConf conf = null; + if (!StringUtils.isBlank(jsonConf)) { + conf = POJOHelper.deserialize(jsonConf, PasswordManagementConf.class); + } + + return conf; + } + + @Override + public void setConf(final PasswordManagementConf conf) { + jsonConf = POJOHelper.serialize(conf); + } +} diff --git a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/PasswordManagementTest.java b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/PasswordManagementTest.java new file mode 100644 index 00000000000..ab03275a80f --- /dev/null +++ b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/PasswordManagementTest.java @@ -0,0 +1,230 @@ +/* + * 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.core.persistence.neo4j.inner; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.apache.commons.lang3.ClassUtils; +import org.apache.syncope.common.lib.password.JDBCPasswordManagementConf; +import org.apache.syncope.common.lib.password.LDAPPasswordManagementConf; +import org.apache.syncope.common.lib.password.PasswordManagementConf; +import org.apache.syncope.common.lib.password.RESTPasswordManagementConf; +import org.apache.syncope.common.lib.password.SyncopePasswordManagementConf; +import org.apache.syncope.core.persistence.api.dao.PasswordManagementDAO; +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; +import org.apache.syncope.core.persistence.neo4j.AbstractTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +public class PasswordManagementTest extends AbstractTest { + + private static boolean isSpecificConf( + final PasswordManagementConf conf, + final Class clazz) { + + return ClassUtils.isAssignable(clazz, conf.getClass()); + } + + @Autowired + private PasswordManagementDAO passwordManagementDAO; + + @Test + public void findAll() { + List modules = passwordManagementDAO.findAll(); + assertNotNull(modules); + assertFalse(modules.isEmpty()); + assertEquals(4, modules.size()); + } + + @Test + public void find() { + PasswordManagement passwordManagement = passwordManagementDAO.findById("DefaultSyncopePasswordManagement") + .orElseThrow(); + assertInstanceOf(SyncopePasswordManagementConf.class, passwordManagement.getConf()); + + passwordManagement = passwordManagementDAO.findById("DefaultLDAPPasswordManagement").orElseThrow(); + assertInstanceOf(LDAPPasswordManagementConf.class, passwordManagement.getConf()); + + passwordManagement = passwordManagementDAO.findById("DefaultJDBCPasswordManagement").orElseThrow(); + assertInstanceOf(JDBCPasswordManagementConf.class, passwordManagement.getConf()); + } + + @Test + public void findByType() { + List passwordManagements = passwordManagementDAO.findAll(); + assertTrue(passwordManagements.stream().anyMatch( + passwordManagement -> isSpecificConf(passwordManagement.getConf(), + SyncopePasswordManagementConf.class) + && passwordManagement.getKey().equals("DefaultSyncopePasswordManagement"))); + assertTrue(passwordManagements.stream().anyMatch( + passwordManagement -> isSpecificConf(passwordManagement.getConf(), + LDAPPasswordManagementConf.class) + && passwordManagement.getKey().equals("DefaultLDAPPasswordManagement"))); + assertTrue(passwordManagements.stream().anyMatch( + passwordManagement -> isSpecificConf(passwordManagement.getConf(), JDBCPasswordManagementConf.class) + && passwordManagement.getKey().equals("DefaultJDBCPasswordManagement"))); + } + + private void savePasswordManagement(final String key, final PasswordManagementConf conf) { + PasswordManagement module = entityFactory.newEntity(PasswordManagement.class); + module.setKey(key); + module.setDescription("A password management module"); + module.setConf(conf); + module.setEnabled(false); + module = passwordManagementDAO.save(module); + + assertNotNull(module); + assertNotNull(module.getKey()); + assertEquals(module, passwordManagementDAO.findById(module.getKey()).orElseThrow()); + } + + @Test + public void saveWithSyncopeModule() { + SyncopePasswordManagementConf conf = new SyncopePasswordManagementConf(); + conf.setDomain("Master"); + + savePasswordManagement("SyncopePasswordManagementTest", conf); + } + + @Test + public void saveWithLDAPModule() { + LDAPPasswordManagementConf conf = new LDAPPasswordManagementConf(); + conf.setBaseDn("dc=example,dc=org"); + conf.setSearchFilter("cn={user}"); + conf.setSubtreeSearch(true); + conf.setLdapUrl("ldap://localhost:1389"); + conf.setUsernameAttribute("uid"); + conf.setBindCredential("Password"); + + savePasswordManagement("LDAPPasswordManagementTest", conf); + } + + @Test + public void saveWithJDBCModule() { + JDBCPasswordManagementConf conf = new JDBCPasswordManagementConf(); + conf.setSqlFindEmail("SELECT email from users_table where name=?"); + conf.setSqlFindPhone("SELECT phoneNumber from users_table where name=?"); + conf.setSqlFindUser("SELECT * from users_table where name=?"); + conf.setSqlChangePassword("UPDATE users_table SET password=? WHERE name=?"); + + savePasswordManagement("JDBCPasswordManagementTest", conf); + } + + @Test + public void saveWithRESTModule() { + RESTPasswordManagementConf conf = new RESTPasswordManagementConf(); + conf.setEndpointPassword("password"); + conf.setEndpointUrlAccountUnlock("http://localhost:9443/syncope-fit-build-tools/cxf/rest/unlockAccount"); + conf.setEndpointUrlChange("http://localhost:9443/syncope-fit-build-tools/cxf/rest/changePassword"); + conf.setEndpointUrlEmail("http://localhost:9443/syncope-fit-build-tools/cxf/rest/findEmail"); + conf.setEndpointUrlPhone("http://localhost:9443/syncope-fit-build-tools/cxf/rest/findPhone"); + conf.setEndpointUrlSecurityQuestions( + "http://localhost:9443/syncope-fit-build-tools/cxf/rest/securityQuestions"); + conf.setEndpointUsername("http://localhost:9443/syncope-fit-build-tools/cxf/rest/findUser"); + conf.setFieldNamePasswordOld("oldPassword"); + conf.setFieldNamePassword("password"); + conf.setEndpointUsername("username"); + + savePasswordManagement("RESTPasswordManagementTest", conf); + } + + @Test + public void updateWithSyncopeModule() { + PasswordManagement module = passwordManagementDAO.findById("DefaultSyncopePasswordManagement").orElseThrow(); + + PasswordManagementConf conf = module.getConf(); + SyncopePasswordManagementConf.class.cast(conf).setDomain("Two"); + module.setConf(conf); + + module = passwordManagementDAO.save(module); + assertNotNull(module); + assertNotNull(module.getKey()); + + PasswordManagement found = passwordManagementDAO.findById(module.getKey()).orElseThrow(); + assertEquals("Two", SyncopePasswordManagementConf.class.cast(found.getConf()).getDomain()); + } + + @Test + public void updateWithLDAPModule() { + PasswordManagement module = passwordManagementDAO.findById("DefaultLDAPPasswordManagement").orElseThrow(); + + PasswordManagementConf conf = module.getConf(); + LDAPPasswordManagementConf.class.cast(conf).setBaseDn("dc=example2,dc=org"); + LDAPPasswordManagementConf.class.cast(conf).setSearchFilter("cn={user2}"); + module.setConf(conf); + + module = passwordManagementDAO.save(module); + assertNotNull(module); + assertNotNull(module.getKey()); + + PasswordManagement found = passwordManagementDAO.findById(module.getKey()).orElseThrow(); + assertEquals("dc=example2,dc=org", LDAPPasswordManagementConf.class.cast(found.getConf()).getBaseDn()); + assertEquals("cn={user2}", LDAPPasswordManagementConf.class.cast(found.getConf()).getSearchFilter()); + } + + @Test + public void updateWithJDBCModule() { + PasswordManagement module = passwordManagementDAO.findById("DefaultJDBCPasswordManagement").orElseThrow(); + + PasswordManagementConf conf = module.getConf(); + JDBCPasswordManagementConf.class.cast(conf).setSqlFindUser("SELECT * from other_table where name=?"); + module.setConf(conf); + + module = passwordManagementDAO.save(module); + assertNotNull(module); + assertNotNull(module.getKey()); + + PasswordManagement found = passwordManagementDAO.findById(module.getKey()).orElseThrow(); + assertEquals("SELECT * from other_table where name=?", + JDBCPasswordManagementConf.class.cast(found.getConf()).getSqlFindUser()); + } + + @Test + public void updateWithRESTModule() { + PasswordManagement module = passwordManagementDAO.findById("DefaultRESTPasswordManagement").orElseThrow(); + + PasswordManagementConf conf = module.getConf(); + RESTPasswordManagementConf.class.cast(conf).setFieldNamePasswordOld("changedOldPassword"); + module.setConf(conf); + + module = passwordManagementDAO.save(module); + assertNotNull(module); + assertNotNull(module.getKey()); + + PasswordManagement found = passwordManagementDAO.findById(module.getKey()).orElseThrow(); + assertEquals("changedOldPassword", + RESTPasswordManagementConf.class.cast(found.getConf()).getFieldNamePasswordOld()); + } + + @Test + public void delete() { + assertTrue(passwordManagementDAO.findById("DefaultSyncopePasswordManagement").isPresent()); + + passwordManagementDAO.deleteById("DefaultSyncopePasswordManagement"); + + assertTrue(passwordManagementDAO.findById("DefaultSyncopePasswordManagement").isEmpty()); + } +} diff --git a/core/persistence-neo4j/src/test/resources/domains/MasterContent.xml b/core/persistence-neo4j/src/test/resources/domains/MasterContent.xml index 35daea62852..151893a38ba 100644 --- a/core/persistence-neo4j/src/test/resources/domains/MasterContent.xml +++ b/core/persistence-neo4j/src/test/resources/domains/MasterContent.xml @@ -90,6 +90,20 @@ under the License. + + + + + + diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/PasswordManagementDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/PasswordManagementDataBinder.java new file mode 100644 index 00000000000..b3b09e0c6a9 --- /dev/null +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/PasswordManagementDataBinder.java @@ -0,0 +1,31 @@ +/* + * 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.core.provisioning.api.data; + +import org.apache.syncope.common.lib.to.PasswordManagementTO; +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; + +public interface PasswordManagementDataBinder { + + PasswordManagement create(PasswordManagementTO passwordManagementTO); + + PasswordManagement update(PasswordManagement passwordManagement, PasswordManagementTO passwordManagementTO); + + PasswordManagementTO getPasswordManagementTO(PasswordManagement passwordManagement); +} diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java index d16b7d349b7..3f2980226a7 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java @@ -88,6 +88,7 @@ import org.apache.syncope.core.provisioning.api.data.ImplementationDataBinder; import org.apache.syncope.core.provisioning.api.data.NotificationDataBinder; import org.apache.syncope.core.provisioning.api.data.OIDCJWKSDataBinder; +import org.apache.syncope.core.provisioning.api.data.PasswordManagementDataBinder; import org.apache.syncope.core.provisioning.api.data.PolicyDataBinder; import org.apache.syncope.core.provisioning.api.data.RealmDataBinder; import org.apache.syncope.core.provisioning.api.data.RelationshipTypeDataBinder; @@ -125,6 +126,7 @@ import org.apache.syncope.core.provisioning.java.data.ImplementationDataBinderImpl; import org.apache.syncope.core.provisioning.java.data.NotificationDataBinderImpl; import org.apache.syncope.core.provisioning.java.data.OIDCJWKSDataBinderImpl; +import org.apache.syncope.core.provisioning.java.data.PasswordManagementDataBinderImpl; import org.apache.syncope.core.provisioning.java.data.PolicyDataBinderImpl; import org.apache.syncope.core.provisioning.java.data.RealmDataBinderImpl; import org.apache.syncope.core.provisioning.java.data.RelationshipTypeDataBinderImpl; @@ -763,6 +765,12 @@ public AttrRepoDataBinder attrRepoDataBinder(final EntityFactory entityFactory) return new AttrRepoDataBinderImpl(entityFactory); } + @ConditionalOnMissingBean + @Bean + public PasswordManagementDataBinder passwordManagementDataBinder(final EntityFactory entityFactory) { + return new PasswordManagementDataBinderImpl(entityFactory); + } + @ConditionalOnMissingBean @Bean public AuthProfileDataBinder authProfileDataBinder(final EntityFactory entityFactory) { diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PasswordManagementDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PasswordManagementDataBinderImpl.java new file mode 100644 index 00000000000..235a2036366 --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PasswordManagementDataBinderImpl.java @@ -0,0 +1,64 @@ +/* + * 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.core.provisioning.java.data; + +import org.apache.syncope.common.lib.to.PasswordManagementTO; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.am.PasswordManagement; +import org.apache.syncope.core.provisioning.api.data.PasswordManagementDataBinder; + +public class PasswordManagementDataBinderImpl implements PasswordManagementDataBinder { + + protected final EntityFactory entityFactory; + + public PasswordManagementDataBinderImpl(final EntityFactory entityFactory) { + this.entityFactory = entityFactory; + } + + @Override + public PasswordManagement create(final PasswordManagementTO passwordManagementTO) { + PasswordManagement passwordManagement = entityFactory.newEntity(PasswordManagement.class); + passwordManagement.setKey(passwordManagementTO.getKey()); + return update(passwordManagement, passwordManagementTO); + } + + @Override + public PasswordManagement update( + final PasswordManagement passwordManagement, + final PasswordManagementTO passwordManagementTO) { + + passwordManagement.setDescription(passwordManagementTO.getDescription()); + passwordManagement.setEnabled(passwordManagementTO.isEnabled()); + passwordManagement.setConf(passwordManagementTO.getConf()); + + return passwordManagement; + } + + @Override + public PasswordManagementTO getPasswordManagementTO(final PasswordManagement passwordManagement) { + PasswordManagementTO passwordManagementTO = new PasswordManagementTO(); + + passwordManagementTO.setKey(passwordManagement.getKey()); + passwordManagementTO.setDescription(passwordManagement.getDescription()); + passwordManagementTO.setEnabled(passwordManagement.isEnabled()); + passwordManagementTO.setConf(passwordManagement.getConf()); + + return passwordManagementTO; + } +} diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java index 266af6a5576..ade6ce52d60 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java @@ -148,6 +148,7 @@ import org.apache.syncope.common.rest.api.service.OIDCC4UIProviderService; import org.apache.syncope.common.rest.api.service.OIDCC4UIService; import org.apache.syncope.common.rest.api.service.OIDCJWKSService; +import org.apache.syncope.common.rest.api.service.PasswordManagementService; import org.apache.syncope.common.rest.api.service.PolicyService; import org.apache.syncope.common.rest.api.service.RealmService; import org.apache.syncope.common.rest.api.service.ReconciliationService; @@ -356,6 +357,8 @@ public void initialize(final ConfigurableApplicationContext ctx) { protected static AttrRepoService ATTR_REPO_SERVICE; + protected static PasswordManagementService PASSWORD_MANAGEMENT_SERVICE; + protected static SecurityQuestionService SECURITY_QUESTION_SERVICE; protected static ImplementationService IMPLEMENTATION_SERVICE; @@ -596,6 +599,7 @@ public static void restSetup() { CLIENT_APP_SERVICE = ADMIN_CLIENT.getService(ClientAppService.class); AUTH_MODULE_SERVICE = ADMIN_CLIENT.getService(AuthModuleService.class); ATTR_REPO_SERVICE = ADMIN_CLIENT.getService(AttrRepoService.class); + PASSWORD_MANAGEMENT_SERVICE = ADMIN_CLIENT.getService(PasswordManagementService.class); SAML2IDP_ENTITY_SERVICE = ADMIN_CLIENT.getService(SAML2IdPEntityService.class); AUTH_PROFILE_SERVICE = ADMIN_CLIENT.getService(AuthProfileService.class); OIDC_JWKS_SERVICE = ADMIN_CLIENT.getService(OIDCJWKSService.class); diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PasswordManagementITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PasswordManagementITCase.java new file mode 100644 index 00000000000..4be19614249 --- /dev/null +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PasswordManagementITCase.java @@ -0,0 +1,326 @@ +/* + * 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.fit.core; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import jakarta.ws.rs.core.Response; +import java.io.IOException; +import java.util.EnumSet; +import java.util.List; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.password.JDBCPasswordManagementConf; +import org.apache.syncope.common.lib.password.LDAPPasswordManagementConf; +import org.apache.syncope.common.lib.password.PasswordManagementConf; +import org.apache.syncope.common.lib.password.RESTPasswordManagementConf; +import org.apache.syncope.common.lib.password.SyncopePasswordManagementConf; +import org.apache.syncope.common.lib.to.PasswordManagementTO; +import org.apache.syncope.common.rest.api.service.PasswordManagementService; +import org.apache.syncope.fit.AbstractITCase; +import org.junit.jupiter.api.Test; + +public class PasswordManagementITCase extends AbstractITCase { + + private enum PasswordManagementSupportedType { + SYNCOPE, + LDAP, + JDBC, + REST + + }; + + private static PasswordManagementTO createPasswordManagement(final PasswordManagementTO passwordManagementTO) { + Response response = PASSWORD_MANAGEMENT_SERVICE.create(passwordManagementTO); + if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) { + Exception ex = CLIENT_FACTORY.getExceptionMapper().fromResponse(response); + if (ex != null) { + throw (RuntimeException) ex; + } + } + return getObject(response.getLocation(), PasswordManagementService.class, passwordManagementTO.getClass()); + } + + private static PasswordManagementTO buildPasswordManagementTO(final PasswordManagementSupportedType type) { + PasswordManagementTO passwordManagementTO = new PasswordManagementTO(); + passwordManagementTO.setKey("Test" + type + "PasswordManagement" + getUUIDString()); + passwordManagementTO.setDescription("A test " + type + " Password Management"); + + PasswordManagementConf conf = null; + switch (type) { + case SYNCOPE: + conf = new SyncopePasswordManagementConf(); + SyncopePasswordManagementConf.class.cast(conf).setDomain(SyncopeConstants.MASTER_DOMAIN); + break; + + case LDAP: + conf = new LDAPPasswordManagementConf(); + LDAPPasswordManagementConf.class.cast(conf).setBaseDn("dc=example,dc=org"); + LDAPPasswordManagementConf.class.cast(conf).setSearchFilter("cn={user}"); + LDAPPasswordManagementConf.class.cast(conf).setSubtreeSearch(true); + LDAPPasswordManagementConf.class.cast(conf).setLdapUrl("ldap://localhost:1389"); + LDAPPasswordManagementConf.class.cast(conf).setUsernameAttribute("uid"); + LDAPPasswordManagementConf.class.cast(conf).setBaseDn("cn=Directory Manager,dc=example,dc=org"); + LDAPPasswordManagementConf.class.cast(conf).setBindCredential("Password"); + break; + + case JDBC: + conf = new JDBCPasswordManagementConf(); + JDBCPasswordManagementConf.class.cast(conf) + .setSqlFindEmail("SELECT email from users_table where name=?"); + JDBCPasswordManagementConf.class.cast(conf) + .setSqlFindPhone("SELECT phoneNumber from users_table where name=?"); + JDBCPasswordManagementConf.class.cast(conf) + .setSqlFindUser("SELECT * from users_table where name=?"); + JDBCPasswordManagementConf.class.cast(conf) + .setSqlChangePassword("UPDATE users_table SET password=? WHERE name=?"); + break; + + case REST: + conf = new RESTPasswordManagementConf(); + RESTPasswordManagementConf.class.cast(conf).setEndpointPassword("password"); + RESTPasswordManagementConf.class.cast(conf).setEndpointUrlAccountUnlock( + "http://localhost:9443/syncope-fit-build-tools/cxf/rest/unlockAccount"); + RESTPasswordManagementConf.class.cast(conf).setEndpointUrlChange( + "http://localhost:9443/syncope-fit-build-tools/cxf/rest/changePassword"); + RESTPasswordManagementConf.class.cast(conf).setEndpointUrlEmail( + "http://localhost:9443/syncope-fit-build-tools/cxf/rest/findEmail"); + RESTPasswordManagementConf.class.cast(conf).setEndpointUrlPhone( + "http://localhost:9443/syncope-fit-build-tools/cxf/rest/findPhone"); + RESTPasswordManagementConf.class.cast(conf).setEndpointUrlSecurityQuestions( + "http://localhost:9443/syncope-fit-build-tools/cxf/rest/securityQuestions"); + RESTPasswordManagementConf.class.cast(conf).setEndpointUsername( + "http://localhost:9443/syncope-fit-build-tools/cxf/rest/findUser"); + RESTPasswordManagementConf.class.cast(conf).setFieldNamePasswordOld("oldPassword"); + RESTPasswordManagementConf.class.cast(conf).setFieldNamePassword("password"); + RESTPasswordManagementConf.class.cast(conf).setEndpointUsername("username"); + break; + + default: + } + passwordManagementTO.setConf(conf); + + return passwordManagementTO; + } + + private static boolean isSpecificConf( + final PasswordManagementConf conf, + final Class clazz) { + + return ClassUtils.isAssignable(clazz, conf.getClass()); + } + + @Test + public void list() { + List passwordManagementTOS = PASSWORD_MANAGEMENT_SERVICE.list(); + assertNotNull(passwordManagementTOS); + assertFalse(passwordManagementTOS.isEmpty()); + + assertTrue(passwordManagementTOS.stream().anyMatch( + passwordManagement -> isSpecificConf(passwordManagement.getConf(), + SyncopePasswordManagementConf.class) && passwordManagement.getKey() + .equals("DefaultSyncopePasswordManagement"))); + assertTrue(passwordManagementTOS.stream().anyMatch( + passwordManagement -> isSpecificConf(passwordManagement.getConf(), + LDAPPasswordManagementConf.class) && passwordManagement.getKey() + .equals("DefaultLDAPPasswordManagement"))); + assertTrue(passwordManagementTOS.stream().anyMatch( + passwordManagement -> isSpecificConf(passwordManagement.getConf(), + JDBCPasswordManagementConf.class) && passwordManagement.getKey() + .equals("DefaultJDBCPasswordManagement"))); + assertTrue(passwordManagementTOS.stream().anyMatch( + passwordManagement -> isSpecificConf(passwordManagement.getConf(), + RESTPasswordManagementConf.class) && passwordManagement.getKey() + .equals("DefaultRESTPasswordManagement"))); + } + + @Test + public void getSyncopePasswordManagement() { + PasswordManagementTO passwordManagementTO = + PASSWORD_MANAGEMENT_SERVICE.read("DefaultSyncopePasswordManagement"); + + assertNotNull(passwordManagementTO); + assertTrue(StringUtils.isNotBlank(passwordManagementTO.getDescription())); + assertTrue(isSpecificConf(passwordManagementTO.getConf(), SyncopePasswordManagementConf.class)); + } + + @Test + public void getLDAPPasswordManagement() { + PasswordManagementTO passwordManagementTO = + PASSWORD_MANAGEMENT_SERVICE.read("DefaultLDAPPasswordManagement"); + + assertNotNull(passwordManagementTO); + assertTrue(StringUtils.isNotBlank(passwordManagementTO.getDescription())); + assertTrue(isSpecificConf(passwordManagementTO.getConf(), LDAPPasswordManagementConf.class)); + } + + @Test + public void getJDBCPasswordManagement() { + PasswordManagementTO passwordManagementTO = + PASSWORD_MANAGEMENT_SERVICE.read("DefaultJDBCPasswordManagement"); + + assertNotNull(passwordManagementTO); + assertTrue(StringUtils.isNotBlank(passwordManagementTO.getDescription())); + assertTrue(isSpecificConf(passwordManagementTO.getConf(), JDBCPasswordManagementConf.class)); + } + + @Test + public void getRESTPasswordManagement() { + PasswordManagementTO passwordManagementTO = + PASSWORD_MANAGEMENT_SERVICE.read("DefaultRESTPasswordManagement"); + + assertNotNull(passwordManagementTO); + assertTrue(StringUtils.isNotBlank(passwordManagementTO.getDescription())); + assertTrue(isSpecificConf(passwordManagementTO.getConf(), RESTPasswordManagementConf.class)); + } + + @Test + public void create() { + EnumSet.allOf(PasswordManagementSupportedType.class).forEach(type -> { + PasswordManagementTO passwordManagementTO = createPasswordManagement(buildPasswordManagementTO(type)); + assertNotNull(passwordManagementTO); + assertTrue(passwordManagementTO.getDescription().contains("A test " + type + " Password Management")); + }); + } + + @Test + public void updateSyncopePasswordManagement() { + PasswordManagementTO syncopePasswordManagementTO = + PASSWORD_MANAGEMENT_SERVICE.read("DefaultSyncopePasswordManagement"); + assertNotNull(syncopePasswordManagementTO); + + PasswordManagementTO newSyncopePasswordManagementTO = + buildPasswordManagementTO(PasswordManagementSupportedType.SYNCOPE); + newSyncopePasswordManagementTO = createPasswordManagement(newSyncopePasswordManagementTO); + assertNotNull(newSyncopePasswordManagementTO); + + PasswordManagementConf conf = syncopePasswordManagementTO.getConf(); + assertNotNull(conf); + SyncopePasswordManagementConf.class.cast(conf).setDomain("Two"); + newSyncopePasswordManagementTO.setConf(conf); + + // update new password management + PASSWORD_MANAGEMENT_SERVICE.update(newSyncopePasswordManagementTO); + newSyncopePasswordManagementTO = PASSWORD_MANAGEMENT_SERVICE.read(newSyncopePasswordManagementTO.getKey()); + assertNotNull(newSyncopePasswordManagementTO); + + conf = newSyncopePasswordManagementTO.getConf(); + assertEquals("Two", SyncopePasswordManagementConf.class.cast(conf).getDomain()); + } + + @Test + public void updateLDAPPasswordManagement() { + PasswordManagementTO ldapPasswordManagementTO = + PASSWORD_MANAGEMENT_SERVICE.read("DefaultLDAPPasswordManagement"); + assertNotNull(ldapPasswordManagementTO); + + PasswordManagementTO newLdapPasswordManagementTO = + buildPasswordManagementTO(PasswordManagementSupportedType.LDAP); + newLdapPasswordManagementTO = createPasswordManagement(newLdapPasswordManagementTO); + assertNotNull(newLdapPasswordManagementTO); + + PasswordManagementConf conf = ldapPasswordManagementTO.getConf(); + assertNotNull(conf); + LDAPPasswordManagementConf.class.cast(conf).setSubtreeSearch(false); + newLdapPasswordManagementTO.setConf(conf); + + // update new password management + PASSWORD_MANAGEMENT_SERVICE.update(newLdapPasswordManagementTO); + newLdapPasswordManagementTO = PASSWORD_MANAGEMENT_SERVICE.read(newLdapPasswordManagementTO.getKey()); + assertNotNull(newLdapPasswordManagementTO); + + conf = newLdapPasswordManagementTO.getConf(); + assertFalse(LDAPPasswordManagementConf.class.cast(conf).isSubtreeSearch()); + } + + @Test + public void updateJDBCPasswordManagement() { + PasswordManagementTO jdbcPasswordManagementTO = + PASSWORD_MANAGEMENT_SERVICE.read("DefaultJDBCPasswordManagement"); + assertNotNull(jdbcPasswordManagementTO); + + PasswordManagementTO newJdbcPasswordManagementTO = + buildPasswordManagementTO(PasswordManagementSupportedType.JDBC); + newJdbcPasswordManagementTO = createPasswordManagement(newJdbcPasswordManagementTO); + assertNotNull(newJdbcPasswordManagementTO); + + PasswordManagementConf conf = jdbcPasswordManagementTO.getConf(); + assertNotNull(conf); + JDBCPasswordManagementConf.class.cast(conf).setSqlFindUser("SELECT * from other_table where name=?"); + newJdbcPasswordManagementTO.setConf(conf); + + // update new password management + PASSWORD_MANAGEMENT_SERVICE.update(newJdbcPasswordManagementTO); + newJdbcPasswordManagementTO = PASSWORD_MANAGEMENT_SERVICE.read(newJdbcPasswordManagementTO.getKey()); + assertNotNull(newJdbcPasswordManagementTO); + + conf = newJdbcPasswordManagementTO.getConf(); + assertEquals("SELECT * from other_table where name=?", + JDBCPasswordManagementConf.class.cast(conf).getSqlFindUser()); + } + + @Test + public void updateRESTPasswordManagement() { + PasswordManagementTO restPasswordManagementTO = + PASSWORD_MANAGEMENT_SERVICE.read("DefaultRESTPasswordManagement"); + assertNotNull(restPasswordManagementTO); + + PasswordManagementTO newRestPasswordManagementTO = + buildPasswordManagementTO(PasswordManagementSupportedType.REST); + newRestPasswordManagementTO = createPasswordManagement(newRestPasswordManagementTO); + assertNotNull(newRestPasswordManagementTO); + + PasswordManagementConf conf = restPasswordManagementTO.getConf(); + assertNotNull(conf); + RESTPasswordManagementConf.class.cast(conf).setFieldNamePasswordOld("changedOldPassword"); + newRestPasswordManagementTO.setConf(conf); + + // update new password management + PASSWORD_MANAGEMENT_SERVICE.update(newRestPasswordManagementTO); + newRestPasswordManagementTO = PASSWORD_MANAGEMENT_SERVICE.read(newRestPasswordManagementTO.getKey()); + assertNotNull(newRestPasswordManagementTO); + + conf = newRestPasswordManagementTO.getConf(); + assertEquals("changedOldPassword", + RESTPasswordManagementConf.class.cast(conf).getFieldNamePasswordOld()); + } + + @Test + public void delete() throws IOException { + EnumSet.allOf(PasswordManagementSupportedType.class).forEach(type -> { + PasswordManagementTO read = createPasswordManagement(buildPasswordManagementTO(type)); + assertNotNull(read); + + PASSWORD_MANAGEMENT_SERVICE.delete(read.getKey()); + + try { + PASSWORD_MANAGEMENT_SERVICE.read(read.getKey()); + fail("This should not happen"); + } catch (SyncopeClientException e) { + assertNotNull(e); + } + }); + } +} diff --git a/fit/wa-reference/src/main/resources/wa-embedded.properties b/fit/wa-reference/src/main/resources/wa-embedded.properties index 35d6c68bb99..3861744e87f 100644 --- a/fit/wa-reference/src/main/resources/wa-embedded.properties +++ b/fit/wa-reference/src/main/resources/wa-embedded.properties @@ -30,9 +30,6 @@ cas.authn.syncope.url=${cas.server.name}/syncope cas.authn.syncope.name=DefaultSyncopeAuthModule cas.authn.pm.core.enabled=true -# TMP until SYNCOPE-1901 -cas.authn.pm.syncope.basic-auth-username=placeholder -cas.authn.pm.syncope.basic-auth-password=placeholder service.discovery.address=https://localhost:9443/syncope-wa/ @@ -48,3 +45,4 @@ cas.tgc.crypto.encryption.key=mW6lMvsSo48eZ1Ntt74a-O9jjQQQ_OLUE24RVN2_A_sPX43mpB cas.webflow.crypto.signing.key=Md6kkPlXx5L18TD0mFELpQXWnDbMffj-uPutPckMnAPPuJQEbfcLLYBnOynYIEDgnEpd7sxUwGYd8_sVYFMcjw cas.webflow.crypto.encryption.key=FhLgLpaPL8GVNuqqo7gtiw + diff --git a/src/main/asciidoc/reference-guide/concepts/concepts.adoc b/src/main/asciidoc/reference-guide/concepts/concepts.adoc index d3d3606e5c5..b2e15b34220 100644 --- a/src/main/asciidoc/reference-guide/concepts/concepts.adoc +++ b/src/main/asciidoc/reference-guide/concepts/concepts.adoc @@ -50,6 +50,8 @@ include::authenticationmodules.adoc[] include::attributerepositories.adoc[] +include::passwordmanagement.adoc[] + include::clientapplications.adoc[] include::domains.adoc[] diff --git a/src/main/asciidoc/reference-guide/concepts/passwordmanagement.adoc b/src/main/asciidoc/reference-guide/concepts/passwordmanagement.adoc new file mode 100644 index 00000000000..b1e4a4014f9 --- /dev/null +++ b/src/main/asciidoc/reference-guide/concepts/passwordmanagement.adoc @@ -0,0 +1,52 @@ +// +// 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. +// +=== Password Management + +Password Management allows handling the password update flow of a user authenticated via <> +according to rules defined by a single configured password management module (only one module can be active at a time). + +Some password management modules are provided: + +* https://apereo.github.io/cas/7.2.x/password_management/Password-Management-LDAP.html[LDAP^] +* https://apereo.github.io/cas/7.2.x/password_management/Password-Management-JDBC.html[JDBC^] +* https://apereo.github.io/cas/7.2.x/password_management/Password-Management-REST.html[REST^] +* https://apereo.github.io/cas/7.2.x/password_management/Password-Management-ApacheSyncope.html[Syncope^] + +[TIP] +==== +Custom password management modules can be provided by implementing the +ifeval::["{snapshotOrRelease}" == "release"] +https://github.com/apache/syncope/blob/syncope-{docVersion}/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/PasswordManagementConf.java[PasswordManagementConf^] +endif::[] +ifeval::["{snapshotOrRelease}" == "snapshot"] +https://github.com/apache/syncope/blob/master/common/am/lib/src/main/java/org/apache/syncope/common/lib/password/PasswordManagementConf.java[PasswordManagementConf^] +endif::[] +interface and extending appropriately the +ifeval::["{snapshotOrRelease}" == "release"] +https://github.com/apache/syncope/blob/syncope-{docVersion}/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java[WAPropertySourceLocator^] +endif::[] +ifeval::["{snapshotOrRelease}" == "snapshot"] +https://github.com/apache/syncope/blob/master/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java[WAPropertySourceLocator^] +endif::[] +class. +==== + +[NOTE] +Password Management is dynamically translated into +https://apereo.github.io/cas/7.2.x/password_management/Password-Management.html[CAS Password Management^] configuration. diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WABootstrapConfiguration.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WABootstrapConfiguration.java index dcd0fb7a213..c78f86ae792 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WABootstrapConfiguration.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WABootstrapConfiguration.java @@ -22,6 +22,7 @@ import org.apache.syncope.wa.bootstrap.mapping.AttrRepoPropertySourceMapper; import org.apache.syncope.wa.bootstrap.mapping.AuthModulePropertySourceMapper; import org.apache.syncope.wa.bootstrap.mapping.DefaultAttrReleaseMapper; +import org.apache.syncope.wa.bootstrap.mapping.PasswordManagementPropertySourceMapper; import org.apereo.cas.configuration.support.CasConfigurationJasyptCipherExecutor; import org.apereo.cas.util.crypto.CipherExecutor; import org.springframework.beans.factory.annotation.Qualifier; @@ -80,6 +81,14 @@ public AttrRepoPropertySourceMapper attrRepoPropertySourceMapper(final WARestCli return new AttrRepoPropertySourceMapper(waRestClient); } + @ConditionalOnMissingBean + @Bean + public PasswordManagementPropertySourceMapper passwordManagementPropertySourceMapper( + final WARestClient waRestClient) { + + return new PasswordManagementPropertySourceMapper(waRestClient); + } + @ConditionalOnMissingBean @Bean public AttrReleaseMapper attrReleaseMapper() { @@ -93,12 +102,13 @@ public PropertySourceLocator configPropertySourceLocator( final WARestClient waRestClient, final AuthModulePropertySourceMapper authModulePropertySourceMapper, final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper, + final PasswordManagementPropertySourceMapper passwordManagementPropertySourceMapper, final AttrReleaseMapper attrReleaseMapper) { return new WAPropertySourceLocator( waRestClient, authModulePropertySourceMapper, - attrRepoPropertySourceMapper, + attrRepoPropertySourceMapper, passwordManagementPropertySourceMapper, attrReleaseMapper, waConfigurationCipher); } diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java index 120652772a8..6f3f3e7a087 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAPropertySourceLocator.java @@ -29,13 +29,16 @@ import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; +import org.apache.syncope.common.lib.to.PasswordManagementTO; import org.apache.syncope.common.rest.api.service.AttrRepoService; import org.apache.syncope.common.rest.api.service.AuthModuleService; +import org.apache.syncope.common.rest.api.service.PasswordManagementService; import org.apache.syncope.common.rest.api.service.wa.WAClientAppService; import org.apache.syncope.common.rest.api.service.wa.WAConfigService; import org.apache.syncope.wa.bootstrap.mapping.AttrReleaseMapper; import org.apache.syncope.wa.bootstrap.mapping.AttrRepoPropertySourceMapper; import org.apache.syncope.wa.bootstrap.mapping.AuthModulePropertySourceMapper; +import org.apache.syncope.wa.bootstrap.mapping.PasswordManagementPropertySourceMapper; import org.apereo.cas.configuration.model.support.oidc.OidcDiscoveryProperties; import org.apereo.cas.oidc.claims.OidcCustomScopeAttributeReleasePolicy; import org.apereo.cas.services.ChainingAttributeReleasePolicy; @@ -60,6 +63,8 @@ public class WAPropertySourceLocator implements PropertySourceLocator { protected final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper; + protected final PasswordManagementPropertySourceMapper passwordManagementPropertySourceMapper; + protected final AttrReleaseMapper attrReleaseMapper; protected final CipherExecutor configurationCipher; @@ -68,12 +73,14 @@ public WAPropertySourceLocator( final WARestClient waRestClient, final AuthModulePropertySourceMapper authModulePropertySourceMapper, final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper, + final PasswordManagementPropertySourceMapper passwordManagementPropertySourceMapper, final AttrReleaseMapper attrReleaseMapper, final CipherExecutor configurationCipher) { this.waRestClient = waRestClient; this.authModulePropertySourceMapper = authModulePropertySourceMapper; this.attrRepoPropertySourceMapper = attrRepoPropertySourceMapper; + this.passwordManagementPropertySourceMapper = passwordManagementPropertySourceMapper; this.attrReleaseMapper = attrReleaseMapper; this.configurationCipher = configurationCipher; } @@ -124,8 +131,14 @@ public PropertySource locate(final Environment environment) { properties.putAll(index(map, prefixes)); }); - properties.put("cas.authn.pm.syncope.url", - StringUtils.substringBefore(syncopeClient.getAddress(), "/rest")); + syncopeClient.getService(PasswordManagementService.class).list().stream(). + filter(PasswordManagementTO::isEnabled).findFirst().ifPresent(passwordManagementTO -> { + LOG.debug("Mapping password module {} ", passwordManagementTO.getKey()); + + Map map = passwordManagementTO.getConf(). + map(passwordManagementTO, passwordManagementPropertySourceMapper); + properties.putAll(index(map, prefixes)); + }); Set customClaims = syncopeClient.getService(WAClientAppService.class).list().stream(). filter(app -> app.getClientAppTO() instanceof OIDCRPClientAppTO && app.getAttrReleasePolicy() != null). @@ -154,7 +167,7 @@ public PropertySource locate(final Environment environment) { Stream.concat(new OidcDiscoveryProperties().getClaims().stream(), customClaims.stream()). collect(Collectors.joining(","))); properties.put("cas.authn.oidc.core.user-defined-scopes.syncope", - String.join(",", customClaims)); + String.join(",", customClaims)); } syncopeClient.getService(WAConfigService.class).list().forEach(attr -> properties.put( diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/PasswordManagementPropertySourceMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/PasswordManagementPropertySourceMapper.java new file mode 100644 index 00000000000..d1dbbc5cd97 --- /dev/null +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/PasswordManagementPropertySourceMapper.java @@ -0,0 +1,131 @@ +/* + * 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.wa.bootstrap.mapping; + +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.lib.SyncopeClient; +import org.apache.syncope.common.lib.password.JDBCPasswordManagementConf; +import org.apache.syncope.common.lib.password.LDAPPasswordManagementConf; +import org.apache.syncope.common.lib.password.PasswordManagementConf; +import org.apache.syncope.common.lib.password.RESTPasswordManagementConf; +import org.apache.syncope.common.lib.password.SyncopePasswordManagementConf; +import org.apache.syncope.common.lib.to.PasswordManagementTO; +import org.apache.syncope.wa.bootstrap.WARestClient; +import org.apereo.cas.configuration.model.support.ldap.AbstractLdapProperties; +import org.apereo.cas.configuration.model.support.pm.JdbcPasswordManagementProperties; +import org.apereo.cas.configuration.model.support.pm.LdapPasswordManagementProperties; +import org.apereo.cas.configuration.model.support.pm.RestfulPasswordManagementProperties; +import org.apereo.cas.configuration.model.support.pm.SyncopePasswordManagementProperties; + +public class PasswordManagementPropertySourceMapper extends PropertySourceMapper + implements PasswordManagementConf.Mapper { + + protected final WARestClient waRestClient; + + public PasswordManagementPropertySourceMapper(final WARestClient waRestClient) { + this.waRestClient = waRestClient; + } + + @Override + public Map map( + final PasswordManagementTO passwordManagementTO, + final SyncopePasswordManagementConf conf) { + + SyncopeClient syncopeClient = waRestClient.getSyncopeClient(); + if (syncopeClient == null) { + LOG.warn("Application context is not ready to bootstrap WA configuration"); + return Map.of(); + } + + SyncopePasswordManagementProperties props = new SyncopePasswordManagementProperties(); + props.setDomain(conf.getDomain()); + props.setUrl(StringUtils.substringBefore(syncopeClient.getAddress(), "/rest")); + props.setBasicAuthUsername(conf.getBasicAuthUsername()); + props.setBasicAuthPassword(conf.getBasicAuthPassword()); + props.setSearchFilter(conf.getSearchFilter()); + props.setHeaders(conf.getHeaders()); + + Map mapped = prefix("cas.authn.pm.syncope.", WAConfUtils.asMap(props)); + mapped.put("cas.authn.pm.syncope.enabled", passwordManagementTO.isEnabled()); + return mapped; + } + + @Override + public Map map( + final PasswordManagementTO passwordManagementTO, + final LDAPPasswordManagementConf conf) { + + LdapPasswordManagementProperties props = new LdapPasswordManagementProperties(); + props.setName(passwordManagementTO.getKey()); + props.setType(AbstractLdapProperties.LdapType.valueOf(conf.getLdapType().name())); + props.setUsernameAttribute(conf.getUsernameAttribute()); + + fill(props, conf); + + Map mapped = prefix("cas.authn.pm.ldap[].", WAConfUtils.asMap(props)); + mapped.put("cas.authn.pm.ldap.enabled", passwordManagementTO.isEnabled()); + return mapped; + } + + @Override + public Map map( + final PasswordManagementTO passwordManagementTO, + final JDBCPasswordManagementConf conf) { + + JdbcPasswordManagementProperties props = new JdbcPasswordManagementProperties(); + props.setSqlChangePassword(conf.getSqlChangePassword()); + props.setSqlFindEmail(conf.getSqlFindEmail()); + props.setSqlFindPhone(conf.getSqlFindPhone()); + props.setSqlFindUser(conf.getSqlFindUser()); + props.setSqlGetSecurityQuestions(conf.getSqlGetSecurityQuestions()); + props.setSqlUpdateSecurityQuestions(conf.getSqlUpdateSecurityQuestions()); + props.setSqlDeleteSecurityQuestions(conf.getSqlDeleteSecurityQuestions()); + props.setSqlUnlockAccount(conf.getSqlUnlockAccount()); + fill(props, conf); + + Map mapped = prefix("cas.authn.pm.jdbc.", WAConfUtils.asMap(props)); + mapped.put("cas.authn.pm.jdbc.enabled", passwordManagementTO.isEnabled()); + return mapped; + } + + @Override + public Map map( + final PasswordManagementTO passwordManagementTO, + final RESTPasswordManagementConf conf) { + + RestfulPasswordManagementProperties props = new RestfulPasswordManagementProperties(); + props.setEndpointPassword(conf.getEndpointPassword()); + props.setEndpointUrlAccountUnlock(conf.getEndpointUrlAccountUnlock()); + props.setEndpointUrlChange(conf.getEndpointUrlChange()); + props.setEndpointUrlEmail(conf.getEndpointUrlEmail()); + props.setEndpointUrlPhone(conf.getEndpointUrlPhone()); + props.setEndpointUrlSecurityQuestions(conf.getEndpointUrlSecurityQuestions()); + props.setEndpointUrlUser(conf.getEndpointUrlUser()); + props.setEndpointUsername(conf.getEndpointUsername()); + props.setFieldNamePasswordOld(conf.getFieldNamePasswordOld()); + props.setFieldNamePassword(conf.getFieldNamePassword()); + props.setFieldNameUser(conf.getFieldNameUser()); + props.setHeaders(conf.getHeaders()); + + Map mapped = prefix("cas.authn.pm.rest.", WAConfUtils.asMap(props)); + mapped.put("cas.authn.pm.rest.enabled", passwordManagementTO.isEnabled()); + return mapped; + } +} diff --git a/wa/starter/pom.xml b/wa/starter/pom.xml index 63f2bf26889..bdc89aea15f 100644 --- a/wa/starter/pom.xml +++ b/wa/starter/pom.xml @@ -414,6 +414,23 @@ under the License. pac4j-oidc + + org.apereo.cas + cas-server-support-pm-core + + + org.apereo.cas + cas-server-support-pm-ldap + + + org.apereo.cas + cas-server-support-pm-jdbc + + + org.apereo.cas + cas-server-support-pm-rest + + org.springframework.boot spring-boot-starter-web diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java index fb9cdfe8c05..5a438833b50 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java @@ -29,6 +29,7 @@ import org.apereo.cas.support.saml.idp.metadata.generator.SamlIdPMetadataGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthContributorAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; @@ -65,12 +66,13 @@ GsonAutoConfiguration.class, JmxAutoConfiguration.class, DataSourceAutoConfiguration.class, + DataSourceHealthContributorAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, RedisAutoConfiguration.class, + RedisRepositoriesAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, CassandraAutoConfiguration.class, - DataSourceTransactionManagerAutoConfiguration.class, - RedisRepositoriesAutoConfiguration.class, CasGoogleAuthenticatorLdapAutoConfiguration.class }) @EnableConfigurationProperties({ WAProperties.class, CasConfigurationProperties.class }) diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java index 0fe67e57fb3..a328f98b2af 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java @@ -27,7 +27,10 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import javax.sql.DataSource; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.common.keymaster.client.api.model.NetworkService; import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart; @@ -69,13 +72,21 @@ import org.apereo.cas.audit.AuditTrailExecutionPlanConfigurer; import org.apereo.cas.authentication.AuthenticationEventExecutionPlan; import org.apereo.cas.authentication.MultifactorAuthenticationProvider; +import org.apereo.cas.authentication.support.password.PasswordEncoderUtils; import org.apereo.cas.authentication.surrogate.SurrogateAuthenticationService; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.configuration.model.support.mfa.gauth.LdapGoogleAuthenticatorMultifactorProperties; +import org.apereo.cas.configuration.model.support.pm.PasswordManagementProperties; import org.apereo.cas.gauth.CasGoogleAuthenticator; import org.apereo.cas.gauth.credential.LdapGoogleAuthenticatorTokenCredentialRepository; import org.apereo.cas.oidc.jwks.generator.OidcJsonWebKeystoreGeneratorService; import org.apereo.cas.otp.repository.credentials.OneTimeTokenCredentialRepository; +import org.apereo.cas.pm.LdapPasswordManagementService; +import org.apereo.cas.pm.PasswordHistoryService; +import org.apereo.cas.pm.PasswordManagementService; +import org.apereo.cas.pm.impl.NoOpPasswordManagementService; +import org.apereo.cas.pm.jdbc.JdbcPasswordManagementService; +import org.apereo.cas.pm.rest.RestPasswordManagementService; import org.apereo.cas.services.ServiceRegistryExecutionPlanConfigurer; import org.apereo.cas.services.ServiceRegistryListener; import org.apereo.cas.support.events.CasEventRepository; @@ -86,6 +97,7 @@ import org.apereo.cas.support.saml.idp.metadata.generator.SamlIdPMetadataGeneratorConfigurationContext; import org.apereo.cas.support.saml.idp.metadata.locator.SamlIdPMetadataLocator; import org.apereo.cas.support.saml.services.idp.metadata.SamlIdPMetadataDocument; +import org.apereo.cas.syncope.pm.SyncopePasswordManagementService; import org.apereo.cas.trusted.authentication.api.MultifactorAuthenticationTrustRecordKeyGenerator; import org.apereo.cas.trusted.authentication.api.MultifactorAuthenticationTrustStorage; import org.apereo.cas.util.LdapUtils; @@ -107,7 +119,10 @@ import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.transaction.support.TransactionOperations; +import org.springframework.web.client.RestTemplate; @Configuration(proxyBeanMethods = false) public class WAContext { @@ -354,6 +369,139 @@ public OneTimeTokenCredentialRepository googleAuthenticatorAccountRegistry( return new WAGoogleMfaAuthCredentialRepository(waRestClient, googleAuthenticatorInstance); } + @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) + @Bean + public PasswordManagementService syncopePasswordChangeService( + final CasConfigurationProperties casProperties, + @Qualifier("passwordManagementCipherExecutor") + final CipherExecutor passwordManagementCipherExecutor, + @Qualifier(PasswordHistoryService.BEAN_NAME) + final PasswordHistoryService passwordHistoryService) { + + PasswordManagementProperties pm = casProperties.getAuthn().getPm(); + if (pm.getCore().isEnabled() && pm.getSyncope().isDefined()) { + return new SyncopePasswordManagementService( + passwordManagementCipherExecutor, + casProperties, + passwordHistoryService); + } + + return new NoOpPasswordManagementService(passwordManagementCipherExecutor, casProperties); + } + + @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) + @Bean + public PasswordManagementService ldapPasswordChangeService( + final CasConfigurationProperties casProperties, + @Qualifier("passwordManagementCipherExecutor") + final CipherExecutor passwordManagementCipherExecutor, + @Qualifier(PasswordHistoryService.BEAN_NAME) + final PasswordHistoryService passwordHistoryService) { + + PasswordManagementProperties pm = casProperties.getAuthn().getPm(); + if (pm.getCore().isEnabled() + && !pm.getLdap().isEmpty() && StringUtils.isNotBlank(pm.getLdap().getFirst().getLdapUrl())) { + + Map connectionFactoryMap = new ConcurrentHashMap<>(); + pm.getLdap().forEach(ldap -> connectionFactoryMap.put( + ldap.getLdapUrl(), + LdapUtils.newLdaptiveConnectionFactory(ldap))); + return new LdapPasswordManagementService( + passwordManagementCipherExecutor, + casProperties, + passwordHistoryService, + connectionFactoryMap); + } + + return new NoOpPasswordManagementService(passwordManagementCipherExecutor, casProperties); + } + + @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) + @Bean + public PasswordManagementService jdbcPasswordChangeService( + final CasConfigurationProperties casProperties, + final ConfigurableApplicationContext ctx, + @Qualifier("jdbcPasswordManagementDataSource") + final DataSource jdbcPasswordManagementDataSource, + @Qualifier("jdbcPasswordManagementTransactionTemplate") + final TransactionOperations jdbcPasswordManagementTransactionTemplate, + @Qualifier("passwordManagementCipherExecutor") + final CipherExecutor passwordManagementCipherExecutor, + @Qualifier(PasswordHistoryService.BEAN_NAME) + final PasswordHistoryService passwordHistoryService) { + + PasswordManagementProperties pm = casProperties.getAuthn().getPm(); + if (pm.getCore().isEnabled() && StringUtils.isNotBlank(pm.getJdbc().getUrl())) { + PasswordEncoder encoder = PasswordEncoderUtils.newPasswordEncoder( + pm.getJdbc().getPasswordEncoder(), ctx); + return new JdbcPasswordManagementService( + passwordManagementCipherExecutor, + casProperties, + jdbcPasswordManagementDataSource, + jdbcPasswordManagementTransactionTemplate, + passwordHistoryService, encoder); + } + + return new NoOpPasswordManagementService(passwordManagementCipherExecutor, casProperties); + } + + @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) + @Bean + public PasswordManagementService restPasswordChangeService( + @Qualifier("passwordChangeServiceRestTemplate") + final RestTemplate passwordChangeServiceRestTemplate, + final CasConfigurationProperties casProperties, + @Qualifier("passwordManagementCipherExecutor") + final CipherExecutor passwordManagementCipherExecutor, + @Qualifier(PasswordHistoryService.BEAN_NAME) + final PasswordHistoryService passwordHistoryService) { + + if (casProperties.getAuthn().getPm().getCore().isEnabled()) { + return new RestPasswordManagementService( + passwordManagementCipherExecutor, + casProperties, + passwordChangeServiceRestTemplate, + passwordHistoryService); + } + + return new NoOpPasswordManagementService(passwordManagementCipherExecutor, casProperties); + } + + @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) + @Bean + public PasswordManagementService passwordChangeService( + final ConfigurableApplicationContext ctx, + final CasConfigurationProperties casProperties, + @Qualifier("passwordManagementCipherExecutor") + final CipherExecutor passwordManagementCipherExecutor, + @Qualifier("syncopePasswordChangeService") + final PasswordManagementService syncopePasswordManagementService, + @Qualifier("ldapPasswordChangeService") + final PasswordManagementService ldapPasswordManagementService, + @Qualifier("jdbcPasswordChangeService") + final PasswordManagementService jdbcPasswordManagementService, + @Qualifier("restPasswordChangeService") + final PasswordManagementService restPasswordManagementService) { + + if (ctx.getEnvironment().getProperty("cas.authn.pm.syncope.enabled", Boolean.class, Boolean.FALSE)) { + return syncopePasswordManagementService; + } + + if (ctx.getEnvironment().getProperty("cas.authn.pm.ldap.enabled", Boolean.class, Boolean.FALSE)) { + return ldapPasswordManagementService; + } + + if (ctx.getEnvironment().getProperty("cas.authn.pm.jdbc.enabled", Boolean.class, Boolean.FALSE)) { + return jdbcPasswordManagementService; + } + + if (ctx.getEnvironment().getProperty("cas.authn.pm.rest.enabled", Boolean.class, Boolean.FALSE)) { + return restPasswordManagementService; + } + + return new NoOpPasswordManagementService(passwordManagementCipherExecutor, casProperties); + } + @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) @Bean(name = MultifactorAuthenticationTrustStorage.BEAN_NAME) public MultifactorAuthenticationTrustStorage mfaTrustStorage( diff --git a/wa/starter/src/test/java/org/apache/syncope/wa/starter/SyncopeCoreTestingServer.java b/wa/starter/src/test/java/org/apache/syncope/wa/starter/SyncopeCoreTestingServer.java index 929d15f9206..17ca8fe4879 100644 --- a/wa/starter/src/test/java/org/apache/syncope/wa/starter/SyncopeCoreTestingServer.java +++ b/wa/starter/src/test/java/org/apache/syncope/wa/starter/SyncopeCoreTestingServer.java @@ -38,6 +38,7 @@ import org.apache.syncope.common.lib.to.AuditEventTO; import org.apache.syncope.common.lib.to.AuthModuleTO; import org.apache.syncope.common.lib.to.PagedResult; +import org.apache.syncope.common.lib.to.PasswordManagementTO; import org.apache.syncope.common.lib.types.ClientAppType; import org.apache.syncope.common.lib.types.OpEvent; import org.apache.syncope.common.lib.wa.GoogleMfaAuthToken; @@ -47,6 +48,7 @@ import org.apache.syncope.common.rest.api.service.AttrRepoService; import org.apache.syncope.common.rest.api.service.AuditService; import org.apache.syncope.common.rest.api.service.AuthModuleService; +import org.apache.syncope.common.rest.api.service.PasswordManagementService; import org.apache.syncope.common.rest.api.service.wa.GoogleMfaAuthTokenService; import org.apache.syncope.common.rest.api.service.wa.ImpersonationService; import org.apache.syncope.common.rest.api.service.wa.WAClientAppService; @@ -63,6 +65,8 @@ public class SyncopeCoreTestingServer implements ApplicationListener ATTR_REPOS = new ArrayList<>(); + public static final List PASSWORD_MANAGEMENTS = new ArrayList<>(); + public static final List CLIENT_APPS = new ArrayList<>(); public static final List CONFIG = new ArrayList<>(); @@ -129,6 +133,37 @@ public void delete(final String key) { } } + protected static class StubPasswordManagementService implements PasswordManagementService { + + @Override + public PasswordManagementTO read(final String key) { + return PASSWORD_MANAGEMENTS.stream().filter(pm -> Objects.equals(key, pm.getKey())). + findFirst().orElseThrow(() -> new NotFoundException("Password Management with key " + key)); + } + + @Override + public List list() { + return PASSWORD_MANAGEMENTS; + } + + @Override + public Response create(final PasswordManagementTO passwordManagementTO) { + PASSWORD_MANAGEMENTS.add(passwordManagementTO); + return Response.created(null).build(); + } + + @Override + public void update(final PasswordManagementTO passwordManagementTO) { + delete(passwordManagementTO.getKey()); + create(passwordManagementTO); + } + + @Override + public void delete(final String key) { + PASSWORD_MANAGEMENTS.removeIf(pm -> Objects.equals(key, pm.getKey())); + } + } + protected static class StubWAClientAppService implements WAClientAppService { @Override @@ -329,6 +364,7 @@ public void onApplicationEvent(final ContextRefreshedEvent event) { AuditService.class, AuthModuleService.class, AttrRepoService.class, + PasswordManagementService.class, WAClientAppService.class, WAConfigService.class, GoogleMfaAuthTokenService.class, @@ -342,6 +378,9 @@ public void onApplicationEvent(final ContextRefreshedEvent event) { sf.setResourceProvider( AttrRepoService.class, new SingletonResourceProvider(new StubAttrRepoService(), true)); + sf.setResourceProvider( + PasswordManagementService.class, + new SingletonResourceProvider(new StubPasswordManagementService(), true)); sf.setResourceProvider( WAClientAppService.class, new SingletonResourceProvider(new StubWAClientAppService(), true));