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 0bbfee1e3b8..4b9b76812b8 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 @@ -30,7 +30,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.OIDCJWKSRestClient; +import org.apache.syncope.client.console.rest.OIDCOpEntityRestClient; import org.apache.syncope.client.console.rest.PasswordManagementRestClient; import org.apache.syncope.client.console.rest.PolicyRestClient; import org.apache.syncope.client.console.rest.SAML2IdPEntityRestClient; @@ -96,8 +96,8 @@ public ClientAppRestClient clientAppRestClient() { @ConditionalOnMissingBean @Bean - public OIDCJWKSRestClient oidcJWKSRestClient() { - return new OIDCJWKSRestClient(); + public OIDCOpEntityRestClient oidcOpEntityRestClient() { + return new OIDCOpEntityRestClient(); } @ConditionalOnMissingBean diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/CASSPDirectoryPanel.java b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/CASSPDirectoryPanel.java index 64d4900d19f..bc285e4d185 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/CASSPDirectoryPanel.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/CASSPDirectoryPanel.java @@ -45,6 +45,7 @@ public CASSPDirectoryPanel(final String id, final ClientAppRestClient restClient policyRestClient, clientAppRestClient, realmRestClient, + oidcOpEntityRestClient, pageRef), true); MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, AMEntitlement.CLIENTAPP_CREATE); diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.java b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.java index d546ea78848..18f02495abb 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.java @@ -35,6 +35,7 @@ import org.apache.syncope.client.console.panels.ModalDirectoryPanel; import org.apache.syncope.client.console.rest.AuditRestClient; import org.apache.syncope.client.console.rest.ClientAppRestClient; +import org.apache.syncope.client.console.rest.OIDCOpEntityRestClient; import org.apache.syncope.client.console.rest.PolicyRestClient; import org.apache.syncope.client.console.rest.RealmRestClient; import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn; @@ -80,6 +81,9 @@ public abstract class ClientAppDirectoryPanel @SpringBean protected AuditRestClient auditRestClient; + @SpringBean + protected OIDCOpEntityRestClient oidcOpEntityRestClient; + protected final ClientAppType type; protected final BaseModal propertiesModal; diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java index 10044edcd27..e54ef6980a7 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java @@ -37,6 +37,7 @@ import org.apache.syncope.client.console.commons.RealmsUtils; import org.apache.syncope.client.console.panels.AbstractModalPanel; import org.apache.syncope.client.console.rest.ClientAppRestClient; +import org.apache.syncope.client.console.rest.OIDCOpEntityRestClient; import org.apache.syncope.client.console.rest.PolicyRestClient; import org.apache.syncope.client.console.rest.RealmRestClient; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; @@ -55,7 +56,7 @@ import org.apache.syncope.client.ui.commons.panels.WizardModalPanel; import org.apache.syncope.client.ui.commons.wizards.AbstractModalPanelBuilder; import org.apache.syncope.client.ui.commons.wizards.AjaxWizard; -import org.apache.syncope.common.lib.OIDCScopeConstants; +import org.apache.syncope.common.lib.OIDCStandardScope; import org.apache.syncope.common.lib.policy.PolicyTO; import org.apache.syncope.common.lib.to.ClientAppTO; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; @@ -147,6 +148,8 @@ protected Map load() { protected final RealmRestClient realmRestClient; + protected final OIDCOpEntityRestClient oidcOpEntityRestClient; + public ClientAppModalPanelBuilder( final ClientAppType type, final T defaultItem, @@ -154,6 +157,7 @@ public ClientAppModalPanelBuilder( final PolicyRestClient policyRestClient, final ClientAppRestClient clientAppRestClient, final RealmRestClient realmRestClient, + final OIDCOpEntityRestClient oidcOpEntityRestClient, final PageReference pageRef) { super(defaultItem, pageRef); @@ -162,6 +166,7 @@ public ClientAppModalPanelBuilder( this.policyRestClient = policyRestClient; this.clientAppRestClient = clientAppRestClient; this.realmRestClient = realmRestClient; + this.oidcOpEntityRestClient = oidcOpEntityRestClient; } @Override @@ -378,7 +383,7 @@ protected void onUpdate(final AjaxRequestTarget target) { applicationType.setChoices(List.of(OIDCApplicationType.values())); fields.add(applicationType.addRequiredLabel().setEnabled(true)); - AjaxTextFieldPanel redirectUri = new AjaxTextFieldPanel("panel", "redirectUris", new Model<>()); + AjaxTextFieldPanel redirectUri = new AjaxTextFieldPanel("panel", "redirectUris", Model.of()); fields.add(new MultiFieldPanel.Builder( new PropertyModel<>(clientAppTO, "redirectUris")).build( "field", @@ -395,21 +400,12 @@ protected void onUpdate(final AjaxRequestTarget target) { new PropertyModel<>(clientAppTO, "supportedResponseTypes"), new ListModel<>(List.of(OIDCResponseType.values())))); - AutoCompleteSettings scopesSettings = new AutoCompleteSettings(); - scopesSettings.setShowCompleteListOnFocusGain(true); - scopesSettings.setShowListOnEmptyInput(true); - AjaxSearchFieldPanel scopes = new AjaxSearchFieldPanel( - "panel", "scopes", new PropertyModel<>(clientAppTO, "scopes"), scopesSettings) { - - private static final long serialVersionUID = 7160878678968866138L; - - @Override - protected Iterator getChoices(final String input) { - List choices = new ArrayList<>(OIDCScopeConstants.ALL_STANDARD_SCOPES); - choices.add(OIDCScopeConstants.SYNCOPE); - return choices.iterator(); - } - }; + AjaxTextFieldPanel scopes = new AjaxTextFieldPanel("panel", "scopes", Model.of()); + scopes.setChoices(Stream.concat(Stream.of(OIDCStandardScope.values()).map(OIDCStandardScope::name), + Optional.ofNullable(oidcOpEntityRestClient.get().get()). + map(oidcOpEntity -> oidcOpEntity.getCustomScopes().keySet().stream()). + orElseGet(() -> Stream.empty())). + distinct().sorted().toList()); fields.add(new MultiFieldPanel.Builder( new PropertyModel<>(clientAppTO, "scopes")).build( "field", @@ -553,7 +549,7 @@ public String getObject() { "field", "nameIdQualifier", new PropertyModel<>(clientAppTO, "nameIdQualifier"), false)); AjaxTextFieldPanel assertionAudience = new AjaxTextFieldPanel( - "panel", "assertionAudience", new Model<>()); + "panel", "assertionAudience", Model.of()); assertionAudience.addValidator(new UrlValidator()); fields.add(new MultiFieldPanel.Builder( new PropertyModel<>(clientAppTO, "assertionAudiences")).build( diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/OIDCRPDirectoryPanel.java b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/OIDCRPDirectoryPanel.java index 14c20ddfb89..5c7388936eb 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/OIDCRPDirectoryPanel.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/OIDCRPDirectoryPanel.java @@ -48,6 +48,7 @@ public OIDCRPDirectoryPanel(final String id, final ClientAppRestClient restClien policyRestClient, clientAppRestClient, realmRestClient, + oidcOpEntityRestClient, pageRef), true); MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, AMEntitlement.CLIENTAPP_CREATE); diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/SAML2SPDirectoryPanel.java b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/SAML2SPDirectoryPanel.java index 6b2b2be34f0..14fe935401e 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/SAML2SPDirectoryPanel.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/SAML2SPDirectoryPanel.java @@ -46,6 +46,7 @@ public SAML2SPDirectoryPanel(final String id, final ClientAppRestClient restClie policyRestClient, clientAppRestClient, realmRestClient, + oidcOpEntityRestClient, pageRef), true); MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, AMEntitlement.CLIENTAPP_CREATE); diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/commons/AMConstants.java b/client/am/console/src/main/java/org/apache/syncope/client/console/commons/AMConstants.java index fcd7b3fc580..ae1e1347d9d 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/commons/AMConstants.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/commons/AMConstants.java @@ -52,6 +52,8 @@ public final class AMConstants { public static final String PREF_AUTHPROFILE_WEBAUTHNDEVICECREDENTIALS_PAGINATOR_ROWS = "authprofile.webAuthnDeviceCredentials.paginator.rows"; + public static final String PREF_OIDC_CUSTOMSCOPES_PAGINATOR_ROWS = "oidc.customScopes.paginator.rows"; + private AMConstants() { // private constructor for static utility class } diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDCJWKSGenerationPanel.java b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/JWKSGenerationPanel.java similarity index 88% rename from client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDCJWKSGenerationPanel.java rename to client/am/console/src/main/java/org/apache/syncope/client/console/panels/JWKSGenerationPanel.java index d5d18f96eb2..e59033a1101 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDCJWKSGenerationPanel.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/JWKSGenerationPanel.java @@ -20,7 +20,7 @@ import java.util.List; import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.rest.OIDCJWKSRestClient; +import org.apache.syncope.client.console.rest.OIDCOpEntityRestClient; import org.apache.syncope.client.console.rest.WAConfigRestClient; import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxEventBehavior; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; @@ -30,16 +30,16 @@ import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.client.ui.commons.pages.BaseWebPage; import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.to.OIDCJWKSTO; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.model.Model; -public class OIDCJWKSGenerationPanel extends AbstractModalPanel { +public class JWKSGenerationPanel extends AbstractModalPanel { private static final long serialVersionUID = -3372006007594607067L; - protected final OIDCJWKSRestClient oidcJWKSRestClient; + protected final OIDCOpEntityRestClient oidcOpEntityRestClient; protected final Model jwksKeyIdM; @@ -47,14 +47,14 @@ public class OIDCJWKSGenerationPanel extends AbstractModalPanel { protected final Model jwksKeySizeM; - public OIDCJWKSGenerationPanel( - final OIDCJWKSRestClient oidcJWKSRestClient, + public JWKSGenerationPanel( + final OIDCOpEntityRestClient oidcOpEntityRestClient, final WAConfigRestClient waConfigRestClient, - final BaseModal modal, + final BaseModal modal, final PageReference pageRef) { super(modal, pageRef); - this.oidcJWKSRestClient = oidcJWKSRestClient; + this.oidcOpEntityRestClient = oidcOpEntityRestClient; jwksKeyIdM = Model.of("syncope"); try { @@ -104,7 +104,7 @@ protected void onEvent(final AjaxRequestTarget target) { @Override public void onSubmit(final AjaxRequestTarget target) { try { - oidcJWKSRestClient.generate(jwksKeyIdM.getObject(), jwksTypeM.getObject(), jwksKeySizeM.getObject()); + oidcOpEntityRestClient.generate(jwksKeyIdM.getObject(), jwksTypeM.getObject(), jwksKeySizeM.getObject()); SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); modal.close(target); diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDC.java b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDC.java index b71c26bf7a5..6e62635f7d2 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDC.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDC.java @@ -24,13 +24,14 @@ import java.util.Optional; import org.apache.commons.lang3.mutable.Mutable; import org.apache.syncope.client.console.SyncopeConsoleSession; -import org.apache.syncope.client.console.rest.OIDCJWKSRestClient; +import org.apache.syncope.client.console.rest.OIDCOpEntityRestClient; import org.apache.syncope.client.console.rest.WAConfigRestClient; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; import org.apache.syncope.client.console.wicket.markup.html.form.JsonEditorPanel; import org.apache.syncope.client.ui.commons.Constants; +import org.apache.syncope.client.ui.commons.markup.html.form.IndicatingOnConfirmAjaxLink; import org.apache.syncope.client.ui.commons.pages.BaseWebPage; -import org.apache.syncope.common.lib.to.OIDCJWKSTO; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; import org.apache.syncope.common.lib.types.AMEntitlement; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -54,12 +55,12 @@ public class OIDC extends Panel { protected static final JsonMapper MAPPER = JsonMapper.builder().findAndAddModules().build(); @SpringBean - protected OIDCJWKSRestClient oidcJWKSRestClient; + protected OIDCOpEntityRestClient oidcOpEntityRestClient; @SpringBean protected WAConfigRestClient waConfigRestClient; - protected final BaseModal generateModal = new BaseModal<>("generateModal"); + protected final BaseModal generateModal = new BaseModal<>("generateModal"); protected final BaseModal viewModal = new BaseModal<>("viewModal") { @@ -72,9 +73,11 @@ protected void onConfigure() { } }; - protected final AjaxLink view; + protected final WebMarkupContainer container; + + protected final Mutable oidcOpEntity; - protected final AjaxLink generate; + protected final AjaxLink view; protected final AjaxLink delete; @@ -82,10 +85,10 @@ public OIDC(final String id, final String waPrefix, final PageReference pageRef) super(id); setOutputMarkupId(true); - WebMarkupContainer container = new WebMarkupContainer("container"); + container = new WebMarkupContainer("container"); add(container.setOutputMarkupId(true)); - Mutable oidcjwksto = oidcJWKSRestClient.get(); + oidcOpEntity = oidcOpEntityRestClient.get(); add(viewModal); viewModal.size(Modal.Size.Extra_large); @@ -97,13 +100,15 @@ public OIDC(final String id, final String waPrefix, final PageReference pageRef) @Override public void onClick(final AjaxRequestTarget target) { - String pretty; - try { - pretty = MAPPER.writerWithDefaultPrettyPrinter(). - writeValueAsString(MAPPER.readTree(oidcjwksto.get().getJson())); - } catch (IOException e) { - LOG.error("Could not pretty-print", e); - pretty = Optional.ofNullable(oidcjwksto.get()).map(OIDCJWKSTO::getJson).orElse(null); + String pretty = null; + if (oidcOpEntity.get() != null) { + try { + pretty = MAPPER.writerWithDefaultPrettyPrinter(). + writeValueAsString(MAPPER.readTree(oidcOpEntity.get().getJWKS())); + } catch (IOException e) { + LOG.error("Could not pretty-print", e); + pretty = Optional.ofNullable(oidcOpEntity.get()).map(OIDCOpEntityTO::getJWKS).orElse(null); + } } viewModal.header(Model.of("JSON Web Key Sets")); @@ -115,56 +120,53 @@ public void onClick(final AjaxRequestTarget target) { protected void onComponentTag(final ComponentTag tag) { super.onComponentTag(tag); - if (oidcjwksto.get() == null) { + if (oidcOpEntity.get() == null) { tag.put("class", "btn btn-app disabled"); } } }; - view.setEnabled(oidcjwksto.get() != null); + view.setEnabled(oidcOpEntity.get() != null); container.add(view.setOutputMarkupId(true)); - MetaDataRoleAuthorizationStrategy.authorize(view, ENABLE, AMEntitlement.OIDC_JWKS_READ); + MetaDataRoleAuthorizationStrategy.authorize(view, ENABLE, AMEntitlement.OIDC_OP_ENTITY_GET); - generate = new AjaxLink<>("generate") { + AjaxLink generate = new AjaxLink<>("generate") { private static final long serialVersionUID = 6250423506463465679L; @Override public void onClick(final AjaxRequestTarget target) { generateModal.header(Model.of("Generate JSON Web Key Sets")); - target.add(generateModal.setContent(new OIDCJWKSGenerationPanel( - oidcJWKSRestClient, waConfigRestClient, generateModal, pageRef))); + target.add(generateModal.setContent(new JWKSGenerationPanel( + oidcOpEntityRestClient, waConfigRestClient, generateModal, pageRef))); generateModal.show(true); } - - @Override - protected void onComponentTag(final ComponentTag tag) { - super.onComponentTag(tag); - - if (oidcjwksto.get() != null) { - tag.put("class", "btn btn-app disabled"); - } - } }; - generate.setEnabled(oidcjwksto.get() == null); container.add(generate.setOutputMarkupId(true)); MetaDataRoleAuthorizationStrategy.authorize(generate, ENABLE, AMEntitlement.OIDC_JWKS_GENERATE); - delete = new AjaxLink<>("delete") { + OIDCCustomScopeDirectoryPanel customScopes = new OIDCCustomScopeDirectoryPanel( + this, "customScopes", oidcOpEntityRestClient, pageRef) { + + private static final long serialVersionUID = 2220666976933420952L; + + }; + container.add(customScopes.setOutputMarkupId(true)); + + delete = new IndicatingOnConfirmAjaxLink<>("delete", "confirmDelete", true) { private static final long serialVersionUID = 6250423506463465679L; @Override public void onClick(final AjaxRequestTarget target) { try { - oidcJWKSRestClient.delete(); - oidcjwksto.setValue(null); - generate.setEnabled(true); + oidcOpEntityRestClient.delete(); + oidcOpEntity.setValue(null); view.setEnabled(false); SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); target.add(container); } catch (Exception e) { - LOG.error("While deleting OIDC JWKS", e); + LOG.error("While deleting OIDC OP", e); SyncopeConsoleSession.get().onException(e); } ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target); @@ -174,27 +176,31 @@ public void onClick(final AjaxRequestTarget target) { protected void onComponentTag(final ComponentTag tag) { super.onComponentTag(tag); - if (oidcjwksto.get() == null) { + if (oidcOpEntity.get() == null) { tag.put("class", "btn btn-app disabled"); } } }; - delete.setEnabled(oidcjwksto.get() != null); + delete.setEnabled(oidcOpEntity.get() != null); container.add(delete.setOutputMarkupId(true)); - MetaDataRoleAuthorizationStrategy.authorize(delete, ENABLE, AMEntitlement.OIDC_JWKS_DELETE); + MetaDataRoleAuthorizationStrategy.authorize(delete, ENABLE, AMEntitlement.OIDC_OP_ENTITY_DELETE); generateModal.addSubmitButton(); add(generateModal); generateModal.setWindowClosedCallback(target -> { - oidcjwksto.setValue(oidcJWKSRestClient.get().get()); - view.setEnabled(oidcjwksto.get() != null); - delete.setEnabled(oidcjwksto.get() != null); - - target.add(container); + refreshOIDCOpEntity(target); generateModal.show(false); }); String wellKnownURI = waPrefix + "/oidc/.well-known/openid-configuration"; container.add(new ExternalLink("wellKnownURI", wellKnownURI, wellKnownURI)); } + + public void refreshOIDCOpEntity(final AjaxRequestTarget target) { + oidcOpEntity.setValue(oidcOpEntityRestClient.get().get()); + view.setEnabled(oidcOpEntity.get() != null); + delete.setEnabled(oidcOpEntity.get() != null); + + target.add(container); + } } diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDCCustomScopeDirectoryPanel.java b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDCCustomScopeDirectoryPanel.java new file mode 100644 index 00000000000..1ac3b134055 --- /dev/null +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDCCustomScopeDirectoryPanel.java @@ -0,0 +1,226 @@ +/* + * 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 java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.lang3.mutable.Mutable; +import org.apache.syncope.client.console.SyncopeConsoleSession; +import org.apache.syncope.client.console.commons.AMConstants; +import org.apache.syncope.client.console.commons.DirectoryDataProvider; +import org.apache.syncope.client.console.pages.BasePage; +import org.apache.syncope.client.console.panels.OIDCCustomScopeDirectoryPanel.CustomScope; +import org.apache.syncope.client.console.rest.OIDCOpEntityRestClient; +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.ui.commons.Constants; +import org.apache.syncope.client.ui.commons.wizards.AjaxWizard; +import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.to.EntityTO; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; +import org.apache.syncope.common.lib.types.AMEntitlement; +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.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.StringResourceModel; + +public abstract class OIDCCustomScopeDirectoryPanel + extends DirectoryPanel, OIDCOpEntityRestClient> { + + private static final long serialVersionUID = -7283064059391373326L; + + public static class CustomScope implements EntityTO { + + private static final long serialVersionUID = -6041970196389196072L; + + private String scope; + + private final List claims = new ArrayList<>(); + + @Override + public void setKey(final String key) { + this.scope = key; + } + + @Override + public String getKey() { + return scope; + } + + public List getClaims() { + return claims; + } + } + + protected final OIDC oidc; + + protected OIDCCustomScopeDirectoryPanel( + final OIDC oidc, final String id, final OIDCOpEntityRestClient restClient, final PageReference pageRef) { + + super(id, restClient, pageRef, true); + this.oidc = oidc; + + addNewItemPanelBuilder(new OIDCCustomScopeWizardBuilder( + new CustomScope(), restClient, pageRef), true); + MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, AMEntitlement.OIDC_OP_ENTITY_SET); + + disableCheckBoxes(); + + initResultTable(); + + modal.setWindowClosedCallback(target -> { + oidc.refreshOIDCOpEntity(target); + modal.show(false); + }); + } + + protected Optional getOIDCOpEntity() { + Mutable oidcOpEntity = restClient.get(); + return Optional.ofNullable(oidcOpEntity.get()); + } + + @Override + protected List> getColumns() { + List> columns = new ArrayList<>(); + + columns.add(new PropertyColumn<>(new StringResourceModel("scope", this), "scope", "scope")); + columns.add(new PropertyColumn<>(new StringResourceModel("claims", this), "claims", "claims")); + + return columns; + } + + @Override + protected ActionsPanel getActions(final IModel model) { + ActionsPanel panel = super.getActions(model); + + getOIDCOpEntity().ifPresent(oidcOpEntity -> { + panel.add(new ActionLink<>() { + + private static final long serialVersionUID = -3722207913631435501L; + + @Override + public void onClick(final AjaxRequestTarget target, final CustomScope ignore) { + send(OIDCCustomScopeDirectoryPanel.this, Broadcast.EXACT, + new AjaxWizard.EditItemActionEvent<>(model.getObject(), target)); + } + }, ActionLink.ActionType.EDIT, AMEntitlement.OIDC_OP_ENTITY_SET); + + panel.add(new ActionLink<>() { + + private static final long serialVersionUID = -3722207913631435501L; + + @Override + public void onClick(final AjaxRequestTarget target, final CustomScope ignore) { + CustomScope clone = new CustomScope(); + clone.setKey(model.getObject().getKey() + "_clone"); + clone.getClaims().addAll(model.getObject().getClaims()); + + send(OIDCCustomScopeDirectoryPanel.this, Broadcast.EXACT, + new AjaxWizard.EditItemActionEvent<>(clone, target)); + } + }, ActionLink.ActionType.CLONE, AMEntitlement.OIDC_OP_ENTITY_SET); + + panel.add(new ActionLink<>() { + + private static final long serialVersionUID = -3722207913631435501L; + + @Override + public void onClick(final AjaxRequestTarget target, final CustomScope ignore) { + CustomScope customScope = model.getObject(); + + oidcOpEntity.getCustomScopes().remove(customScope.getKey()); + try { + restClient.set(oidcOpEntity); + + SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED)); + target.add(container); + } catch (SyncopeClientException e) { + LOG.error("While updating OIDC OP custom scopes", e); + SyncopeConsoleSession.get().onException(e); + } + + ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); + } + }, ActionLink.ActionType.DELETE, AMEntitlement.OIDC_OP_ENTITY_SET, true); + }); + + return panel; + } + + @Override + protected Collection getBatches() { + return List.of(); + } + + @Override + protected DirectoryDataProvider dataProvider() { + return new CustomScopeDataProvider(rows); + } + + @Override + protected String paginatorRowsKey() { + return AMConstants.PREF_OIDC_CUSTOMSCOPES_PAGINATOR_ROWS; + } + + protected class CustomScopeDataProvider extends DirectoryDataProvider { + + private static final long serialVersionUID = 4725679400450513556L; + + public CustomScopeDataProvider(final int paginatorRows) { + super(paginatorRows); + } + + @Override + public Iterator iterator(final long first, final long count) { + return getOIDCOpEntity().map(oidcOpEntity -> { + List list = oidcOpEntity.getCustomScopes().entrySet().stream(). + map(entry -> { + CustomScope customScope = new CustomScope(); + customScope.setKey(entry.getKey()); + customScope.getClaims().addAll(entry.getValue()); + return customScope; + }). + sorted(Comparator.comparing(CustomScope::getKey)). + collect(Collectors.toList()); + return list.subList((int) first, (int) first + (int) count).iterator(); + }).orElseGet(() -> Collections.emptyIterator()); + } + + @Override + public long size() { + return getOIDCOpEntity().map(oidcOpEntity -> oidcOpEntity.getCustomScopes().size()).orElse(0); + } + + @Override + public IModel model(final CustomScope report) { + return new CompoundPropertyModel<>(report); + } + } +} diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDCCustomScopeWizardBuilder.java b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDCCustomScopeWizardBuilder.java new file mode 100644 index 00000000000..cfbdf26d84e --- /dev/null +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/OIDCCustomScopeWizardBuilder.java @@ -0,0 +1,87 @@ +/* + * 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 java.io.Serializable; +import java.util.HashSet; +import java.util.Optional; +import org.apache.syncope.client.console.panels.OIDCCustomScopeDirectoryPanel.CustomScope; +import org.apache.syncope.client.console.rest.OIDCOpEntityRestClient; +import org.apache.syncope.client.console.wizards.BaseAjaxWizardBuilder; +import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel; +import org.apache.syncope.client.ui.commons.markup.html.form.MultiFieldPanel; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; +import org.apache.wicket.PageReference; +import org.apache.wicket.extensions.wizard.WizardModel; +import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.PropertyModel; + +public class OIDCCustomScopeWizardBuilder extends BaseAjaxWizardBuilder { + + private static final long serialVersionUID = 6268620772839923063L; + + protected final OIDCOpEntityRestClient restClient; + + public OIDCCustomScopeWizardBuilder( + final CustomScope customScope, + final OIDCOpEntityRestClient restClient, + final PageReference pageRef) { + + super(customScope, pageRef); + this.restClient = restClient; + } + + @Override + protected Serializable onApplyInternal(final CustomScope modelObject) { + OIDCOpEntityTO oidcOpEntity = Optional.ofNullable(restClient.get().get()).orElseGet(() -> new OIDCOpEntityTO()); + + Optional.ofNullable(getOriginalItem().getKey()).ifPresent(oidcOpEntity.getCustomScopes()::remove); + + oidcOpEntity.getCustomScopes().put(modelObject.getKey(), new HashSet<>(modelObject.getClaims())); + + restClient.set(oidcOpEntity); + + return modelObject; + } + + @Override + protected WizardModel buildModelSteps(final CustomScope modelObject, final WizardModel wizardModel) { + wizardModel.add(new Profile(modelObject)); + return wizardModel; + } + + protected class Profile extends WizardStep { + + private static final long serialVersionUID = 746342514949780968L; + + public Profile(final CustomScope modelObject) { + AjaxTextFieldPanel scope = new AjaxTextFieldPanel( + "scope", "scope", new PropertyModel<>(modelObject, "scope"), false); + add(scope.setReadOnly(modelObject.getKey() != null).setRequired(true)); + + AjaxTextFieldPanel claims = new AjaxTextFieldPanel("panel", "claims", Model.of()); + add(new MultiFieldPanel.Builder( + new PropertyModel<>(modelObject, "claims")).build( + "claims", + "claims", + claims).setRequired(true)); + } + } +} diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/OIDCJWKSRestClient.java b/client/am/console/src/main/java/org/apache/syncope/client/console/rest/OIDCOpEntityRestClient.java similarity index 59% rename from client/am/console/src/main/java/org/apache/syncope/client/console/rest/OIDCJWKSRestClient.java rename to client/am/console/src/main/java/org/apache/syncope/client/console/rest/OIDCOpEntityRestClient.java index 70a94e40309..394ad7de4ac 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/OIDCJWKSRestClient.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/rest/OIDCOpEntityRestClient.java @@ -21,29 +21,33 @@ import jakarta.ws.rs.core.Response; import org.apache.commons.lang3.mutable.Mutable; import org.apache.commons.lang3.mutable.MutableObject; -import org.apache.syncope.common.lib.to.OIDCJWKSTO; -import org.apache.syncope.common.rest.api.service.OIDCJWKSService; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; +import org.apache.syncope.common.rest.api.service.OIDCOpEntityService; -public class OIDCJWKSRestClient extends BaseRestClient { +public class OIDCOpEntityRestClient extends BaseRestClient { private static final long serialVersionUID = -1392090291817187902L; - public Mutable get() { - MutableObject result = new MutableObject<>(); + public OIDCOpEntityTO generate(final String jwksKeyId, final String jwksType, final int jwksKeySize) { + Response response = getService(OIDCOpEntityService.class).generate(jwksKeyId, jwksType, jwksKeySize); + return response.readEntity(OIDCOpEntityTO.class); + } + + public Mutable get() { + MutableObject result = new MutableObject<>(); try { - result.setValue(getService(OIDCJWKSService.class).get()); + result.setValue(getService(OIDCOpEntityService.class).get()); } catch (Exception e) { LOG.debug("While getting OIDC JKS", e); } return result; } - public OIDCJWKSTO generate(final String jwksKeyId, final String jwksType, final int jwksKeySize) { - Response response = getService(OIDCJWKSService.class).generate(jwksKeyId, jwksType, jwksKeySize); - return response.readEntity(OIDCJWKSTO.class); + public void set(final OIDCOpEntityTO oidcOpEntity) { + getService(OIDCOpEntityService.class).set(oidcOpEntity); } public void delete() { - getService(OIDCJWKSService.class).delete(); + getService(OIDCOpEntityService.class).delete(); } } diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDCJWKSGenerationPanel.html b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/JWKSGenerationPanel.html similarity index 100% rename from client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDCJWKSGenerationPanel.html rename to client/am/console/src/main/resources/org/apache/syncope/client/console/panels/JWKSGenerationPanel.html diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC.html b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC.html index f67ee751dcf..967d4cacccd 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC.html +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC.html @@ -18,32 +18,49 @@ --> -
-
-
-
-

JSON Web Key Sets

+
+
+
+
+
+

JSON Web Key Sets

+
+
- +
+
+
+

Metadata URI

+
+
+ +
+
-
-
-
-

Well-Known URI

-
-
- + +
+
+
+
+

+
+
+
+
diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC.properties index 7c83852f9c6..ecaa5872eea 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC.properties +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC.properties @@ -17,4 +17,9 @@ # view=View generate=Generate -delete=Delete +reset=Reset +scope=Scope +claims=Claims +customScopes=Custom Scopes +any.edit=Edit ${key} +any.new=New Custom Scope diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDCCustomScopeWizardBuilder$Profile.html b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDCCustomScopeWizardBuilder$Profile.html new file mode 100644 index 00000000000..f9e0e53d5a3 --- /dev/null +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDCCustomScopeWizardBuilder$Profile.html @@ -0,0 +1,24 @@ + + + +
+
+
+ diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_fr_CA.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_fr_CA.properties index 7c83852f9c6..ecaa5872eea 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_fr_CA.properties +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_fr_CA.properties @@ -17,4 +17,9 @@ # view=View generate=Generate -delete=Delete +reset=Reset +scope=Scope +claims=Claims +customScopes=Custom Scopes +any.edit=Edit ${key} +any.new=New Custom Scope diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_it.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_it.properties index f55ab922a69..be089deb505 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_it.properties +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_it.properties @@ -17,4 +17,9 @@ # view=Vedi generate=Genera -delete=Cancella +reset=Ripristina +scope=Scope +claims=Claims +customScopes=Scope personalizzati +any.edit=Modifica ${key} +any.new=Nuovo Scope personalizzato diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_ja.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_ja.properties index 7c83852f9c6..ecaa5872eea 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_ja.properties +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_ja.properties @@ -17,4 +17,9 @@ # view=View generate=Generate -delete=Delete +reset=Reset +scope=Scope +claims=Claims +customScopes=Custom Scopes +any.edit=Edit ${key} +any.new=New Custom Scope diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_pt_BR.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_pt_BR.properties index 7c83852f9c6..ecaa5872eea 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_pt_BR.properties +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_pt_BR.properties @@ -17,4 +17,9 @@ # view=View generate=Generate -delete=Delete +reset=Reset +scope=Scope +claims=Claims +customScopes=Custom Scopes +any.edit=Edit ${key} +any.new=New Custom Scope diff --git a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_ru.properties b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_ru.properties index 7c83852f9c6..ecaa5872eea 100644 --- a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_ru.properties +++ b/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/OIDC_ru.properties @@ -17,4 +17,9 @@ # view=View generate=Generate -delete=Delete +reset=Reset +scope=Scope +claims=Claims +customScopes=Custom Scopes +any.edit=Edit ${key} +any.new=New Custom Scope diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/AjaxTextFieldPanel.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/AjaxTextFieldPanel.java index 0478abbf16c..521c4607073 100644 --- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/AjaxTextFieldPanel.java +++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/AjaxTextFieldPanel.java @@ -50,6 +50,7 @@ public AjaxTextFieldPanel(final String id, final String name, final IModel model, final boolean enableOnChange) { + super(id, name, model); questionMarkJexlHelp = Constants.getJEXLPopover(this, TooltipConfig.Placement.right); diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxSearchFieldPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxSearchFieldPanel.java index e81ff761b44..a48e4fd01a0 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxSearchFieldPanel.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxSearchFieldPanel.java @@ -19,7 +19,6 @@ package org.apache.syncope.client.console.wicket.markup.html.form; import java.util.Iterator; -import java.util.List; import org.apache.syncope.client.ui.commons.Constants; import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior; import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAutoCompleteBehavior; @@ -33,12 +32,10 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.model.ResourceModel; -public class AjaxSearchFieldPanel extends TextFieldPanel implements Cloneable { +public abstract class AjaxSearchFieldPanel extends TextFieldPanel implements Cloneable { private static final long serialVersionUID = 6890905510177974519L; - private List choices = List.of(); - private final IAutoCompleteRenderer renderer; private final AutoCompleteSettings settings; @@ -51,6 +48,7 @@ public AjaxSearchFieldPanel( final String id, final String name, final IModel model, final AutoCompleteSettings settings) { + this(id, name, model, null, settings); } @@ -59,6 +57,7 @@ public AjaxSearchFieldPanel( final IModel model, final IAutoCompleteRenderer renderer, final AutoCompleteSettings settings) { + super(id, name, model); this.settings = settings; @@ -107,16 +106,10 @@ protected void onUpdate(final AjaxRequestTarget target) { } } - public List getChoices() { - return choices; - } - public void onUpdateBehavior() { } - protected Iterator getChoices(final String input) { - return choices.iterator(); - } + protected abstract Iterator getChoices(String input); @Override public FieldPanel clone() { diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/OIDCScopeConstants.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/OIDCStandardScope.java similarity index 59% rename from common/am/lib/src/main/java/org/apache/syncope/common/lib/OIDCScopeConstants.java rename to common/am/lib/src/main/java/org/apache/syncope/common/lib/OIDCStandardScope.java index 29c3e39d11f..57aa9dda276 100644 --- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/OIDCScopeConstants.java +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/OIDCStandardScope.java @@ -18,25 +18,12 @@ */ package org.apache.syncope.common.lib; -import java.util.List; +public enum OIDCStandardScope { -public final class OIDCScopeConstants { + openid, + address, + email, + profile, + phone; - public static final String OPEN_ID = "openid"; - - public static final String PROFILE = "profile"; - - public static final String EMAIL = "email"; - - public static final String ADDRESS = "address"; - - public static final String PHONE = "phone"; - - public static final String SYNCOPE = "syncope"; - - public static final List ALL_STANDARD_SCOPES = List.of(OPEN_ID, PROFILE, EMAIL, ADDRESS, PHONE); - - private OIDCScopeConstants() { - // private constructor for static utility class - } } diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/OIDCJWKSTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/OIDCOpEntityTO.java similarity index 58% rename from common/am/lib/src/main/java/org/apache/syncope/common/lib/to/OIDCJWKSTO.java rename to common/am/lib/src/main/java/org/apache/syncope/common/lib/to/OIDCOpEntityTO.java index ea7e14c9345..3620779c5ac 100644 --- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/OIDCJWKSTO.java +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/OIDCOpEntityTO.java @@ -18,44 +18,22 @@ */ package org.apache.syncope.common.lib.to; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; -public class OIDCJWKSTO implements EntityTO { +public class OIDCOpEntityTO implements EntityTO { private static final long serialVersionUID = 1285073386484048953L; - public static class Builder { - - private final OIDCJWKSTO instance = new OIDCJWKSTO(); - - public OIDCJWKSTO.Builder json(final String json) { - instance.setJson(json); - return this; - } - - public OIDCJWKSTO.Builder key(final String key) { - instance.setKey(key); - return this; - } - - public OIDCJWKSTO build() { - return instance; - } - } - private String key; - private String json; + private String jwks; - public String getJson() { - return json; - } - - public void setJson(final String json) { - this.json = json; - } + private final Map> customScopes = new HashMap<>(); @Override public String getKey() { @@ -67,13 +45,26 @@ public void setKey(final String key) { this.key = key; } + public String getJWKS() { + return jwks; + } + + public void setJWKS(final String jwks) { + this.jwks = jwks; + } + + public Map> getCustomScopes() { + return customScopes; + } + @Override public int hashCode() { - return new HashCodeBuilder() - .appendSuper(super.hashCode()) - .append(key) - .append(json) - .toHashCode(); + return new HashCodeBuilder(). + appendSuper(super.hashCode()). + append(key). + append(jwks). + append(customScopes). + toHashCode(); } @Override @@ -87,19 +78,22 @@ public boolean equals(final Object obj) { if (obj.getClass() != getClass()) { return false; } - OIDCJWKSTO rhs = (OIDCJWKSTO) obj; - return new EqualsBuilder() - .appendSuper(super.equals(obj)) - .append(this.key, rhs.key) - .append(this.json, rhs.json) - .isEquals(); + OIDCOpEntityTO rhs = (OIDCOpEntityTO) obj; + return new EqualsBuilder(). + appendSuper(super.equals(obj)). + append(this.key, rhs.key). + append(this.jwks, rhs.jwks). + append(this.customScopes, rhs.customScopes). + isEquals(); } @Override public String toString() { - return new ToStringBuilder(this) - .append("key", key) - .append("json", json) - .toString(); + return new ToStringBuilder(this). + append(super.toString()). + append("key", key). + append("jwks", jwks). + append("customScopes", customScopes). + toString(); } } 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 12b334ec42a..69c5fa0a036 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 @@ -98,11 +98,11 @@ public final class AMEntitlement { public static final String OIDC_JWKS_GENERATE = "OIDC_JWKS_GENERATE"; - public static final String OIDC_JWKS_READ = "OIDC_JWKS_READ"; + public static final String OIDC_OP_ENTITY_GET = "OIDC_OP_ENTITY_GET"; - public static final String OIDC_JWKS_SET = "OIDC_JWKS_SET"; + public static final String OIDC_OP_ENTITY_SET = "OIDC_OP_ENTITY_SET"; - public static final String OIDC_JWKS_DELETE = "OIDC_JWKS_DELETE"; + public static final String OIDC_OP_ENTITY_DELETE = "OIDC_OP_ENTITY_DELETE"; public static final String WA_CONFIG_LIST = "WA_CONFIG_LIST"; diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCJWKSService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCOpEntityService.java similarity index 92% rename from common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCJWKSService.java rename to common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCOpEntityService.java index d150591d26f..6cb411e8845 100644 --- a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCJWKSService.java +++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCOpEntityService.java @@ -31,31 +31,32 @@ import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.apache.syncope.common.lib.to.OIDCJWKSTO; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; @Tag(name = "OpenID Connect 1.0") @SecurityRequirements({ @SecurityRequirement(name = "BasicAuthentication"), @SecurityRequirement(name = "Bearer") }) -@Path("oidc/jwks") -public interface OIDCJWKSService extends JAXRSService { +@Path("oidc/op") +public interface OIDCOpEntityService extends JAXRSService { @GET @Produces({ MediaType.APPLICATION_JSON }) - OIDCJWKSTO get(); + OIDCOpEntityTO get(); @ApiResponses( @ApiResponse(responseCode = "204", description = "Operation was successful")) - @POST + @PUT @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - void set(@NotNull OIDCJWKSTO entityTO); + void set(@NotNull OIDCOpEntityTO oidcOpEntityTO); @ApiResponses({ @ApiResponse(responseCode = "201", @@ -68,7 +69,6 @@ public interface OIDCJWKSService extends JAXRSService { @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Path("new") Response generate( @NotNull @QueryParam("jwksKeyId") @DefaultValue("syncope") String jwksKeyId, @NotNull @QueryParam("jwksType") @DefaultValue("RSA") String jwksType, diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPEntityService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPEntityService.java index 761936f773e..468f6e5ae8a 100644 --- a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPEntityService.java +++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPEntityService.java @@ -28,7 +28,7 @@ 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; @@ -75,7 +75,7 @@ public interface SAML2IdPEntityService extends JAXRSService { */ @Parameter(name = "key", description = "SAML2IdPEntityTO's key", in = ParameterIn.PATH, schema = @Schema(type = "string")) - @POST + @PUT @Path("{key}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) 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 aaefc3501a2..7cbf0757423 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 @@ -31,7 +31,7 @@ import org.apache.syncope.core.persistence.api.dao.AuthModuleDAO; import org.apache.syncope.core.persistence.api.dao.AuthProfileDAO; 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.OIDCOpEntityDAO; 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; @@ -44,7 +44,7 @@ import org.apache.syncope.core.provisioning.api.data.AuthModuleDataBinder; 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.OIDCOpEntityDataBinder; 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; @@ -122,13 +122,12 @@ public ClientAppLogic clientAppLogic( @ConditionalOnMissingBean @Bean - public OIDCJWKSLogic oidcJWKSLogic( - final OIDCJWKSDataBinder oidcJWKSDataBinder, - final OIDCJWKSDAO oidcJWKSDAO, - final WAConfigDAO waConfigDAO, + public OIDCOpEntityLogic oidcOpEntityLogic( + final OIDCOpEntityDataBinder oidcOpEntityDataBinder, + final OIDCOpEntityDAO oidcOpEntityDAO, final EntityFactory entityFactory) { - return new OIDCJWKSLogic(oidcJWKSDataBinder, oidcJWKSDAO, waConfigDAO, entityFactory); + return new OIDCOpEntityLogic(oidcOpEntityDataBinder, oidcOpEntityDAO, entityFactory); } @ConditionalOnMissingBean diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCJWKSLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCJWKSLogic.java deleted file mode 100644 index 05ec506714c..00000000000 --- a/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCJWKSLogic.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.syncope.common.lib.to.OIDCJWKSTO; -import org.apache.syncope.common.lib.types.AMEntitlement; -import org.apache.syncope.common.lib.types.IdRepoEntitlement; -import org.apache.syncope.core.persistence.api.dao.DuplicateException; -import org.apache.syncope.core.persistence.api.dao.NotFoundException; -import org.apache.syncope.core.persistence.api.dao.OIDCJWKSDAO; -import org.apache.syncope.core.persistence.api.dao.WAConfigDAO; -import org.apache.syncope.core.persistence.api.entity.EntityFactory; -import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS; -import org.apache.syncope.core.persistence.api.entity.am.WAConfigEntry; -import org.apache.syncope.core.provisioning.api.data.OIDCJWKSDataBinder; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.transaction.annotation.Transactional; - -public class OIDCJWKSLogic extends AbstractTransactionalLogic { - - protected final OIDCJWKSDataBinder binder; - - protected final OIDCJWKSDAO oidcJWKSDAO; - - protected final WAConfigDAO waConfigDAO; - - protected final EntityFactory entityFactory; - - public OIDCJWKSLogic( - final OIDCJWKSDataBinder binder, - final OIDCJWKSDAO oidcJWKSDAO, - final WAConfigDAO waConfigDAO, - final EntityFactory entityFactory) { - - this.binder = binder; - this.oidcJWKSDAO = oidcJWKSDAO; - this.waConfigDAO = waConfigDAO; - this.entityFactory = entityFactory; - } - - @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_READ + "') " - + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')") - @Transactional(readOnly = true) - public OIDCJWKSTO get() { - return oidcJWKSDAO.get(). - map(binder::getOIDCJWKSTO). - orElseThrow(() -> new NotFoundException("OIDC JWKS not found")); - } - - @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_GENERATE + "') " - + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')") - public OIDCJWKSTO generate(final String jwksKeyId, final String jwksType, final int jwksKeySize) { - if (oidcJWKSDAO.get().isEmpty()) { - OIDCJWKSTO oidcJWKSTO = binder.getOIDCJWKSTO( - oidcJWKSDAO.save(binder.create(jwksKeyId, jwksType, jwksKeySize))); - - WAConfigEntry jwksKeyIdConfig = entityFactory.newEntity(WAConfigEntry.class); - jwksKeyIdConfig.setKey("cas.authn.oidc.jwks.core.jwks-key-id"); - jwksKeyIdConfig.setValues(List.of(jwksKeyId)); - waConfigDAO.save(jwksKeyIdConfig); - - WAConfigEntry jwksTypeConfig = entityFactory.newEntity(WAConfigEntry.class); - jwksTypeConfig.setKey("cas.authn.oidc.jwks.core.jwks-type"); - jwksTypeConfig.setValues(List.of(jwksType)); - waConfigDAO.save(jwksTypeConfig); - - WAConfigEntry jwksKeySizeConfig = entityFactory.newEntity(WAConfigEntry.class); - jwksKeySizeConfig.setKey("cas.authn.oidc.jwks.core.jwks-key-size"); - jwksKeySizeConfig.setValues(List.of(String.valueOf(jwksKeySize))); - waConfigDAO.save(jwksKeySizeConfig); - - return oidcJWKSTO; - } - - throw new DuplicateException("OIDC JWKS already set"); - } - - @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_SET + "') " - + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')") - public OIDCJWKSTO set(final OIDCJWKSTO entityTO) { - OIDCJWKS jwks = oidcJWKSDAO.get().orElseGet(() -> entityFactory.newEntity(OIDCJWKS.class)); - jwks.setJson(entityTO.getJson()); - return binder.getOIDCJWKSTO(oidcJWKSDAO.save(jwks)); - } - - @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_DELETE + "')") - public void delete() { - oidcJWKSDAO.delete(); - } - - @Override - protected OIDCJWKSTO resolveReference(final Method method, final Object... args) - throws UnresolvedReferenceException { - - OIDCJWKS jwks = oidcJWKSDAO.get().orElseThrow(UnresolvedReferenceException::new); - return binder.getOIDCJWKSTO(jwks); - } -} diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCOpEntityLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCOpEntityLogic.java new file mode 100644 index 00000000000..06f64b3da59 --- /dev/null +++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCOpEntityLogic.java @@ -0,0 +1,90 @@ +/* + * 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 org.apache.syncope.common.lib.to.OIDCOpEntityTO; +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.OIDCOpEntityDAO; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.am.OIDCOpEntity; +import org.apache.syncope.core.provisioning.api.data.OIDCOpEntityDataBinder; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; + +public class OIDCOpEntityLogic extends AbstractTransactionalLogic { + + protected final OIDCOpEntityDataBinder binder; + + protected final OIDCOpEntityDAO oidcOpEntityDAO; + + protected final EntityFactory entityFactory; + + public OIDCOpEntityLogic( + final OIDCOpEntityDataBinder binder, + final OIDCOpEntityDAO oidcOpEntityDAO, + final EntityFactory entityFactory) { + + this.binder = binder; + this.oidcOpEntityDAO = oidcOpEntityDAO; + this.entityFactory = entityFactory; + } + + @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_GENERATE + "') " + + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')") + public OIDCOpEntityTO generate(final String jwksKeyId, final String jwksType, final int jwksKeySize) { + OIDCOpEntity oidcOpEntity = oidcOpEntityDAO.get().orElseGet(() -> entityFactory.newEntity(OIDCOpEntity.class)); + oidcOpEntity.setJWKS(binder.generateJWKS(jwksKeyId, jwksType, jwksKeySize)); + + return binder.getOIDCOpEntityTO(oidcOpEntityDAO.save(oidcOpEntity)); + } + + @PreAuthorize("hasRole('" + AMEntitlement.OIDC_OP_ENTITY_GET + "') " + + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')") + @Transactional(readOnly = true) + public OIDCOpEntityTO get() { + return oidcOpEntityDAO.get(). + map(binder::getOIDCOpEntityTO). + orElseThrow(() -> new NotFoundException("OIDC OP not found")); + } + + @PreAuthorize("hasRole('" + AMEntitlement.OIDC_OP_ENTITY_SET + "') " + + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')") + public OIDCOpEntityTO set(final OIDCOpEntityTO oidcOpEntityTO) { + OIDCOpEntity oidcOpEntity = oidcOpEntityDAO.get().orElseGet(() -> entityFactory.newEntity(OIDCOpEntity.class)); + binder.update(oidcOpEntity, oidcOpEntityTO); + + return binder.getOIDCOpEntityTO(oidcOpEntityDAO.save(oidcOpEntity)); + } + + @PreAuthorize("hasRole('" + AMEntitlement.OIDC_OP_ENTITY_DELETE + "')") + public void delete() { + oidcOpEntityDAO.delete(); + } + + @Override + protected OIDCOpEntityTO resolveReference(final Method method, final Object... args) + throws UnresolvedReferenceException { + + OIDCOpEntity oidcOp = oidcOpEntityDAO.get().orElseThrow(UnresolvedReferenceException::new); + return binder.getOIDCOpEntityTO(oidcOp); + } +} 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 41bcebdf7d5..157f8363067 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 @@ -23,7 +23,7 @@ import org.apache.syncope.common.rest.api.service.AuthProfileSelfService; 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.OIDCOpEntityService; 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; @@ -39,7 +39,7 @@ import org.apache.syncope.core.logic.AuthModuleLogic; 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.OIDCOpEntityLogic; import org.apache.syncope.core.logic.PasswordManagementLogic; import org.apache.syncope.core.logic.SAML2IdPEntityLogic; import org.apache.syncope.core.logic.SRARouteLogic; @@ -55,7 +55,7 @@ import org.apache.syncope.core.rest.cxf.service.AuthProfileSelfServiceImpl; 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.OIDCOpEntityServiceImpl; 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; @@ -134,8 +134,8 @@ public ImpersonationService impersonationService(final ImpersonationLogic impers @ConditionalOnMissingBean @Bean - public OIDCJWKSService oidcJWKSService(final OIDCJWKSLogic oidcJWKSLogic) { - return new OIDCJWKSServiceImpl(oidcJWKSLogic); + public OIDCOpEntityService oidcOpService(final OIDCOpEntityLogic oidcOpLogic) { + return new OIDCOpEntityServiceImpl(oidcOpLogic); } @ConditionalOnMissingBean diff --git a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCJWKSServiceImpl.java b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCOpEntityServiceImpl.java similarity index 69% rename from core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCJWKSServiceImpl.java rename to core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCOpEntityServiceImpl.java index 2ab21d57774..ff13e45c610 100644 --- a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCJWKSServiceImpl.java +++ b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCOpEntityServiceImpl.java @@ -20,31 +20,31 @@ import jakarta.ws.rs.core.Response; import java.net.URI; -import org.apache.syncope.common.lib.to.OIDCJWKSTO; -import org.apache.syncope.common.rest.api.service.OIDCJWKSService; -import org.apache.syncope.core.logic.OIDCJWKSLogic; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; +import org.apache.syncope.common.rest.api.service.OIDCOpEntityService; +import org.apache.syncope.core.logic.OIDCOpEntityLogic; -public class OIDCJWKSServiceImpl extends AbstractService implements OIDCJWKSService { +public class OIDCOpEntityServiceImpl extends AbstractService implements OIDCOpEntityService { - protected final OIDCJWKSLogic logic; + protected final OIDCOpEntityLogic logic; - public OIDCJWKSServiceImpl(final OIDCJWKSLogic logic) { + public OIDCOpEntityServiceImpl(final OIDCOpEntityLogic logic) { this.logic = logic; } @Override - public OIDCJWKSTO get() { + public OIDCOpEntityTO get() { return logic.get(); } @Override - public void set(final OIDCJWKSTO entityTO) { - logic.set(entityTO); + public void set(final OIDCOpEntityTO oidcOpEntityTO) { + logic.set(oidcOpEntityTO); } @Override public Response generate(final String jwksKeyId, final String jwksType, final int jwksKeySize) { - OIDCJWKSTO jwks = logic.generate(jwksKeyId, jwksType, jwksKeySize); + OIDCOpEntityTO jwks = logic.generate(jwksKeyId, jwksType, jwksKeySize); URI location = uriInfo.getAbsolutePathBuilder().build(); return Response.created(location).entity(jwks).build(); } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/OIDCJWKSDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/OIDCOpEntityDAO.java similarity index 83% rename from core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/OIDCJWKSDAO.java rename to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/OIDCOpEntityDAO.java index b3d16a17ef8..a33f9237c63 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/OIDCJWKSDAO.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/OIDCOpEntityDAO.java @@ -19,13 +19,13 @@ package org.apache.syncope.core.persistence.api.dao; import java.util.Optional; -import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS; +import org.apache.syncope.core.persistence.api.entity.am.OIDCOpEntity; -public interface OIDCJWKSDAO { +public interface OIDCOpEntityDAO { - Optional get(); + Optional get(); - OIDCJWKS save(OIDCJWKS jwks); + OIDCOpEntity save(OIDCOpEntity oidcOp); void delete(); } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/OIDCJWKS.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/OIDCOpEntity.java similarity index 82% rename from core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/OIDCJWKS.java rename to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/OIDCOpEntity.java index b111c971b1d..c5aa01e3a8e 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/OIDCJWKS.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/OIDCOpEntity.java @@ -18,12 +18,15 @@ */ package org.apache.syncope.core.persistence.api.entity.am; +import java.util.Map; +import java.util.Set; import org.apache.syncope.core.persistence.api.entity.Entity; -public interface OIDCJWKS extends Entity { +public interface OIDCOpEntity extends Entity { - String getJson(); + String getJWKS(); - void setJson(String json); + void setJWKS(String jwks); + Map> getCustomScopes(); } diff --git a/core/persistence-jpa-upgrader/src/main/java/org/apache/syncope/core/persistence/jpa/upgrade/GenerateUpgradeSQL.java b/core/persistence-jpa-upgrader/src/main/java/org/apache/syncope/core/persistence/jpa/upgrade/GenerateUpgradeSQL.java index cc962cb6f61..afec9a7556f 100644 --- a/core/persistence-jpa-upgrader/src/main/java/org/apache/syncope/core/persistence/jpa/upgrade/GenerateUpgradeSQL.java +++ b/core/persistence-jpa-upgrader/src/main/java/org/apache/syncope/core/persistence/jpa/upgrade/GenerateUpgradeSQL.java @@ -38,6 +38,9 @@ public class GenerateUpgradeSQL { UPDATE SyncopeGroup SET groupOwner_id=gManager_id; ALTER TABLE SyncopeGroup DROP COLUMN groupOwner_id; + INSERT INTO OIDCOpEntity SELECT id,json AS jwks FROM OIDCJWKS; + DROP TABLE OIDCJWKS; + DROP TABLE SyncopeRole_DynRealm; DROP TABLE DynRealmMembership; DROP TABLE DynRealm; diff --git a/core/persistence-jpa-upgrader/src/main/resources/schema-mariadb.xml b/core/persistence-jpa-upgrader/src/main/resources/schema-mariadb.xml index b026707ea15..25aa8bbebd1 100644 --- a/core/persistence-jpa-upgrader/src/main/resources/schema-mariadb.xml +++ b/core/persistence-jpa-upgrader/src/main/resources/schema-mariadb.xml @@ -705,10 +705,11 @@ under the License. - +
- + +
@@ -1439,4 +1440,4 @@ under the License.
- \ No newline at end of file + diff --git a/core/persistence-jpa-upgrader/src/main/resources/schema-mysql.xml b/core/persistence-jpa-upgrader/src/main/resources/schema-mysql.xml index b9bdbc91f58..91d6c120310 100644 --- a/core/persistence-jpa-upgrader/src/main/resources/schema-mysql.xml +++ b/core/persistence-jpa-upgrader/src/main/resources/schema-mysql.xml @@ -705,10 +705,11 @@ under the License. - +
- + +
@@ -1439,4 +1440,4 @@ under the License.
- \ No newline at end of file + diff --git a/core/persistence-jpa-upgrader/src/main/resources/schema-oracle.xml b/core/persistence-jpa-upgrader/src/main/resources/schema-oracle.xml index 405ad454b0f..22d42a3a43c 100644 --- a/core/persistence-jpa-upgrader/src/main/resources/schema-oracle.xml +++ b/core/persistence-jpa-upgrader/src/main/resources/schema-oracle.xml @@ -705,10 +705,11 @@ under the License. - +
- + +
@@ -1439,4 +1440,4 @@ under the License.
- \ No newline at end of file + diff --git a/core/persistence-jpa-upgrader/src/main/resources/schema-postgresql.xml b/core/persistence-jpa-upgrader/src/main/resources/schema-postgresql.xml index 71779429317..bbe40d11fb3 100644 --- a/core/persistence-jpa-upgrader/src/main/resources/schema-postgresql.xml +++ b/core/persistence-jpa-upgrader/src/main/resources/schema-postgresql.xml @@ -705,10 +705,11 @@ under the License. - +
- + +
@@ -1439,4 +1440,4 @@ under the License.
- \ No newline at end of file + diff --git a/core/persistence-jpa-upgrader/src/test/java/org/apache/syncope/core/persistence/jpa/upgrade/VerifySyncope4Test.java b/core/persistence-jpa-upgrader/src/test/java/org/apache/syncope/core/persistence/jpa/upgrade/VerifySyncope4Test.java index fe957006501..8f8d225019e 100644 --- a/core/persistence-jpa-upgrader/src/test/java/org/apache/syncope/core/persistence/jpa/upgrade/VerifySyncope4Test.java +++ b/core/persistence-jpa-upgrader/src/test/java/org/apache/syncope/core/persistence/jpa/upgrade/VerifySyncope4Test.java @@ -18,35 +18,20 @@ */ package org.apache.syncope.core.persistence.jpa.upgrade; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import jakarta.persistence.EntityManager; -import jakarta.persistence.TypedQuery; -import java.util.List; import org.apache.syncope.common.lib.types.AMEntitlement; -import org.apache.syncope.common.lib.types.AttrSchemaType; -import org.apache.syncope.common.lib.types.ConnPoolConf; import org.apache.syncope.common.lib.types.EntitlementsHolder; import org.apache.syncope.common.lib.types.IdMEntitlement; import org.apache.syncope.common.lib.types.IdRepoEntitlement; -import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; -import org.apache.syncope.core.persistence.api.dao.ConnInstanceDAO; -import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO; +import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO; +import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; import org.apache.syncope.core.persistence.api.dao.GroupDAO; -import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; -import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO; -import org.apache.syncope.core.persistence.api.dao.RoleDAO; -import org.apache.syncope.core.persistence.api.dao.UserDAO; -import org.apache.syncope.core.persistence.api.entity.ConnInstance; -import org.apache.syncope.core.persistence.api.entity.ExternalResource; -import org.apache.syncope.core.persistence.api.entity.PlainSchema; -import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; +import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.group.Group; -import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension; import org.apache.syncope.core.persistence.jpa.MasterDomain; -import org.apache.syncope.core.persistence.jpa.entity.JPAConnInstance; -import org.apache.syncope.core.persistence.jpa.entity.JPAExternalResource; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -71,122 +56,23 @@ public static void init() { EntitlementsHolder.getInstance().addAll(AMEntitlement.values()); } - @Autowired - private ConnInstanceDAO connInstanceDAO; - - @Autowired - private ExternalResourceDAO resourceDAO; - - @Autowired - private PlainSchemaDAO plainSchemaDAO; - - @Autowired - private RoleDAO roleDAO; - - @Autowired - private RelationshipTypeDAO relationshipTypeDAO; - - @Autowired - private AnyObjectDAO anyObjectDAO; - @Autowired private GroupDAO groupDAO; @Autowired - private UserDAO userDAO; + private AnyTypeDAO anyTypeDAO; @Autowired - private EntityManager entityManager; - - @Test - void connectors() { - long count = connInstanceDAO.count(); - - TypedQuery query = entityManager.createQuery( - "SELECT e FROM " + JPAConnInstance.class.getSimpleName() + " e", ConnInstance.class); - - List connectors = query.getResultList(); - - assertEquals(count, connectors.size()); - - ConnPoolConf poolConf = connInstanceDAO.findById( - "74141a3b-0762-4720-a4aa-fc3e374ef3ef").orElseThrow().getPoolConf(); - assertEquals(3, poolConf.getMaxIdle()); - assertEquals(5, poolConf.getMaxObjects()); - assertEquals(10, poolConf.getMaxWait()); - assertEquals(5, poolConf.getMinEvictableIdleTimeMillis()); - assertEquals(2, poolConf.getMinIdle()); - } - - @Test - void resources() { - long count = resourceDAO.count(); - - TypedQuery query = entityManager.createQuery( - "SELECT e FROM " + JPAExternalResource.class.getSimpleName() + " e", ExternalResource.class); - - List resources = query.getResultList(); - - assertEquals(count, resources.size()); - - assertEquals( - "20ab5a8c-4b0c-432c-b957-f7fb9784d9f7", - resourceDAO.findById("resource-testdb").orElseThrow().getAccountPolicy().getKey()); - } - - @Test - void plainSchemas() { - long count = plainSchemaDAO.count(); - - List plainSchemas = plainSchemaDAO.findAll(); - - assertEquals(count, plainSchemas.size()); - - PlainSchema gender = plainSchemaDAO.findById("gender").orElseThrow(); - assertEquals(AttrSchemaType.Enum, gender.getType()); - assertEquals(2, gender.getEnumValues().size()); - assertTrue(gender.getEnumValues().containsKey("M")); - assertTrue(gender.getEnumValues().containsValue("Male")); - assertTrue(gender.getEnumValues().containsKey("F")); - assertTrue(gender.getEnumValues().containsValue("Female")); - } - - @Test - void relationshipTypes() { - assertTrue(relationshipTypeDAO.findAll().stream(). - allMatch(r -> r.getLeftEndAnyType() != null && r.getRightEndAnyType() != null)); - } - - @Test - void anyObjects() { - long count = anyObjectDAO.count(); - - List anyObjects = anyObjectDAO.findAll(); - - assertEquals(count, anyObjects.size()); - } + private AnyTypeClassDAO anyTypeClassDAO; @Test void groups() { - long count = groupDAO.count(); - - List groups = groupDAO.findAll(); - - assertEquals(count, groups.size()); - } - - @Test - void users() { - long count = userDAO.count(); - - List users = userDAO.findAll(); - - assertEquals(count, users.size()); + Group group = groupDAO.findById("f779c0d4-633b-4be5-8f57-32eb478a3ca5").orElseThrow(); + assertFalse(group.getTypeExtensions().isEmpty()); - assertEquals( - "Antonio", - userDAO.findByUsername("vivaldi").orElseThrow(). - getPlainAttr("firstname").orElseThrow(). - getValuesAsStrings().getFirst()); + GroupTypeExtension te = group.getTypeExtension(anyTypeDAO.findById("PRINTER").orElseThrow()).orElseThrow(); + assertFalse(te.getAuxClasses().isEmpty()); + AnyTypeClass other = anyTypeClassDAO.findById("other").orElseThrow(); + assertTrue(te.getAuxClasses().stream().anyMatch(other::equals)); } } 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 0c7e13fdc98..29f1073fa34 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 @@ -53,7 +53,7 @@ import org.apache.syncope.core.persistence.api.dao.JobStatusDAO; import org.apache.syncope.core.persistence.api.dao.MailTemplateDAO; 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.OIDCOpEntityDAO; 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; @@ -89,7 +89,7 @@ import org.apache.syncope.core.persistence.jpa.dao.JPABatchDAO; import org.apache.syncope.core.persistence.jpa.dao.JPAEntityCacheDAO; import org.apache.syncope.core.persistence.jpa.dao.JPAJobStatusDAO; -import org.apache.syncope.core.persistence.jpa.dao.JPAOIDCJWKSDAO; +import org.apache.syncope.core.persistence.jpa.dao.JPAOIDCOpEntityDAO; import org.apache.syncope.core.persistence.jpa.dao.JPAPersistenceInfoDAO; import org.apache.syncope.core.persistence.jpa.dao.JPAPolicyDAO; import org.apache.syncope.core.persistence.jpa.dao.JPARealmDAO; @@ -704,8 +704,8 @@ public NotificationDAO notificationDAO( @ConditionalOnMissingBean @Bean - public OIDCJWKSDAO oidcJWKSDAO(final EntityManager entityManager) { - return new JPAOIDCJWKSDAO(entityManager); + public OIDCOpEntityDAO oidcOpEntityDAO(final EntityManager entityManager) { + return new JPAOIDCOpEntityDAO(entityManager); } @ConditionalOnMissingBean diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCJWKSDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCOpEntityDAO.java similarity index 66% rename from core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCJWKSDAO.java rename to core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCOpEntityDAO.java index 87c524fcce4..9d8f28d0baa 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCJWKSDAO.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAOIDCOpEntityDAO.java @@ -22,29 +22,29 @@ import jakarta.persistence.NoResultException; import jakarta.persistence.TypedQuery; import java.util.Optional; -import org.apache.syncope.core.persistence.api.dao.OIDCJWKSDAO; -import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS; -import org.apache.syncope.core.persistence.jpa.entity.am.JPAOIDCJWKS; +import org.apache.syncope.core.persistence.api.dao.OIDCOpEntityDAO; +import org.apache.syncope.core.persistence.api.entity.am.OIDCOpEntity; +import org.apache.syncope.core.persistence.jpa.entity.am.JPAOIDCOpEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.annotation.Transactional; -public class JPAOIDCJWKSDAO implements OIDCJWKSDAO { +public class JPAOIDCOpEntityDAO implements OIDCOpEntityDAO { - private static final Logger LOG = LoggerFactory.getLogger(OIDCJWKSDAO.class); + private static final Logger LOG = LoggerFactory.getLogger(OIDCOpEntityDAO.class); protected final EntityManager entityManager; - public JPAOIDCJWKSDAO(final EntityManager entityManager) { + public JPAOIDCOpEntityDAO(final EntityManager entityManager) { this.entityManager = entityManager; } @Transactional(readOnly = true) @Override - public Optional get() { + public Optional get() { try { - TypedQuery query = entityManager.createQuery( - "SELECT e FROM " + JPAOIDCJWKS.class.getSimpleName() + " e", OIDCJWKS.class); + TypedQuery query = entityManager.createQuery( + "SELECT e FROM " + JPAOIDCOpEntity.class.getSimpleName() + " e", OIDCOpEntity.class); return Optional.ofNullable(query.getSingleResult()); } catch (NoResultException e) { LOG.debug("No OIDC JWKS found", e); @@ -53,12 +53,15 @@ public Optional get() { } @Override - public OIDCJWKS save(final OIDCJWKS jwks) { - return entityManager.merge(jwks); + public OIDCOpEntity save(final OIDCOpEntity oidcOpEntity) { + ((JPAOIDCOpEntity) oidcOpEntity).map2json(); + OIDCOpEntity merged = entityManager.merge(oidcOpEntity); + ((JPAOIDCOpEntity) merged).postSave(); + return merged; } @Override public void delete() { - entityManager.createQuery("DELETE FROM " + JPAOIDCJWKS.class.getSimpleName()).executeUpdate(); + entityManager.createQuery("DELETE FROM " + JPAOIDCOpEntity.class.getSimpleName()).executeUpdate(); } } 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 5252e878c4e..5b4b8b4c95f 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 @@ -50,7 +50,7 @@ import org.apache.syncope.core.persistence.api.entity.am.AuthModule; import org.apache.syncope.core.persistence.api.entity.am.AuthProfile; 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.OIDCOpEntity; 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; @@ -95,7 +95,7 @@ import org.apache.syncope.core.persistence.jpa.entity.am.JPAAuthModule; import org.apache.syncope.core.persistence.jpa.entity.am.JPAAuthProfile; 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.JPAOIDCOpEntity; 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; @@ -275,8 +275,8 @@ public E newEntity(final Class reference) { result = (E) new JPASAML2IdPEntity(); } else if (reference.equals(AuthProfile.class)) { result = (E) new JPAAuthProfile(); - } else if (reference.equals(OIDCJWKS.class)) { - result = (E) new JPAOIDCJWKS(); + } else if (reference.equals(OIDCOpEntity.class)) { + result = (E) new JPAOIDCOpEntity(); } else if (reference.equals(WAConfigEntry.class)) { result = (E) new JPAWAConfigEntry(); } else if (reference.equals(ConfParam.class)) { diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPAOIDCJWKS.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPAOIDCJWKS.java deleted file mode 100644 index c900763c240..00000000000 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPAOIDCJWKS.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Lob; -import jakarta.persistence.Table; -import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS; -import org.apache.syncope.core.persistence.jpa.entity.AbstractGeneratedKeyEntity; - -@Entity -@Table(name = JPAOIDCJWKS.TABLE) -public class JPAOIDCJWKS extends AbstractGeneratedKeyEntity implements OIDCJWKS { - - public static final String TABLE = "OIDCJWKS"; - - private static final long serialVersionUID = 47352617217394093L; - - @Column(nullable = false) - @Lob - private String json; - - @Override - public String getJson() { - return this.json; - } - - @Override - public void setJson(final String json) { - this.json = json; - } -} diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPAOIDCOpEntity.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPAOIDCOpEntity.java new file mode 100644 index 00000000000..4f14f8d642b --- /dev/null +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPAOIDCOpEntity.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.core.persistence.jpa.entity.am; + +import com.fasterxml.jackson.core.type.TypeReference; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Lob; +import jakarta.persistence.PostLoad; +import jakarta.persistence.PostPersist; +import jakarta.persistence.PostUpdate; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import org.apache.syncope.core.persistence.api.entity.am.OIDCOpEntity; +import org.apache.syncope.core.persistence.jpa.entity.AbstractGeneratedKeyEntity; +import org.apache.syncope.core.provisioning.api.serialization.POJOHelper; + +@Entity +@Table(name = JPAOIDCOpEntity.TABLE) +public class JPAOIDCOpEntity extends AbstractGeneratedKeyEntity implements OIDCOpEntity { + + private static final long serialVersionUID = 47352617217394093L; + + public static final String TABLE = "OIDCOpEntity"; + + protected static final TypeReference>> CUSTOMSCOPES_TYPEREF = + new TypeReference>>() { + }; + + @Column(nullable = false) + @Lob + private String jwks; + + @Lob + private String customScopes; + + @Transient + private Map> customScopesMap = new HashMap<>(); + + @Override + public String getJWKS() { + return jwks; + } + + @Override + public void setJWKS(final String jwks) { + this.jwks = jwks; + } + + @Override + public Map> getCustomScopes() { + return customScopesMap; + } + + protected void json2map(final boolean clearFirst) { + if (clearFirst) { + getCustomScopes().clear(); + } + if (customScopes != null) { + getCustomScopes().putAll(POJOHelper.deserialize(customScopes, CUSTOMSCOPES_TYPEREF)); + } + } + + @PostLoad + public void postLoad() { + json2map(false); + } + + @PostPersist + @PostUpdate + public void postSave() { + json2map(true); + } + + @PrePersist + @PreUpdate + public void map2json() { + customScopes = POJOHelper.serialize(getCustomScopes()); + } +} diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCJWKSTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCOpEntityTest.java similarity index 55% rename from core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCJWKSTest.java rename to core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCOpEntityTest.java index d1c7c6ca4ff..515b8390d72 100644 --- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCJWKSTest.java +++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCOpEntityTest.java @@ -18,39 +18,47 @@ */ package org.apache.syncope.core.persistence.jpa.inner; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; +import java.util.Set; import java.util.UUID; -import org.apache.syncope.core.persistence.api.dao.OIDCJWKSDAO; -import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS; +import org.apache.syncope.core.persistence.api.dao.OIDCOpEntityDAO; +import org.apache.syncope.core.persistence.api.entity.am.OIDCOpEntity; 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 OIDCJWKSTest extends AbstractTest { +public class OIDCOpEntityTest extends AbstractTest { @Autowired - private OIDCJWKSDAO jwksDAO; + private OIDCOpEntityDAO oidcOpEntityDAO; @Test public void save() throws Exception { - OIDCJWKS jwks = entityFactory.newEntity(OIDCJWKS.class); - - RSAKey jwk = new RSAKeyGenerator(2048) - .keyUse(KeyUse.SIGNATURE) - .keyID(UUID.randomUUID().toString()) - .generate(); - - String json = new JWKSet(jwk).toString(); - jwks.setJson(json); - jwks = jwksDAO.save(jwks); - assertNotNull(jwks); - assertNotNull(jwks.getKey()); + OIDCOpEntity oidcOpEntity = entityFactory.newEntity(OIDCOpEntity.class); + + RSAKey jwk = new RSAKeyGenerator(2048). + keyUse(KeyUse.SIGNATURE). + keyID(UUID.randomUUID().toString()). + generate(); + oidcOpEntity.setJWKS(new JWKSet(jwk).toString()); + + oidcOpEntity.getCustomScopes().put("scope1", Set.of("claim1", "claim2")); + oidcOpEntity.getCustomScopes().put("scope2", Set.of("claim1", "claim3", "claim4")); + + oidcOpEntity = oidcOpEntityDAO.save(oidcOpEntity); + entityManager.flush(); + + assertNotNull(oidcOpEntity); + assertNotNull(oidcOpEntity.getKey()); + assertEquals(2, oidcOpEntity.getCustomScopes().size()); + assertEquals(Set.of("claim1", "claim2"), oidcOpEntity.getCustomScopes().get("scope1")); } } 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 939c2ca8cf6..f260a5aa15b 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 @@ -56,7 +56,7 @@ import org.apache.syncope.core.persistence.api.dao.JobStatusDAO; import org.apache.syncope.core.persistence.api.dao.MailTemplateDAO; 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.OIDCOpEntityDAO; 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; @@ -95,7 +95,7 @@ import org.apache.syncope.core.persistence.neo4j.dao.Neo4jBatchDAO; import org.apache.syncope.core.persistence.neo4j.dao.Neo4jEntityCacheDAO; import org.apache.syncope.core.persistence.neo4j.dao.Neo4jJobStatusDAO; -import org.apache.syncope.core.persistence.neo4j.dao.Neo4jOIDCJWKSDAO; +import org.apache.syncope.core.persistence.neo4j.dao.Neo4jOIDCOpEntityDAO; import org.apache.syncope.core.persistence.neo4j.dao.Neo4jPersistenceInfoDAO; import org.apache.syncope.core.persistence.neo4j.dao.Neo4jPolicyDAO; import org.apache.syncope.core.persistence.neo4j.dao.Neo4jRealmDAO; @@ -996,8 +996,8 @@ public NotificationDAO notificationDAO( @ConditionalOnMissingBean @Bean - public OIDCJWKSDAO oidcJWKSDAO(final Neo4jTemplate neo4jTemplate, final NodeValidator nodeValidator) { - return new Neo4jOIDCJWKSDAO(neo4jTemplate, nodeValidator); + public OIDCOpEntityDAO oidcOpEntityDAO(final Neo4jTemplate neo4jTemplate, final NodeValidator nodeValidator) { + return new Neo4jOIDCOpEntityDAO(neo4jTemplate, nodeValidator); } @ConditionalOnMissingBean diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jOIDCJWKSDAO.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jOIDCOpEntityDAO.java similarity index 65% rename from core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jOIDCJWKSDAO.java rename to core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jOIDCOpEntityDAO.java index 99d98911e5a..8280fae9c45 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jOIDCJWKSDAO.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jOIDCOpEntityDAO.java @@ -19,37 +19,40 @@ package org.apache.syncope.core.persistence.neo4j.dao; import java.util.Optional; -import org.apache.syncope.core.persistence.api.dao.OIDCJWKSDAO; -import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS; -import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jOIDCJWKS; +import org.apache.syncope.core.persistence.api.dao.OIDCOpEntityDAO; +import org.apache.syncope.core.persistence.api.entity.am.OIDCOpEntity; +import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jOIDCOpEntity; import org.apache.syncope.core.persistence.neo4j.spring.NodeValidator; import org.springframework.data.neo4j.core.Neo4jTemplate; import org.springframework.transaction.annotation.Transactional; -public class Neo4jOIDCJWKSDAO implements OIDCJWKSDAO { +public class Neo4jOIDCOpEntityDAO implements OIDCOpEntityDAO { protected final Neo4jTemplate neo4jTemplate; protected final NodeValidator nodeValidator; - public Neo4jOIDCJWKSDAO(final Neo4jTemplate neo4jTemplate, final NodeValidator nodeValidator) { + public Neo4jOIDCOpEntityDAO(final Neo4jTemplate neo4jTemplate, final NodeValidator nodeValidator) { this.neo4jTemplate = neo4jTemplate; this.nodeValidator = nodeValidator; } @Transactional(readOnly = true) @Override - public Optional get() { - return neo4jTemplate.findAll(Neo4jOIDCJWKS.class).stream().findFirst().map(OIDCJWKS.class::cast); + public Optional get() { + return neo4jTemplate.findAll(Neo4jOIDCOpEntity.class).stream().findFirst().map(OIDCOpEntity.class::cast); } @Override - public OIDCJWKS save(final OIDCJWKS jwks) { - return neo4jTemplate.save(nodeValidator.validate(jwks)); + public OIDCOpEntity save(final OIDCOpEntity oidcOp) { + ((Neo4jOIDCOpEntity) oidcOp).map2json(); + OIDCOpEntity saved = neo4jTemplate.save(nodeValidator.validate(oidcOp)); + ((Neo4jOIDCOpEntity) saved).postSave(); + return saved; } @Override public void delete() { - neo4jTemplate.deleteAll(Neo4jOIDCJWKS.class); + neo4jTemplate.deleteAll(Neo4jOIDCOpEntity.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 4395777398e..d34a6521f94 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 @@ -51,7 +51,7 @@ import org.apache.syncope.core.persistence.api.entity.am.AuthModule; import org.apache.syncope.core.persistence.api.entity.am.AuthProfile; 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.OIDCOpEntity; 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; @@ -97,7 +97,7 @@ import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jAuthModule; import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jAuthProfile; import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jCASSPClientApp; -import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jOIDCJWKS; +import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jOIDCOpEntity; import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jOIDCRPClientApp; import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jSAML2IdPEntity; import org.apache.syncope.core.persistence.neo4j.entity.am.Neo4jSAML2SPClientApp; @@ -276,8 +276,8 @@ public E newEntity(final Class reference) { result = (E) new Neo4jSAML2IdPEntity(); } else if (reference.equals(AuthProfile.class)) { result = (E) new Neo4jAuthProfile(); - } else if (reference.equals(OIDCJWKS.class)) { - result = (E) new Neo4jOIDCJWKS(); + } else if (reference.equals(OIDCOpEntity.class)) { + result = (E) new Neo4jOIDCOpEntity(); } else if (reference.equals(WAConfigEntry.class)) { result = (E) new Neo4jWAConfigEntry(); } else if (reference.equals(ConfParam.class)) { diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4jOIDCJWKS.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4jOIDCJWKS.java deleted file mode 100644 index 7224ad9fb75..00000000000 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4jOIDCJWKS.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.syncope.core.persistence.api.entity.am.OIDCJWKS; -import org.apache.syncope.core.persistence.neo4j.entity.AbstractGeneratedKeyNode; -import org.springframework.data.neo4j.core.schema.Node; - -@Node(Neo4jOIDCJWKS.NODE) -public class Neo4jOIDCJWKS extends AbstractGeneratedKeyNode implements OIDCJWKS { - - public static final String NODE = "OIDCJWKS"; - - private static final long serialVersionUID = 47352617217394093L; - - @NotNull - private String json; - - @Override - public String getJson() { - return this.json; - } - - @Override - public void setJson(final String json) { - this.json = json; - } -} diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4jOIDCOpEntity.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4jOIDCOpEntity.java new file mode 100644 index 00000000000..c5d2fc0f0a7 --- /dev/null +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4jOIDCOpEntity.java @@ -0,0 +1,88 @@ +/* + * 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 com.fasterxml.jackson.core.type.TypeReference; +import jakarta.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import org.apache.syncope.core.persistence.api.entity.am.OIDCOpEntity; +import org.apache.syncope.core.persistence.neo4j.entity.AbstractGeneratedKeyNode; +import org.apache.syncope.core.provisioning.api.serialization.POJOHelper; +import org.springframework.data.annotation.Transient; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.PostLoad; + +@Node(Neo4jOIDCOpEntity.NODE) +public class Neo4jOIDCOpEntity extends AbstractGeneratedKeyNode implements OIDCOpEntity { + + private static final long serialVersionUID = 47352617217394093L; + + public static final String NODE = "OIDCOpEntity"; + + protected static final TypeReference>> CUSTOMSCOPES_TYPEREF = + new TypeReference>>() { + }; + + @NotNull + private String jwks; + + private String customScopes; + + @Transient + private Map> customScopesMap = new HashMap<>(); + + @Override + public String getJWKS() { + return jwks; + } + + @Override + public void setJWKS(final String jwks) { + this.jwks = jwks; + } + + @Override + public Map> getCustomScopes() { + return customScopesMap; + } + + protected void json2map(final boolean clearFirst) { + if (clearFirst) { + getCustomScopes().clear(); + } + if (customScopes != null) { + getCustomScopes().putAll(POJOHelper.deserialize(customScopes, CUSTOMSCOPES_TYPEREF)); + } + } + + @PostLoad + public void postLoad() { + json2map(false); + } + + public void postSave() { + json2map(true); + } + + public void map2json() { + customScopes = POJOHelper.serialize(getCustomScopes()); + } +} diff --git a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/OIDCJWKSTest.java b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/OIDCOpEntityTest.java similarity index 56% rename from core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/OIDCJWKSTest.java rename to core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/OIDCOpEntityTest.java index ff467e93d8d..ec88cfc591f 100644 --- a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/OIDCJWKSTest.java +++ b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/OIDCOpEntityTest.java @@ -18,39 +18,45 @@ */ package org.apache.syncope.core.persistence.neo4j.inner; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; +import java.util.Set; import java.util.UUID; -import org.apache.syncope.core.persistence.api.dao.OIDCJWKSDAO; -import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS; +import org.apache.syncope.core.persistence.api.dao.OIDCOpEntityDAO; +import org.apache.syncope.core.persistence.api.entity.am.OIDCOpEntity; 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 OIDCJWKSTest extends AbstractTest { +public class OIDCOpEntityTest extends AbstractTest { @Autowired - private OIDCJWKSDAO jwksDAO; + private OIDCOpEntityDAO oidcOpEntityDAO; @Test public void save() throws Exception { - OIDCJWKS jwks = entityFactory.newEntity(OIDCJWKS.class); - - RSAKey jwk = new RSAKeyGenerator(2048) - .keyUse(KeyUse.SIGNATURE) - .keyID(UUID.randomUUID().toString()) - .generate(); - - String json = new JWKSet(jwk).toString(); - jwks.setJson(json); - jwks = jwksDAO.save(jwks); - assertNotNull(jwks); - assertNotNull(jwks.getKey()); + OIDCOpEntity oidcOpEntity = entityFactory.newEntity(OIDCOpEntity.class); + + RSAKey jwk = new RSAKeyGenerator(2048). + keyUse(KeyUse.SIGNATURE). + keyID(UUID.randomUUID().toString()). + generate(); + oidcOpEntity.setJWKS(new JWKSet(jwk).toString()); + + oidcOpEntity.getCustomScopes().put("scope1", Set.of("claim1", "claim2")); + oidcOpEntity.getCustomScopes().put("scope2", Set.of("claim1", "claim3", "claim4")); + + oidcOpEntity = oidcOpEntityDAO.save(oidcOpEntity); + assertNotNull(oidcOpEntity); + assertNotNull(oidcOpEntity.getKey()); + assertEquals(2, oidcOpEntity.getCustomScopes().size()); + assertEquals(Set.of("claim1", "claim2"), oidcOpEntity.getCustomScopes().get("scope1")); } } diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCJWKSDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCOpEntityDataBinder.java similarity index 82% rename from core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCJWKSDataBinder.java rename to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCOpEntityDataBinder.java index 1e80199f410..20e9479e480 100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCJWKSDataBinder.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCOpEntityDataBinder.java @@ -18,10 +18,10 @@ */ package org.apache.syncope.core.provisioning.api.data; -import org.apache.syncope.common.lib.to.OIDCJWKSTO; -import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; +import org.apache.syncope.core.persistence.api.entity.am.OIDCOpEntity; -public interface OIDCJWKSDataBinder { +public interface OIDCOpEntityDataBinder { String PARAMETER_STATE = "state"; @@ -53,7 +53,9 @@ public long getState() { } } - OIDCJWKSTO getOIDCJWKSTO(OIDCJWKS jwks); + String generateJWKS(String jwksKeyId, String jwksType, int jwksKeySize); - OIDCJWKS create(String jwksKeyId, String jwksType, int jwksKeySize); + OIDCOpEntityTO getOIDCOpEntityTO(OIDCOpEntity oidcOpEntity); + + void update(OIDCOpEntity oidcOpEntity, OIDCOpEntityTO oidcOpEntityTO); } 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 66e92d4ab75..f95d8201dd1 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 @@ -47,6 +47,7 @@ import org.apache.syncope.core.persistence.api.dao.JobStatusDAO; import org.apache.syncope.core.persistence.api.dao.MailTemplateDAO; import org.apache.syncope.core.persistence.api.dao.NotificationDAO; +import org.apache.syncope.core.persistence.api.dao.OIDCOpEntityDAO; import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; import org.apache.syncope.core.persistence.api.dao.PolicyDAO; import org.apache.syncope.core.persistence.api.dao.RealmDAO; @@ -90,7 +91,7 @@ import org.apache.syncope.core.provisioning.api.data.GroupDataBinder; 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.OIDCOpEntityDataBinder; 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; @@ -132,7 +133,7 @@ import org.apache.syncope.core.provisioning.java.data.GroupDataBinderImpl; 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.OIDCOpEntityDataBinderImpl; 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; @@ -812,9 +813,10 @@ public AuthProfileDataBinder authProfileDataBinder(final EntityFactory entityFac public ClientAppDataBinder clientAppDataBinder( final PolicyDAO policyDAO, final RealmSearchDAO realmSearchDAO, + final OIDCOpEntityDAO oidcOEntityDAO, final EntityFactory entityFactory) { - return new ClientAppDataBinderImpl(policyDAO, realmSearchDAO, entityFactory); + return new ClientAppDataBinderImpl(policyDAO, realmSearchDAO, oidcOEntityDAO, entityFactory); } @ConditionalOnMissingBean @@ -917,8 +919,11 @@ public NotificationDataBinder notificationDataBinder( @ConditionalOnMissingBean @Bean - public OIDCJWKSDataBinder oidcJWKSDataBinder(final EntityFactory entityFactory) { - return new OIDCJWKSDataBinderImpl(entityFactory); + public OIDCOpEntityDataBinder oidcOpEntityDataBinder( + final WAConfigDAO waConfigDAO, + final EntityFactory entityFactory) { + + return new OIDCOpEntityDataBinderImpl(waConfigDAO, entityFactory); } @ConditionalOnMissingBean diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java index 5abdc08768c..6973dbf5fc6 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java @@ -18,7 +18,11 @@ */ package org.apache.syncope.core.provisioning.java.data; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; +import org.apache.syncope.common.lib.OIDCStandardScope; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.to.CASSPClientAppTO; import org.apache.syncope.common.lib.to.ClientAppTO; @@ -26,6 +30,7 @@ import org.apache.syncope.common.lib.to.SAML2SPClientAppTO; import org.apache.syncope.common.lib.types.ClientExceptionType; import org.apache.syncope.common.rest.api.service.SAML2IdPEntityService; +import org.apache.syncope.core.persistence.api.dao.OIDCOpEntityDAO; import org.apache.syncope.core.persistence.api.dao.PolicyDAO; import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO; import org.apache.syncope.core.persistence.api.entity.EntityFactory; @@ -47,15 +52,19 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder { protected final RealmSearchDAO realmSearchDAO; + protected final OIDCOpEntityDAO oidcOpEntityDAO; + protected final EntityFactory entityFactory; public ClientAppDataBinderImpl( final PolicyDAO policyDAO, final RealmSearchDAO realmSearchDAO, + final OIDCOpEntityDAO oidcOpEntityDAO, final EntityFactory entityFactory) { this.policyDAO = policyDAO; this.realmSearchDAO = realmSearchDAO; + this.oidcOpEntityDAO = oidcOpEntityDAO; this.entityFactory = entityFactory; } @@ -251,8 +260,6 @@ protected void doUpdate(final OIDCRPClientApp clientApp, final OIDCRPClientAppTO clientApp.getSupportedGrantTypes().addAll(clientAppTO.getSupportedGrantTypes()); clientApp.getSupportedResponseTypes().clear(); clientApp.getSupportedResponseTypes().addAll(clientAppTO.getSupportedResponseTypes()); - clientApp.getScopes().clear(); - clientApp.getScopes().addAll(clientAppTO.getScopes()); clientApp.setLogoutUri(clientAppTO.getLogoutUri()); clientApp.setJwks(clientAppTO.getJwks()); clientApp.setJwksUri(clientAppTO.getJwksUri()); @@ -263,6 +270,19 @@ protected void doUpdate(final OIDCRPClientApp clientApp, final OIDCRPClientAppTO clientApp.setRefreshTokenMaxActiveTokens(clientAppTO.getRefreshTokenMaxActiveTokens()); clientApp.setRefreshTokenTimeToKill(clientAppTO.getRefreshTokenTimeToKill()); clientApp.setDeviceTokenTimeToKill(clientAppTO.getDeviceTokenTimeToKill()); + + Set allowedScopes = new HashSet<>(); + Stream.of(OIDCStandardScope.values()).map(OIDCStandardScope::name).forEach(allowedScopes::add); + oidcOpEntityDAO.get().ifPresent(oidcOpEntity -> allowedScopes.addAll(oidcOpEntity.getCustomScopes().keySet())); + + if (!allowedScopes.containsAll(clientAppTO.getScopes())) { + SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidValues); + clientAppTO.getScopes().removeAll(allowedScopes); + sce.getElements().add("Undefined OIDC scope(s):" + clientAppTO.getScopes()); + throw sce; + } + clientApp.getScopes().clear(); + clientApp.getScopes().addAll(clientAppTO.getScopes()); } protected OIDCRPClientAppTO getOIDCClientAppTO(final OIDCRPClientApp clientApp) { diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCJWKSDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCOpEntityDataBinderImpl.java similarity index 61% rename from core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCJWKSDataBinderImpl.java rename to core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCOpEntityDataBinderImpl.java index 862c83decdd..732d22946bd 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCJWKSDataBinderImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCOpEntityDataBinderImpl.java @@ -22,11 +22,13 @@ import java.util.List; import java.util.Locale; import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.to.OIDCJWKSTO; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; import org.apache.syncope.common.lib.types.ClientExceptionType; +import org.apache.syncope.core.persistence.api.dao.WAConfigDAO; import org.apache.syncope.core.persistence.api.entity.EntityFactory; -import org.apache.syncope.core.persistence.api.entity.am.OIDCJWKS; -import org.apache.syncope.core.provisioning.api.data.OIDCJWKSDataBinder; +import org.apache.syncope.core.persistence.api.entity.am.OIDCOpEntity; +import org.apache.syncope.core.persistence.api.entity.am.WAConfigEntry; +import org.apache.syncope.core.provisioning.api.data.OIDCOpEntityDataBinder; import org.apache.syncope.core.spring.security.SecureRandomUtils; import org.jose4j.jwk.EcJwkGenerator; import org.jose4j.jwk.JsonWebKey; @@ -40,24 +42,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class OIDCJWKSDataBinderImpl implements OIDCJWKSDataBinder { +public class OIDCOpEntityDataBinderImpl implements OIDCOpEntityDataBinder { - protected static final Logger LOG = LoggerFactory.getLogger(OIDCJWKSDataBinder.class); + protected static final Logger LOG = LoggerFactory.getLogger(OIDCOpEntityDataBinder.class); + + protected final WAConfigDAO waConfigDAO; protected final EntityFactory entityFactory; - public OIDCJWKSDataBinderImpl(final EntityFactory entityFactory) { + public OIDCOpEntityDataBinderImpl(final WAConfigDAO waConfigDAO, final EntityFactory entityFactory) { + this.waConfigDAO = waConfigDAO; this.entityFactory = entityFactory; } - @Override - public OIDCJWKSTO getOIDCJWKSTO(final OIDCJWKS jwks) { - return new OIDCJWKSTO.Builder(). - key(jwks.getKey()). - json(jwks.getJson()). - build(); - } - protected PublicJsonWebKey generate( final String jwksKeyId, final String jwksType, @@ -97,7 +94,7 @@ protected PublicJsonWebKey generate( } @Override - public OIDCJWKS create(final String jwksKeyId, final String jwksType, final int jwksKeySize) { + public String generateJWKS(final String jwksKeyId, final String jwksType, final int jwksKeySize) { List keys = new ArrayList<>(); try { keys.add(generate(jwksKeyId, jwksType, jwksKeySize, Use.SIGNATURE, JsonWebKeyLifecycleState.CURRENT)); @@ -105,15 +102,49 @@ public OIDCJWKS create(final String jwksKeyId, final String jwksType, final int keys.add(generate(jwksKeyId, jwksType, jwksKeySize, Use.SIGNATURE, JsonWebKeyLifecycleState.FUTURE)); keys.add(generate(jwksKeyId, jwksType, jwksKeySize, Use.ENCRYPTION, JsonWebKeyLifecycleState.FUTURE)); } catch (JoseException e) { - LOG.error("Could not create OIDC JWKS", e); + LOG.error("Could not generate OIDC JWKS", e); SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown); sce.getElements().add(e.getMessage()); throw sce; } - OIDCJWKS oidcJWKS = entityFactory.newEntity(OIDCJWKS.class); - oidcJWKS.setJson(new JsonWebKeySet(keys).toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE)); - return oidcJWKS; + WAConfigEntry jwksKeyIdConfig = entityFactory.newEntity(WAConfigEntry.class); + jwksKeyIdConfig.setKey("cas.authn.oidc.jwks.core.jwks-key-id"); + jwksKeyIdConfig.setValues(List.of(jwksKeyId)); + waConfigDAO.save(jwksKeyIdConfig); + + WAConfigEntry jwksTypeConfig = entityFactory.newEntity(WAConfigEntry.class); + jwksTypeConfig.setKey("cas.authn.oidc.jwks.core.jwks-type"); + jwksTypeConfig.setValues(List.of(jwksType)); + waConfigDAO.save(jwksTypeConfig); + + WAConfigEntry jwksKeySizeConfig = entityFactory.newEntity(WAConfigEntry.class); + jwksKeySizeConfig.setKey("cas.authn.oidc.jwks.core.jwks-key-size"); + jwksKeySizeConfig.setValues(List.of(String.valueOf(jwksKeySize))); + waConfigDAO.save(jwksKeySizeConfig); + + return new JsonWebKeySet(keys).toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE); + } + + @Override + public OIDCOpEntityTO getOIDCOpEntityTO(final OIDCOpEntity oidcOpEntity) { + OIDCOpEntityTO oidcOpEntityTO = new OIDCOpEntityTO(); + oidcOpEntityTO.setKey(oidcOpEntity.getKey()); + oidcOpEntityTO.setJWKS(oidcOpEntity.getJWKS()); + oidcOpEntityTO.getCustomScopes().putAll(oidcOpEntity.getCustomScopes()); + + return oidcOpEntityTO; + } + + @Override + public void update(final OIDCOpEntity oidcOpEntity, final OIDCOpEntityTO oidcOpEntityTO) { + oidcOpEntity.setJWKS(oidcOpEntityTO.getJWKS()); + if (oidcOpEntity.getJWKS() == null) { + oidcOpEntity.setJWKS(generateJWKS("syncope", "RSA", 2048)); + } + + oidcOpEntity.getCustomScopes().clear(); + oidcOpEntity.getCustomScopes().putAll(oidcOpEntityTO.getCustomScopes()); } } diff --git a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder.java b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder.java index 6d53e47eb13..51a02ed9182 100644 --- a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder.java +++ b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder.java @@ -23,6 +23,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.client.console.SyncopeConsoleSession; @@ -40,7 +41,7 @@ import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.client.ui.commons.markup.html.form.MultiFieldPanel; import org.apache.syncope.client.ui.commons.wizards.AjaxWizardBuilder; -import org.apache.syncope.common.lib.OIDCScopeConstants; +import org.apache.syncope.common.lib.OIDCStandardScope; import org.apache.syncope.common.lib.to.ImplementationTO; import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO; import org.apache.syncope.common.lib.types.OIDCClientImplementationType; @@ -269,7 +270,7 @@ protected void onUpdate(final AjaxRequestTarget target) { }); AjaxTextFieldPanel value = new AjaxTextFieldPanel("panel", "scopes", new Model<>()); - value.setChoices(OIDCScopeConstants.ALL_STANDARD_SCOPES); + value.setChoices(Stream.of(OIDCStandardScope.values()).map(OIDCStandardScope::name).toList()); content.add(new MultiFieldPanel.Builder( new PropertyModel<>(opTO, "scopes")).build("scopes", "scopes", value)); } diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java index c7dc2c2ec46..bf6c1e82e61 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UILogic.java @@ -20,7 +20,9 @@ import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.JWTParser; import com.nimbusds.jwt.SignedJWT; +import com.nimbusds.oauth2.sdk.token.AccessToken; import java.lang.reflect.Method; import java.text.ParseException; import java.util.HashMap; @@ -169,8 +171,8 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat OidcClient oidcClient = getOidcClient(oidcClientCacheLogin, op, redirectURI); // 2. get OpenID Connect tokens - String idTokenHint; - JWTClaimsSet idToken; + String idTokenHint = null; + JWTClaimsSet claimsSet = null; try { OidcCredentials credentials = new OidcCredentials(); credentials.setCode(authorizationCode); @@ -178,27 +180,39 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat oidcClient.getAuthenticator().validate( new CallContext(new OIDCC4UIContext(), NoOpSessionStore.INSTANCE), credentials); - JWT jwt = credentials.toIdToken(); - idToken = jwt.getJWTClaimsSet(); - idTokenHint = jwt.serialize(); + JWT idToken = credentials.toIdToken(); + if (idToken == null) { + AccessToken accessToken = credentials.toAccessToken(); + if (accessToken != null) { + claimsSet = JWTParser.parse(accessToken.getValue()).getJWTClaimsSet(); + } + } else { + idTokenHint = idToken.serialize(); + claimsSet = idToken.getJWTClaimsSet(); + } } catch (Exception e) { LOG.error("While validating Token Response", e); SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown); sce.getElements().add(e.getMessage()); throw sce; } + if (claimsSet == null) { + SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown); + sce.getElements().add("Unable to extract OIDC claims"); + throw sce; + } // 3. prepare the result OIDCLoginResponse loginResp = new OIDCLoginResponse(); loginResp.setLogoutSupported(StringUtils.isNotBlank(op.getEndSessionEndpoint())); // 3a. find matching user (if any) and return the received attributes - String keyValue = idToken.getSubject(); + String keyValue = claimsSet.getSubject(); for (Item item : op.getItems()) { Attr attrTO = new Attr(); attrTO.setSchema(item.getExtAttrName()); - String value = Optional.ofNullable(idToken.getClaim(item.getExtAttrName())). + String value = Optional.ofNullable(claimsSet.getClaim(item.getExtAttrName())). map(Object::toString). orElse(null); if (value != null) { @@ -263,7 +277,7 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat // 4. generate JWT for further access Map claims = new HashMap<>(); claims.put(JWT_CLAIM_OP_NAME, opName); - claims.put(JWT_CLAIM_ID_TOKEN, idTokenHint); + Optional.ofNullable(idTokenHint).ifPresent(v -> claims.put(JWT_CLAIM_ID_TOKEN, v)); String authorities = null; try { @@ -274,7 +288,7 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat } AccessTokenDataBinder.AccessTokenInfo accessTokenInfo = accessTokenDataBinder.create( - Optional.ofNullable(idToken.getClaim(Pac4jConstants.OIDC_CLAIM_SESSIONID)).map(Object::toString), + Optional.ofNullable(claimsSet.getClaim(Pac4jConstants.OIDC_CLAIM_SESSIONID)).map(Object::toString), loginResp.getUsername(), claims, authorities, 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 9b170b71b04..a396c0768e0 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 @@ -144,7 +144,7 @@ import org.apache.syncope.common.rest.api.service.NotificationService; 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.OIDCOpEntityService; 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; @@ -377,7 +377,7 @@ public void initialize(final ConfigurableApplicationContext ctx) { protected static SAML2IdPEntityService SAML2IDP_ENTITY_SERVICE; - protected static OIDCJWKSService OIDC_JWKS_SERVICE; + protected static OIDCOpEntityService OIDC_OP_ENTITY_SERVICE; protected static WAConfigService WA_CONFIG_SERVICE; @@ -580,7 +580,7 @@ public static void restSetup() { 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); + OIDC_OP_ENTITY_SERVICE = ADMIN_CLIENT.getService(OIDCOpEntityService.class); WA_CONFIG_SERVICE = ADMIN_CLIENT.getService(WAConfigService.class); } diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java index 8c15d891af9..de79ce4833c 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java @@ -24,21 +24,45 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import jakarta.ws.rs.core.Response; +import java.util.Set; import java.util.UUID; import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.OIDCStandardScope; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.policy.AccessPolicyTO; import org.apache.syncope.common.lib.policy.AuthPolicyTO; import org.apache.syncope.common.lib.to.CASSPClientAppTO; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apache.syncope.common.lib.to.SAML2SPClientAppTO; import org.apache.syncope.common.lib.types.ClientAppType; +import org.apache.syncope.common.lib.types.ClientExceptionType; import org.apache.syncope.common.lib.types.PolicyType; import org.apache.syncope.fit.AbstractITCase; import org.junit.jupiter.api.Test; public class ClientAppITCase extends AbstractITCase { + private static OIDCOpEntityTO getOIDCOpEntityTO() { + OIDCOpEntityTO oidcOpEntityTO = null; + try { + oidcOpEntityTO = OIDC_OP_ENTITY_SERVICE.get(); + } catch (SyncopeClientException e) { + if (e.getType() == ClientExceptionType.NotFound) { + try (Response response = OIDC_OP_ENTITY_SERVICE.generate("syncope", "RSA", 2048)) { + oidcOpEntityTO = response.readEntity(OIDCOpEntityTO.class); + } catch (Exception ge) { + fail("While generating new OIDC JWKS", ge); + } + } else { + throw e; + } + } + assertNotNull(oidcOpEntityTO); + return oidcOpEntityTO; + } + @Test public void createSAML2SP() { createClientApp(ClientAppType.SAML2SP, buildSAML2SP()); @@ -140,6 +164,27 @@ public void updateOIDCRP() { OIDCRPClientAppTO oidcrpTO = buildOIDCRP(); oidcrpTO = createClientApp(ClientAppType.OIDCRP, oidcrpTO); + // attempt to set a scope not defined by OIDC OP -> fail + oidcrpTO.getScopes().add(OIDCStandardScope.openid.name()); + oidcrpTO.getScopes().add(OIDCStandardScope.profile.name()); + oidcrpTO.getScopes().add("missing"); + + OIDCOpEntityTO oidcOpEntityTO = getOIDCOpEntityTO(); + oidcOpEntityTO.getCustomScopes().clear(); + OIDC_OP_ENTITY_SERVICE.set(oidcOpEntityTO); + + try { + CLIENT_APP_SERVICE.update(ClientAppType.OIDCRP, oidcrpTO); + fail(); + } catch (SyncopeClientException e) { + assertEquals(ClientExceptionType.InvalidValues, e.getType()); + assertTrue(e.getElements().iterator().next().startsWith("Undefined OIDC scope(s)")); + } + + // adjust OIDC OP to support the new scope + oidcOpEntityTO.getCustomScopes().put("missing", Set.of()); + OIDC_OP_ENTITY_SERVICE.set(oidcOpEntityTO); + AccessPolicyTO accessPolicyTO = new AccessPolicyTO(); accessPolicyTO.setKey("NewAccessPolicyTest_" + getUUIDString()); accessPolicyTO.setName("New Access policy"); diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OIDCJWKSITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OIDCOpEntityITCase.java similarity index 70% rename from fit/core-reference/src/test/java/org/apache/syncope/fit/core/OIDCJWKSITCase.java rename to fit/core-reference/src/test/java/org/apache/syncope/fit/core/OIDCOpEntityITCase.java index de1001c6cef..9081e532eae 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OIDCJWKSITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OIDCOpEntityITCase.java @@ -24,38 +24,32 @@ import jakarta.ws.rs.core.Response; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.types.ClientExceptionType; -import org.apache.syncope.common.rest.api.service.OIDCJWKSService; +import org.apache.syncope.common.rest.api.service.OIDCOpEntityService; import org.apache.syncope.fit.AbstractITCase; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -public class OIDCJWKSITCase extends AbstractITCase { +public class OIDCOpEntityITCase extends AbstractITCase { - private static OIDCJWKSService WA_OIDC_JWKS_SERVICE; + private static OIDCOpEntityService WA_OIDC_OP_ENTITY_SERVICE; @BeforeAll public static void setup() { - WA_OIDC_JWKS_SERVICE = ANONYMOUS_CLIENT.getService(OIDCJWKSService.class); + WA_OIDC_OP_ENTITY_SERVICE = ANONYMOUS_CLIENT.getService(OIDCOpEntityService.class); } @Test public void deleteGetSet() { try { - OIDC_JWKS_SERVICE.delete(); + OIDC_OP_ENTITY_SERVICE.delete(); - WA_OIDC_JWKS_SERVICE.get(); + WA_OIDC_OP_ENTITY_SERVICE.get(); fail("Should not locate an OIDC JWKS"); } catch (SyncopeClientException e) { assertEquals(ClientExceptionType.NotFound, e.getType()); } - Response response = WA_OIDC_JWKS_SERVICE.generate("syncope", "RSA", 2048); + Response response = WA_OIDC_OP_ENTITY_SERVICE.generate("syncope", "RSA", 2048); assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); - try { - WA_OIDC_JWKS_SERVICE.generate("syncope", "RSA", 2048); - fail("Should not recreate an OIDC JWKS"); - } catch (SyncopeClientException e) { - assertEquals(ClientExceptionType.EntityExists, e.getType()); - } } } diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java index ad1596536b8..dfc00a6f11e 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java @@ -44,6 +44,7 @@ import org.apache.syncope.common.rest.api.service.ClientAppService; import org.apache.syncope.common.rest.api.service.ImplementationService; import org.apache.syncope.common.rest.api.service.OIDCC4UIProviderService; +import org.apache.syncope.common.rest.api.service.OIDCOpEntityService; import org.apache.syncope.common.rest.api.service.PolicyService; import org.apache.syncope.common.rest.api.service.SAML2IdPEntityService; import org.apache.syncope.common.rest.api.service.SAML2SP4UIIdPService; @@ -95,6 +96,8 @@ public abstract class AbstractITCase { protected static PolicyService POLICY_SERVICE; + protected static OIDCOpEntityService OIDC_OP_ENTITY_SERVICE; + protected static ClientAppService CLIENT_APP_SERVICE; protected static WAConfigService WA_CONFIG_SERVICE; @@ -114,6 +117,7 @@ public static void restSetup() { TASK_SERVICE = ADMIN_CLIENT.getService(TaskService.class); USER_SERVICE = ADMIN_CLIENT.getService(UserService.class); POLICY_SERVICE = ADMIN_CLIENT.getService(PolicyService.class); + OIDC_OP_ENTITY_SERVICE = ADMIN_CLIENT.getService(OIDCOpEntityService.class); CLIENT_APP_SERVICE = ADMIN_CLIENT.getService(ClientAppService.class); WA_CONFIG_SERVICE = ADMIN_CLIENT.getService(WAConfigService.class); SRA_ROUTE_SERVICE = ADMIN_CLIENT.getService(SRARouteService.class); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractOIDCITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractOIDCITCase.java index edab40638f6..be59aa302f9 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractOIDCITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractOIDCITCase.java @@ -41,6 +41,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.http.Consts; @@ -56,10 +57,11 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; -import org.apache.syncope.common.lib.OIDCScopeConstants; +import org.apache.syncope.common.lib.OIDCStandardScope; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO; import org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apache.syncope.common.lib.types.ClientAppType; import org.apache.syncope.common.lib.types.OIDCGrantType; @@ -67,12 +69,13 @@ import org.apache.syncope.common.lib.types.PolicyType; import org.apache.syncope.common.rest.api.RESTHeaders; import org.apache.syncope.common.rest.api.service.wa.WAConfigService; -import org.apereo.cas.oidc.OidcConstants; import org.jsoup.Jsoup; import org.junit.jupiter.api.Test; abstract class AbstractOIDCITCase extends AbstractSRAITCase { + protected static final String GROUPS_SCOPE = "groups"; + protected static String SRA_REGISTRATION_ID; protected static Long CLIENT_APP_ID; @@ -114,6 +117,23 @@ protected static AttrReleasePolicyTO getAttrReleasePolicy() { }); } + protected static void oidcOpEntitySetup() { + OIDCOpEntityTO oidcOpEntity; + try { + oidcOpEntity = OIDC_OP_ENTITY_SERVICE.get(); + } catch (Exception e) { + Response response = OIDC_OP_ENTITY_SERVICE.generate("syncope", "RSA", 2048); + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); + + oidcOpEntity = OIDC_OP_ENTITY_SERVICE.get(); + } + + if (!oidcOpEntity.getCustomScopes().containsKey(GROUPS_SCOPE)) { + oidcOpEntity.getCustomScopes().put(GROUPS_SCOPE, Set.of("groups")); + OIDC_OP_ENTITY_SERVICE.set(oidcOpEntity); + } + } + protected static void oidcClientAppSetup( final String appName, final String sraRegistrationId, @@ -121,6 +141,8 @@ protected static void oidcClientAppSetup( final String clientId, final String clientSecret) { + oidcOpEntitySetup(); + OIDCRPClientAppTO clientApp = CLIENT_APP_SERVICE.list(ClientAppType.OIDCRP).stream(). filter(app -> appName.equals(app.getName())). map(OIDCRPClientAppTO.class::cast). @@ -153,9 +175,10 @@ protected static void oidcClientAppSetup( clientApp.setLogoutUri(SRA_ADDRESS + "/logout"); clientApp.setAuthPolicy(getAuthPolicy().getKey()); clientApp.setAttrReleasePolicy(getAttrReleasePolicy().getKey()); - clientApp.getScopes().add(OIDCScopeConstants.OPEN_ID); - clientApp.getScopes().add(OIDCScopeConstants.PROFILE); - clientApp.getScopes().add(OIDCScopeConstants.EMAIL); + clientApp.getScopes().add(OIDCStandardScope.openid.name()); + clientApp.getScopes().add(OIDCStandardScope.profile.name()); + clientApp.getScopes().add(OIDCStandardScope.email.name()); + clientApp.getScopes().add(GROUPS_SCOPE); clientApp.getSupportedGrantTypes().add(OIDCGrantType.password); clientApp.getSupportedGrantTypes().add(OIDCGrantType.authorization_code); @@ -164,16 +187,9 @@ protected static void oidcClientAppSetup( await().atMost(120, TimeUnit.SECONDS).pollInterval(20, TimeUnit.SECONDS).until(() -> { try { String metadata = WebClient.create( - WA_ADDRESS + "/oidc/" + OidcConstants.WELL_KNOWN_OPENID_CONFIGURATION_URL). - get().readEntity(String.class); - if (!metadata.contains("groups")) { - WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of()); - throw new IllegalStateException(); - } - metadata = WebClient.create( WA_ADDRESS + "/actuator/env", ANONYMOUS_USER, ANONYMOUS_KEY, null). get().readEntity(String.class); - if (!metadata.contains("cas.authn.oidc.core.user-defined-scopes.syncope")) { + if (!metadata.contains("cas.authn.oidc.core.user-defined-scopes." + GROUPS_SCOPE)) { WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of()); throw new IllegalStateException(); } @@ -302,7 +318,7 @@ void rest() throws IOException, ParseException { param("client_secret", CLIENT_SECRET). param("username", "verdi"). param("password", "password"). - param("scope", "openid profile email syncope"); + param("scope", "openid profile email " + GROUPS_SCOPE); response = WebClient.create(TOKEN_URI).post(form); assertEquals(HttpStatus.SC_OK, response.getStatus()); assertTrue(response.getHeaderString(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.APPLICATION_JSON)); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java index 276c5681ce6..3a6e307d22b 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java @@ -33,6 +33,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.http.Consts; import org.apache.http.HttpHeaders; @@ -49,10 +50,11 @@ import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.apache.syncope.client.ui.commons.panels.OIDCC4UIConstants; -import org.apache.syncope.common.lib.OIDCScopeConstants; +import org.apache.syncope.common.lib.OIDCStandardScope; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.Item; import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.ClientAppType; @@ -66,7 +68,28 @@ public class OIDCC4UIITCase extends AbstractUIITCase { + private static final String ITCASE_SCOPE = "itcase"; + + private static void oidcOpEntitySetup() { + OIDCOpEntityTO oidcOpEntity; + try { + oidcOpEntity = OIDC_OP_ENTITY_SERVICE.get(); + } catch (Exception e) { + Response response = OIDC_OP_ENTITY_SERVICE.generate("syncope", "RSA", 2048); + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); + + oidcOpEntity = OIDC_OP_ENTITY_SERVICE.get(); + } + + if (!oidcOpEntity.getCustomScopes().containsKey(ITCASE_SCOPE)) { + oidcOpEntity.getCustomScopes().put(ITCASE_SCOPE, Set.of("identifier")); + OIDC_OP_ENTITY_SERVICE.set(oidcOpEntity); + } + } + private static void clientAppSetup(final String appName, final String baseAddress, final long appId) { + oidcOpEntitySetup(); + OIDCRPClientAppTO clientApp = CLIENT_APP_SERVICE.list(ClientAppType.OIDCRP).stream(). filter(app -> appName.equals(app.getName())). map(OIDCRPClientAppTO.class::cast). @@ -102,9 +125,10 @@ private static void clientAppSetup(final String appName, final String baseAddres Set.of(OIDCResponseType.CODE, OIDCResponseType.ID_TOKEN_TOKEN, OIDCResponseType.TOKEN)); clientApp.setAuthPolicy(getAuthPolicy().getKey()); clientApp.setAttrReleasePolicy(getAttrReleasePolicy().getKey()); - clientApp.getScopes().add(OIDCScopeConstants.OPEN_ID); - clientApp.getScopes().add(OIDCScopeConstants.PROFILE); - clientApp.getScopes().add(OIDCScopeConstants.EMAIL); + clientApp.getScopes().add(OIDCStandardScope.openid.name()); + clientApp.getScopes().add(OIDCStandardScope.profile.name()); + clientApp.getScopes().add(OIDCStandardScope.email.name()); + clientApp.getScopes().add(ITCASE_SCOPE); CLIENT_APP_SERVICE.update(ClientAppType.OIDCRP, clientApp); @@ -113,7 +137,7 @@ private static void clientAppSetup(final String appName, final String baseAddres String metadata = WebClient.create( WA_ADDRESS + "/actuator/env", ANONYMOUS_USER, ANONYMOUS_KEY, null). get().readEntity(String.class); - if (!metadata.contains("cas.authn.oidc.core.user-defined-scopes.syncope")) { + if (!metadata.contains("cas.authn.oidc.core.user-defined-scopes." + ITCASE_SCOPE)) { WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of()); throw new IllegalStateException(); } @@ -160,8 +184,8 @@ private static void oidcSetup( cas.setIssuer(WA_ADDRESS + "/oidc"); cas.setHasDiscovery(true); - cas.getScopes().addAll(OIDCScopeConstants.ALL_STANDARD_SCOPES); - cas.getScopes().add("syncope"); + Stream.of(OIDCStandardScope.values()).map(OIDCStandardScope::name).forEach(cas.getScopes()::add); + cas.getScopes().add(ITCASE_SCOPE); cas.setCreateUnmatching(createUnmatching); cas.setSelfRegUnmatching(selfRegUnmatching); 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 c78f86ae792..dea90b075a7 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 @@ -102,14 +102,12 @@ public PropertySourceLocator configPropertySourceLocator( final WARestClient waRestClient, final AuthModulePropertySourceMapper authModulePropertySourceMapper, final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper, - final PasswordManagementPropertySourceMapper passwordManagementPropertySourceMapper, - final AttrReleaseMapper attrReleaseMapper) { + final PasswordManagementPropertySourceMapper passwordManagementPropertySourceMapper) { return new WAPropertySourceLocator( waRestClient, authModulePropertySourceMapper, 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 6f3f3e7a087..40957c160fd 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 @@ -28,21 +28,17 @@ import org.apache.commons.lang3.StringUtils; 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.OIDCStandardScope; 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.OIDCOpEntityService; 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; -import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy; import org.apereo.cas.util.crypto.CipherExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,8 +61,6 @@ public class WAPropertySourceLocator implements PropertySourceLocator { protected final PasswordManagementPropertySourceMapper passwordManagementPropertySourceMapper; - protected final AttrReleaseMapper attrReleaseMapper; - protected final CipherExecutor configurationCipher; public WAPropertySourceLocator( @@ -74,14 +68,12 @@ public WAPropertySourceLocator( 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; } @@ -140,38 +132,31 @@ public PropertySource locate(final Environment environment) { properties.putAll(index(map, prefixes)); }); - Set customClaims = syncopeClient.getService(WAClientAppService.class).list().stream(). - filter(app -> app.getClientAppTO() instanceof OIDCRPClientAppTO && app.getAttrReleasePolicy() != null). - flatMap(app -> { - RegisteredServiceAttributeReleasePolicy attributeReleasePolicy = - attrReleaseMapper.build(app.getClientAppTO(), app.getAttrReleasePolicy()); - - if (attributeReleasePolicy instanceof OidcCustomScopeAttributeReleasePolicy custom) { - return custom.getAllowedAttributes().stream(); - } - - if (attributeReleasePolicy instanceof ChainingAttributeReleasePolicy chain) { - return chain.getPolicies().stream(). - filter(OidcCustomScopeAttributeReleasePolicy.class::isInstance). - map(OidcCustomScopeAttributeReleasePolicy.class::cast). - flatMap(p -> p.getAllowedAttributes().stream()); - } - - return Stream.empty(); - }).collect(Collectors.toSet()); - if (!customClaims.isEmpty()) { - Stream.concat(new OidcDiscoveryProperties().getClaims().stream(), customClaims.stream()). - collect(Collectors.joining(",")); - + Map> customScopes; + try { + customScopes = syncopeClient.getService(OIDCOpEntityService.class).get().getCustomScopes(); + } catch (Exception e) { + LOG.warn("Could not read OIDC OP: no custom scopes or claims will be set", e); + customScopes = Map.of(); + } + if (!customScopes.isEmpty()) { + properties.put("cas.authn.oidc.discovery.scopes", + Stream.concat(Stream.of(OIDCStandardScope.values()).map(OIDCStandardScope::name), + customScopes.keySet().stream()). + distinct(). + collect(Collectors.joining(","))); properties.put("cas.authn.oidc.discovery.claims", - Stream.concat(new OidcDiscoveryProperties().getClaims().stream(), customClaims.stream()). + Stream.concat(new OidcDiscoveryProperties().getClaims().stream(), + customScopes.values().stream().flatMap(Set::stream)). + distinct(). collect(Collectors.joining(","))); - properties.put("cas.authn.oidc.core.user-defined-scopes.syncope", - String.join(",", customClaims)); + + customScopes.forEach((scope, claims) -> properties.put( + "cas.authn.oidc.core.user-defined-scopes." + scope, String.join(",", claims))); } - syncopeClient.getService(WAConfigService.class).list().forEach(attr -> properties.put( - attr.getSchema(), String.join(",", attr.getValues()))); + syncopeClient.getService(WAConfigService.class).list(). + forEach(attr -> properties.put(attr.getSchema(), String.join(",", attr.getValues()))); LOG.debug("Collected WA properties: {}", properties); Map decodedProperties = configurationCipher.decode(properties, ArrayUtils.EMPTY_OBJECT_ARRAY); diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java index 028ebdefee2..d5944b22cf3 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java @@ -21,6 +21,8 @@ import org.apache.syncope.common.lib.policy.AttrReleasePolicyConf; import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO; import org.apache.syncope.common.lib.to.ClientAppTO; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; +import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy; public interface AttrReleaseMapper { @@ -28,4 +30,9 @@ public interface AttrReleaseMapper { boolean supports(AttrReleasePolicyConf conf); RegisteredServiceAttributeReleasePolicy build(ClientAppTO clientApp, AttrReleasePolicyTO policy); + + RegisteredServiceAttributeReleasePolicy build( + OIDCRPClientAppTO clientApp, + AttrReleasePolicyTO policy, + OIDCOpEntityTO oidcOpEntity); } diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java index 46d94ac1c33..267b0bd0609 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java @@ -23,11 +23,13 @@ import java.util.HashSet; import java.util.Map; import java.util.Optional; -import org.apache.syncope.common.lib.OIDCScopeConstants; +import java.util.function.Supplier; +import org.apache.syncope.common.lib.OIDCStandardScope; import org.apache.syncope.common.lib.policy.AttrReleasePolicyConf; import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO; import org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf; import org.apache.syncope.common.lib.to.ClientAppTO; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apereo.cas.authentication.principal.DefaultPrincipalAttributesRepository; import org.apereo.cas.authentication.principal.cache.AbstractPrincipalAttributesRepository; @@ -38,6 +40,7 @@ import org.apereo.cas.oidc.claims.OidcAddressScopeAttributeReleasePolicy; import org.apereo.cas.oidc.claims.OidcCustomScopeAttributeReleasePolicy; import org.apereo.cas.oidc.claims.OidcEmailScopeAttributeReleasePolicy; +import org.apereo.cas.oidc.claims.OidcOpenIdScopeAttributeReleasePolicy; import org.apereo.cas.oidc.claims.OidcPhoneScopeAttributeReleasePolicy; import org.apereo.cas.oidc.claims.OidcProfileScopeAttributeReleasePolicy; import org.apereo.cas.services.AbstractRegisteredServiceAttributeReleasePolicy; @@ -69,63 +72,6 @@ public boolean supports(final AttrReleasePolicyConf conf) { return DefaultAttrReleasePolicyConf.class.equals(conf.getClass()); } - protected Map buildOidc( - final OIDCRPClientAppTO rp, - final DefaultAttrReleasePolicyConf conf) { - - Map policies = new HashMap<>(); - - conf.getReleaseAttrs().forEach((internal, external) -> { - if (OidcProfileScopeAttributeReleasePolicy.ALLOWED_CLAIMS.contains(external.toString())) { - if (rp.getScopes().contains(OIDCScopeConstants.PROFILE)) { - policies.computeIfAbsent( - OIDCScopeConstants.PROFILE, - k -> new OidcProfileScopeAttributeReleasePolicy()). - getClaimMappings().put(external.toString(), internal); - } else { - warnMissingScope(rp.getName(), internal, external, OIDCScopeConstants.PROFILE); - } - } else if (OidcEmailScopeAttributeReleasePolicy.ALLOWED_CLAIMS.contains(external.toString())) { - if (rp.getScopes().contains(OIDCScopeConstants.EMAIL)) { - policies.computeIfAbsent( - OIDCScopeConstants.EMAIL, - k -> new OidcEmailScopeAttributeReleasePolicy()). - getClaimMappings().put(external.toString(), internal); - } else { - warnMissingScope(rp.getName(), internal, external, OIDCScopeConstants.EMAIL); - } - } else if (OidcAddressScopeAttributeReleasePolicy.ALLOWED_CLAIMS.contains(external.toString())) { - if (rp.getScopes().contains(OIDCScopeConstants.ADDRESS)) { - policies.computeIfAbsent( - OIDCScopeConstants.ADDRESS, - k -> new OidcAddressScopeAttributeReleasePolicy()). - getClaimMappings().put(external.toString(), internal); - } else { - warnMissingScope(rp.getName(), internal, external, OIDCScopeConstants.ADDRESS); - } - } else if (OidcPhoneScopeAttributeReleasePolicy.ALLOWED_CLAIMS.contains(external.toString())) { - if (rp.getScopes().contains(OIDCScopeConstants.PHONE)) { - policies.computeIfAbsent( - OIDCScopeConstants.PHONE, - k -> new OidcPhoneScopeAttributeReleasePolicy()). - getClaimMappings().put(external.toString(), internal); - } else { - warnMissingScope(rp.getName(), internal, external, OIDCScopeConstants.PHONE); - } - } else { - BaseOidcScopeAttributeReleasePolicy custom = policies.computeIfAbsent( - OIDCScopeConstants.SYNCOPE, - k -> new OidcCustomScopeAttributeReleasePolicy( - OIDCScopeConstants.SYNCOPE, new ArrayList<>())); - - custom.getAllowedAttributes().add(external.toString()); - custom.getClaimMappings().put(external.toString(), internal); - } - }); - - return policies; - } - protected void setPrincipalAttributesRepository( final DefaultAttrReleasePolicyConf.PrincipalAttrRepoConf parc, final AbstractRegisteredServiceAttributeReleasePolicy policy) { @@ -156,50 +102,11 @@ protected Optional buildConsentPolicy( return Optional.of(consentPolicy); } - @Override - public RegisteredServiceAttributeReleasePolicy build(final ClientAppTO app, final AttrReleasePolicyTO policy) { - DefaultAttrReleasePolicyConf conf = (DefaultAttrReleasePolicyConf) policy.getConf(); - - Map oidc = null; - ReturnMappedAttributeReleasePolicy returnMapped = null; - if (!conf.getReleaseAttrs().isEmpty()) { - if (app instanceof OIDCRPClientAppTO rp) { - oidc = buildOidc(rp, conf); - } else { - returnMapped = new ReturnMappedAttributeReleasePolicy(); - returnMapped.setAllowedAttributes(conf.getReleaseAttrs()); - } - } - - ReturnAllowedAttributeReleasePolicy returnAllowed = null; - if (!conf.getAllowedAttrs().isEmpty()) { - returnAllowed = new ReturnAllowedAttributeReleasePolicy(); - returnAllowed.setAllowedAttributes(conf.getAllowedAttrs()); - } - - ChainingAttributeReleasePolicy chain = new ChainingAttributeReleasePolicy(); - AbstractRegisteredServiceAttributeReleasePolicy single = null; - if (oidc == null) { - if (returnMapped == null && returnAllowed == null) { - single = new DenyAllAttributeReleasePolicy(); - } else if (returnMapped != null && returnAllowed == null) { - single = returnMapped; - } else if (returnMapped == null && returnAllowed != null) { - single = returnAllowed; - } else { - chain.addPolicies(returnMapped, returnAllowed); - } - } else { - if (oidc.size() == 1) { - single = oidc.values().iterator().next(); - } else { - // if present, add the custom scope at the end of the chain - oidc.entrySet().stream(). - filter(entry -> !OIDCScopeConstants.SYNCOPE.equals(entry.getKey())). - forEach(entry -> chain.addPolicies(entry.getValue())); - Optional.ofNullable(oidc.get(OIDCScopeConstants.SYNCOPE)).ifPresent(chain::addPolicies); - } - } + protected RegisteredServiceAttributeReleasePolicy build( + final AttrReleasePolicyTO policy, + final DefaultAttrReleasePolicyConf conf, + final AbstractRegisteredServiceAttributeReleasePolicy single, + final ChainingAttributeReleasePolicy chain) { Optional consentPolicy = buildConsentPolicy(policy, conf); if (!chain.getPolicies().isEmpty()) { @@ -230,4 +137,164 @@ public RegisteredServiceAttributeReleasePolicy build(final ClientAppTO app, fina return single == null ? chain : single; } + + @Override + public RegisteredServiceAttributeReleasePolicy build(final ClientAppTO app, final AttrReleasePolicyTO policy) { + DefaultAttrReleasePolicyConf conf = (DefaultAttrReleasePolicyConf) policy.getConf(); + + ReturnMappedAttributeReleasePolicy returnMapped = null; + ReturnAllowedAttributeReleasePolicy returnAllowed = null; + if (!conf.getReleaseAttrs().isEmpty()) { + returnMapped = new ReturnMappedAttributeReleasePolicy(); + returnMapped.setAllowedAttributes(conf.getReleaseAttrs()); + + if (!conf.getAllowedAttrs().isEmpty()) { + returnAllowed = new ReturnAllowedAttributeReleasePolicy(); + returnAllowed.setAllowedAttributes(conf.getAllowedAttrs()); + } + } + + ChainingAttributeReleasePolicy chain = new ChainingAttributeReleasePolicy(); + AbstractRegisteredServiceAttributeReleasePolicy single = null; + if (returnMapped == null && returnAllowed == null) { + single = new DenyAllAttributeReleasePolicy(); + } else if (returnMapped != null && returnAllowed == null) { + single = returnMapped; + } else if (returnMapped == null && returnAllowed != null) { + single = returnAllowed; + } else { + chain.addPolicies(returnMapped, returnAllowed); + } + + return build(policy, conf, single, chain); + } + + protected void buildForOIDCStandardScope( + final OIDCRPClientAppTO clientApp, + final DefaultAttrReleasePolicyConf conf, + final Map policies, + final Supplier attributeReleasePolicyCreator, + final OIDCStandardScope scope, + final String internal, + final String external) { + + if (clientApp.getScopes().contains(scope.name())) { + BaseOidcScopeAttributeReleasePolicy policy = policies.computeIfAbsent( + scope.name(), k -> attributeReleasePolicyCreator.get()); + + policy.getClaimMappings().put(external, internal); + + if (conf.getAllowedAttrs().contains(external)) { + policy.getAllowedAttributes().add(external); + } + } else { + warnMissingScope(clientApp.getName(), internal, external, scope.name()); + } + } + + protected void buildForOIDCustomScope( + final OIDCRPClientAppTO clientApp, + final Map policies, + final String scope, + final String internal, + final String external) { + + if (clientApp.getScopes().contains(scope)) { + BaseOidcScopeAttributeReleasePolicy policy = policies.computeIfAbsent( + scope, k -> new OidcCustomScopeAttributeReleasePolicy(scope, new ArrayList<>())); + + policy.getClaimMappings().put(external, internal); + + policy.getAllowedAttributes().add(external); + } else { + warnMissingScope(clientApp.getName(), internal, external, scope); + } + } + + @Override + public RegisteredServiceAttributeReleasePolicy build( + final OIDCRPClientAppTO clientApp, + final AttrReleasePolicyTO policy, + final OIDCOpEntityTO oidcOpEntity) { + + if (clientApp.getScopes().isEmpty()) { + return build(clientApp, policy); + } + + DefaultAttrReleasePolicyConf conf = (DefaultAttrReleasePolicyConf) policy.getConf(); + + Map policies = new HashMap<>(); + + if (clientApp.getScopes().contains(OIDCStandardScope.openid.name())) { + policies.put(OIDCStandardScope.openid.name(), new OidcOpenIdScopeAttributeReleasePolicy()); + } + conf.getReleaseAttrs().forEach((internal, external) -> { + if (OidcProfileScopeAttributeReleasePolicy.ALLOWED_CLAIMS.contains(external.toString())) { + buildForOIDCStandardScope( + clientApp, + conf, + policies, + OidcProfileScopeAttributeReleasePolicy::new, + OIDCStandardScope.profile, + internal, + external.toString()); + } else if (OidcEmailScopeAttributeReleasePolicy.ALLOWED_CLAIMS.contains(external.toString())) { + buildForOIDCStandardScope( + clientApp, + conf, + policies, + OidcEmailScopeAttributeReleasePolicy::new, + OIDCStandardScope.email, + internal, + external.toString()); + } else if (OidcAddressScopeAttributeReleasePolicy.ALLOWED_CLAIMS.contains(external.toString())) { + buildForOIDCStandardScope( + clientApp, + conf, + policies, + OidcAddressScopeAttributeReleasePolicy::new, + OIDCStandardScope.address, + internal, + external.toString()); + } else if (OidcPhoneScopeAttributeReleasePolicy.ALLOWED_CLAIMS.contains(external.toString())) { + buildForOIDCStandardScope( + clientApp, + conf, + policies, + OidcPhoneScopeAttributeReleasePolicy::new, + OIDCStandardScope.phone, + internal, + external.toString()); + } else { + oidcOpEntity.getCustomScopes().entrySet().stream(). + filter(entry -> entry.getValue().contains(external.toString())). + map(Map.Entry::getKey).findFirst().ifPresentOrElse( + scope -> buildForOIDCustomScope( + clientApp, policies, scope, internal, external.toString()), + () -> LOG.warn( + "OIDC client app {} defines custom claim {}={} for which no valid scope could be found", + clientApp.getName(), internal, external)); + } + }); + + ChainingAttributeReleasePolicy chain = new ChainingAttributeReleasePolicy(); + AbstractRegisteredServiceAttributeReleasePolicy single = null; + if (policies.size() == 1) { + single = policies.values().iterator().next(); + } else { + // add the custom scopes at the end of the chain + policies.entrySet().stream(). + filter(entry -> !(entry.getValue() instanceof OidcCustomScopeAttributeReleasePolicy)). + forEach(entry -> chain.addPolicies(entry.getValue())); + policies.entrySet().stream(). + filter(entry -> entry.getValue() instanceof OidcCustomScopeAttributeReleasePolicy). + forEach(entry -> chain.addPolicies(entry.getValue())); + } + + if (single == null && chain.getPolicies().isEmpty()) { + single = new DenyAllAttributeReleasePolicy(); + } + + return build(policy, conf, single, chain); + } } 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 af93d084bd0..99ee94103c2 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 @@ -223,7 +223,8 @@ public RegisteredServiceMapper registeredServiceMapper( final List accessMappers, final List attrReleaseMappers, final List ticketExpirationMappers, - final List clientAppMappers) { + final List clientAppMappers, + final WARestClient waRestClient) { return new RegisteredServiceMapper( Optional.ofNullable(casProperties.getAuthn().getPac4j().getCore().getName()). @@ -234,7 +235,8 @@ public RegisteredServiceMapper registeredServiceMapper( accessMappers, attrReleaseMappers, ticketExpirationMappers, - clientAppMappers); + clientAppMappers, + waRestClient); } @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/OIDCRPClientAppTOMapper.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/OIDCRPClientAppTOMapper.java index 567238a210c..b1bc7e656c6 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/OIDCRPClientAppTOMapper.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/OIDCRPClientAppTOMapper.java @@ -18,12 +18,10 @@ */ package org.apache.syncope.wa.starter.mapping; -import java.util.HashSet; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; -import org.apache.syncope.common.lib.OIDCScopeConstants; import org.apache.syncope.common.lib.to.ClientAppTO; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apache.syncope.common.lib.types.OIDCGrantType; @@ -31,8 +29,6 @@ import org.apache.syncope.common.lib.types.OIDCTokenEncryptionAlg; import org.apache.syncope.common.lib.types.OIDCTokenSigningAlg; import org.apache.syncope.common.lib.wa.WAClientApp; -import org.apereo.cas.oidc.claims.OidcCustomScopeAttributeReleasePolicy; -import org.apereo.cas.services.ChainingAttributeReleasePolicy; import org.apereo.cas.services.OidcRegisteredService; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAccessStrategy; @@ -120,14 +116,7 @@ public RegisteredService map( service.setLogoutUrl(rp.getLogoutUri()); service.setTokenEndpointAuthenticationMethod(rp.getTokenEndpointAuthenticationMethod().name()); - service.setScopes(new HashSet<>(rp.getScopes())); - - if (attributeReleasePolicy instanceof OidcCustomScopeAttributeReleasePolicy - || (attributeReleasePolicy instanceof ChainingAttributeReleasePolicy chain - && chain.getPolicies().stream().anyMatch(OidcCustomScopeAttributeReleasePolicy.class::isInstance))) { - - service.getScopes().add(OIDCScopeConstants.SYNCOPE); - } + service.setScopes(rp.getScopes().stream().collect(Collectors.toSet())); setPolicies(service, authPolicy, mfaPolicy, accessStrategy, attributeReleasePolicy, tgtExpirationPolicy, stExpirationPolicy, tgtProxyExpirationPolicy, stProxyExpirationPolicy); @@ -136,33 +125,29 @@ public RegisteredService map( || rp.getAccessTokenTimeToKill() != null || rp.getAccessTokenMaxActiveTokens() != null) { - DefaultRegisteredServiceOAuthAccessTokenExpirationPolicy accessTokenExpirationPolicy = + DefaultRegisteredServiceOAuthAccessTokenExpirationPolicy policy = new DefaultRegisteredServiceOAuthAccessTokenExpirationPolicy(); - Optional.ofNullable(rp.getAccessTokenMaxTimeToLive()) - .ifPresent(accessTokenExpirationPolicy::setMaxTimeToLive); - Optional.ofNullable(rp.getAccessTokenTimeToKill()) - .ifPresent(accessTokenExpirationPolicy::setTimeToKill); - Optional.ofNullable(rp.getAccessTokenMaxActiveTokens()) - .ifPresent(accessTokenExpirationPolicy::setMaxActiveTokens); - service.setAccessTokenExpirationPolicy(accessTokenExpirationPolicy); + Optional.ofNullable(rp.getAccessTokenMaxTimeToLive()).ifPresent(policy::setMaxTimeToLive); + Optional.ofNullable(rp.getAccessTokenTimeToKill()).ifPresent(policy::setTimeToKill); + Optional.ofNullable(rp.getAccessTokenMaxActiveTokens()).ifPresent(policy::setMaxActiveTokens); + service.setAccessTokenExpirationPolicy(policy); } if (rp.getRefreshTokenTimeToKill() != null || rp.getRefreshTokenMaxActiveTokens() != null) { - DefaultRegisteredServiceOAuthRefreshTokenExpirationPolicy refreshTokenExpirationPolicy = + DefaultRegisteredServiceOAuthRefreshTokenExpirationPolicy policy = new DefaultRegisteredServiceOAuthRefreshTokenExpirationPolicy(); - Optional.ofNullable(rp.getRefreshTokenTimeToKill()) - .ifPresent(refreshTokenExpirationPolicy::setTimeToKill); - Optional.ofNullable(rp.getRefreshTokenMaxActiveTokens()) - .ifPresent(refreshTokenExpirationPolicy::setMaxActiveTokens); - service.setRefreshTokenExpirationPolicy(refreshTokenExpirationPolicy); + Optional.ofNullable(rp.getRefreshTokenTimeToKill()).ifPresent(policy::setTimeToKill); + Optional.ofNullable(rp.getRefreshTokenMaxActiveTokens()).ifPresent(policy::setMaxActiveTokens); + service.setRefreshTokenExpirationPolicy(policy); } - if (rp.getDeviceTokenTimeToKill() != null) { - DefaultRegisteredServiceOAuthDeviceTokenExpirationPolicy deviceTokenExpirationPolicy = + Optional.ofNullable(rp.getDeviceTokenTimeToKill()).ifPresent(timeToKill -> { + DefaultRegisteredServiceOAuthDeviceTokenExpirationPolicy policy = new DefaultRegisteredServiceOAuthDeviceTokenExpirationPolicy(); - deviceTokenExpirationPolicy.setTimeToKill(rp.getDeviceTokenTimeToKill()); - service.setDeviceTokenExpirationPolicy(deviceTokenExpirationPolicy); - } + policy.setTimeToKill(timeToKill); + service.setDeviceTokenExpirationPolicy(policy); + }); + return service; } } diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/RegisteredServiceMapper.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/RegisteredServiceMapper.java index 1e7b1a19320..3cbe5d2536a 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/RegisteredServiceMapper.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/RegisteredServiceMapper.java @@ -22,7 +22,11 @@ import java.util.Optional; import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO; import org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; +import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apache.syncope.common.lib.wa.WAClientApp; +import org.apache.syncope.common.rest.api.service.OIDCOpEntityService; +import org.apache.syncope.wa.bootstrap.WARestClient; import org.apache.syncope.wa.bootstrap.mapping.AttrReleaseMapper; import org.apereo.cas.authentication.AuthenticationEventExecutionPlan; import org.apereo.cas.authentication.MultifactorAuthenticationProvider; @@ -61,6 +65,8 @@ public class RegisteredServiceMapper { protected final List clientAppMappers; + protected final WARestClient waRestClient; + public RegisteredServiceMapper( final String pac4jCoreName, final ObjectProvider authEventExecPlan, @@ -69,7 +75,8 @@ public RegisteredServiceMapper( final List accessMappers, final List attrReleaseMappers, final List ticketExpirationMappers, - final List clientAppMappers) { + final List clientAppMappers, + final WARestClient waRestClient) { this.pac4jCoreName = pac4jCoreName; this.authEventExecPlan = authEventExecPlan; @@ -79,20 +86,46 @@ public RegisteredServiceMapper( this.attrReleaseMappers = attrReleaseMappers; this.ticketExpirationMappers = ticketExpirationMappers; this.clientAppMappers = clientAppMappers; + this.waRestClient = waRestClient; + } + + protected OIDCOpEntityTO getOIDCOpEntityTO(final WAClientApp clientApp) { + if (clientApp.getClientAppTO() instanceof OIDCRPClientAppTO) { + OIDCOpEntityTO oidcOpEntity = null; + if (waRestClient.isReady()) { + try { + oidcOpEntity = waRestClient.getService(OIDCOpEntityService.class).get(); + } catch (Exception e) { + LOG.error("While reading OIDC OP", e); + } + } else { + LOG.debug("Syncope client is not yet ready to fetch OIDC OP"); + } + if (oidcOpEntity == null) { + return new OIDCOpEntityTO(); + } + return oidcOpEntity; + } + + return null; } public RegisteredService toRegisteredService(final WAClientApp clientApp) { return clientAppMappers.stream(). filter(m -> m.supports(clientApp.getClientAppTO())). findFirst(). - map(clientAppMapper -> toRegisteredService(clientApp, clientAppMapper)). + map(clientAppMapper -> toRegisteredService(clientApp, clientAppMapper, getOIDCOpEntityTO(clientApp))). orElseGet(() -> { LOG.warn("Unable to locate mapper for {}", clientApp.getClientAppTO().getClass().getName()); return null; }); } - public RegisteredService toRegisteredService(final WAClientApp clientApp, final ClientAppMapper clientAppMapper) { + protected RegisteredService toRegisteredService( + final WAClientApp clientApp, + final ClientAppMapper clientAppMapper, + final OIDCOpEntityTO oidcOpEntity) { + RegisteredServiceAuthenticationPolicy authPolicy = null; RegisteredServiceMultifactorPolicy mfaPolicy = null; RegisteredServiceDelegatedAuthenticationPolicy delegatedAuthPolicy = null; @@ -138,7 +171,9 @@ public RegisteredService toRegisteredService(final WAClientApp clientApp, final filter(m -> m.supports(attrReleasePolicyTO.getConf())). findFirst(); RegisteredServiceAttributeReleasePolicy attributeReleasePolicy = attrReleaseMapper. - map(mapper -> mapper.build(clientApp.getClientAppTO(), attrReleasePolicyTO)). + map(mapper -> clientApp.getClientAppTO() instanceof OIDCRPClientAppTO oidcRPClientAppTO + ? mapper.build(oidcRPClientAppTO, attrReleasePolicyTO, oidcOpEntity) + : mapper.build(clientApp.getClientAppTO(), attrReleasePolicyTO)). orElse(null); RegisteredServiceTicketGrantingTicketExpirationPolicy tgtExpirationPolicy = null; diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/oidc/WAOIDCJWKSGeneratorService.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/oidc/WAOIDCJWKSGeneratorService.java index 23d8bfd9cbf..b9bd2d5cc7b 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/oidc/WAOIDCJWKSGeneratorService.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/oidc/WAOIDCJWKSGeneratorService.java @@ -22,9 +22,9 @@ import java.nio.charset.StandardCharsets; import java.util.Optional; import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.to.OIDCJWKSTO; +import org.apache.syncope.common.lib.to.OIDCOpEntityTO; import org.apache.syncope.common.lib.types.ClientExceptionType; -import org.apache.syncope.common.rest.api.service.OIDCJWKSService; +import org.apache.syncope.common.rest.api.service.OIDCOpEntityService; import org.apache.syncope.wa.bootstrap.WARestClient; import org.apereo.cas.oidc.jwks.generator.OidcJsonWebKeystoreGeneratedEvent; import org.apereo.cas.oidc.jwks.generator.OidcJsonWebKeystoreGeneratorService; @@ -68,10 +68,10 @@ public WAOIDCJWKSGeneratorService( @Override public JsonWebKeySet store(final JsonWebKeySet jsonWebKeySet) { - OIDCJWKSService service = waRestClient.getService(OIDCJWKSService.class); - OIDCJWKSTO to = new OIDCJWKSTO(); - to.setJson(jsonWebKeySet.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE)); - service.set(to); + OIDCOpEntityService service = waRestClient.getService(OIDCOpEntityService.class); + OIDCOpEntityTO oidcOpEntity = new OIDCOpEntityTO(); + oidcOpEntity.setJWKS(jsonWebKeySet.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE)); + service.set(oidcOpEntity); return jsonWebKeySet; } @@ -82,26 +82,26 @@ public Optional find() { @Override public Resource generate() { - OIDCJWKSService service = waRestClient.getService(OIDCJWKSService.class); - OIDCJWKSTO jwksTO = null; + OIDCOpEntityService service = waRestClient.getService(OIDCOpEntityService.class); + OIDCOpEntityTO oidcOpEntity = null; try { - jwksTO = service.get(); + oidcOpEntity = service.get(); } catch (SyncopeClientException e) { if (e.getType() == ClientExceptionType.NotFound) { try (Response response = service.generate(jwksKeyId, jwksType, jwksKeySize)) { - jwksTO = response.readEntity(OIDCJWKSTO.class); + oidcOpEntity = response.readEntity(OIDCOpEntityTO.class); } catch (Exception ge) { LOG.error("While generating new OIDC JWKS", ge); } } else { - LOG.error("While reading OIDC JWKS", e); + LOG.error("While reading OIDC OP", e); } } - if (jwksTO == null) { - throw new IllegalStateException("Unable to determine OIDC JWKS resource"); + if (oidcOpEntity == null) { + throw new IllegalStateException("Unable to determine OIDC OP"); } - Resource result = new ByteArrayResource(jwksTO.getJson().getBytes(StandardCharsets.UTF_8), "OIDC JWKS"); + Resource result = new ByteArrayResource(oidcOpEntity.getJWKS().getBytes(StandardCharsets.UTF_8), "OIDC JWKS"); ClientInfo clientInfo = ClientInfoHolder.getClientInfo(); applicationContext.publishEvent(new OidcJsonWebKeystoreGeneratedEvent(this, result, clientInfo)); return result; diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientCustomizer.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientCustomizer.java index c947fa3d94e..fe37103fc4c 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientCustomizer.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientCustomizer.java @@ -41,8 +41,10 @@ public void customize(final Client client) { if (client instanceof SAML2Client saml2Client) { LOG.debug("Customizing SAML2 client {}", client.getName()); SAML2Configuration configuration = saml2Client.getConfiguration(); - configuration.setKeystoreGenerator(new WASAML2ClientKeystoreGenerator(restClient, saml2Client)); - configuration.setMetadataGenerator(new WASAML2ClientMetadataGenerator(restClient, saml2Client)); + configuration.setKeystoreGenerator( + new WASAML2ClientKeystoreGenerator(restClient, saml2Client.getName(), configuration)); + configuration.setMetadataGenerator( + new WASAML2ClientMetadataGenerator(restClient, saml2Client.getName())); } } } diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientKeystoreGenerator.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientKeystoreGenerator.java index ec328b6a21b..aa10409cc7a 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientKeystoreGenerator.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientKeystoreGenerator.java @@ -30,7 +30,7 @@ import org.apache.commons.io.IOUtils; import org.apache.syncope.common.rest.api.service.wa.WASAML2SPService; import org.apache.syncope.wa.bootstrap.WARestClient; -import org.pac4j.saml.client.SAML2Client; +import org.pac4j.saml.config.SAML2Configuration; import org.pac4j.saml.metadata.keystore.BaseSAML2KeystoreGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,10 +41,14 @@ public class WASAML2ClientKeystoreGenerator extends BaseSAML2KeystoreGenerator { protected final WARestClient waRestClient; - protected final SAML2Client saml2Client; + protected final String saml2Client; - public WASAML2ClientKeystoreGenerator(final WARestClient waRestClient, final SAML2Client saml2Client) { - super(saml2Client.getConfiguration()); + public WASAML2ClientKeystoreGenerator( + final WARestClient waRestClient, + final String saml2Client, + final SAML2Configuration configuration) { + + super(configuration); this.waRestClient = waRestClient; this.saml2Client = saml2Client; } @@ -53,10 +57,10 @@ public WASAML2ClientKeystoreGenerator(final WARestClient waRestClient, final SAM public boolean shouldGenerate() { try { Response response = waRestClient.getService(WASAML2SPService.class). - getSAML2SPKeystore(saml2Client.getName()); + getSAML2SPKeystore(saml2Client); return response.getStatus() == Response.Status.NOT_FOUND.getStatusCode() || !response.hasEntity(); } catch (Exception e) { - LOG.error("While attempting to read if keystore is available for SP Entity {}", saml2Client.getName(), e); + LOG.error("While attempting to read if keystore is available for SP Entity {}", saml2Client, e); return true; } } @@ -75,19 +79,19 @@ protected void store(final KeyStore ks, final X509Certificate certificate, final } waRestClient.getService(WASAML2SPService.class).setSAML2SPKeystore( - saml2Client.getName(), IOUtils.toInputStream(encodedKeystore, StandardCharsets.UTF_8)); + saml2Client, IOUtils.toInputStream(encodedKeystore, StandardCharsets.UTF_8)); } @Override public InputStream retrieve() throws Exception { try { String encodedKeystore = waRestClient.getService(WASAML2SPService.class). - getSAML2SPKeystore(saml2Client.getName()).readEntity(String.class); + getSAML2SPKeystore(saml2Client).readEntity(String.class); LOG.debug("Retrieved keystore {}", encodedKeystore); return new ByteArrayInputStream(Base64.getDecoder().decode(encodedKeystore)); } catch (Exception e) { - String message = "Unable to fetch SAML2 SP keystore for " + saml2Client.getName(); + String message = "Unable to fetch SAML2 SP keystore for " + saml2Client; LOG.error(message, e); throw new Exception(message, e); } diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientMetadataGenerator.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientMetadataGenerator.java index eaadfdd8a2d..15066f43e92 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientMetadataGenerator.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientMetadataGenerator.java @@ -27,7 +27,6 @@ import org.opensaml.saml.metadata.resolver.MetadataResolver; import org.opensaml.saml.metadata.resolver.impl.AbstractMetadataResolver; import org.opensaml.saml.saml2.metadata.EntityDescriptor; -import org.pac4j.saml.client.SAML2Client; import org.pac4j.saml.metadata.BaseSAML2MetadataGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,9 +37,9 @@ public class WASAML2ClientMetadataGenerator extends BaseSAML2MetadataGenerator { protected final WARestClient waRestClient; - protected final SAML2Client saml2Client; + protected final String saml2Client; - public WASAML2ClientMetadataGenerator(final WARestClient waRestClient, final SAML2Client saml2Client) { + public WASAML2ClientMetadataGenerator(final WARestClient waRestClient, final String saml2Client) { this.waRestClient = waRestClient; this.saml2Client = saml2Client; } @@ -53,12 +52,12 @@ public boolean storeMetadata(final String metadata, final boolean force) { protected Optional metadataAvailable() { try { String encodedMetadata = waRestClient.getService(WASAML2SPService.class). - getSAML2SPMetadata(saml2Client.getName()).readEntity(String.class); + getSAML2SPMetadata(saml2Client).readEntity(String.class); LOG.debug("Retrieved metadata {}", encodedMetadata); return Optional.of(new String(Base64.getDecoder().decode(encodedMetadata), StandardCharsets.UTF_8)); } catch (Exception e) { - LOG.error("While attempting to read metadata for SP Entity {}", saml2Client.getName(), e); + LOG.error("While attempting to read metadata for SP Entity {}", saml2Client, e); return Optional.empty(); } } @@ -111,9 +110,9 @@ public MetadataResolver buildMetadataResolver() throws Exception { try { waRestClient.getService(WASAML2SPService.class).setSAML2SPMetadata( - saml2Client.getName(), IOUtils.toInputStream(encodedMetadata, StandardCharsets.UTF_8)); + saml2Client, IOUtils.toInputStream(encodedMetadata, StandardCharsets.UTF_8)); } catch (Exception e) { - LOG.error("While storing SP {} metadata", saml2Client.getName(), e); + LOG.error("While storing SP {} metadata", saml2Client, e); } } diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2MetadataResolver.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2MetadataResolver.java index da9b3ea7a08..240d732fd3a 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2MetadataResolver.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2MetadataResolver.java @@ -23,7 +23,6 @@ import org.apache.syncope.common.rest.api.service.wa.WASAML2SPService; import org.apache.syncope.wa.bootstrap.WARestClient; import org.opensaml.saml.metadata.resolver.impl.AbstractReloadingMetadataResolver; -import org.pac4j.saml.client.SAML2Client; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,28 +32,28 @@ public class WASAML2MetadataResolver extends AbstractReloadingMetadataResolver { protected final WARestClient waRestClient; - protected final SAML2Client saml2Client; + protected final String saml2Client; - public WASAML2MetadataResolver(final WARestClient waRestClient, final SAML2Client saml2Client) { + public WASAML2MetadataResolver(final WARestClient waRestClient, final String saml2Client) { this.waRestClient = waRestClient; this.saml2Client = saml2Client; } @Override protected String getMetadataIdentifier() { - return saml2Client.getName(); + return saml2Client; } @Override protected byte[] fetchMetadata() throws ResolverException { try { String encodedMetadata = waRestClient.getService(WASAML2SPService.class). - getSAML2SPMetadata(saml2Client.getName()).readEntity(String.class); + getSAML2SPMetadata(saml2Client).readEntity(String.class); LOG.debug("Retrieved metadata {}", encodedMetadata); return Base64.getDecoder().decode(encodedMetadata); } catch (Exception e) { - String message = "Unable to fetch SP metadata for SP entity " + saml2Client.getName(); + String message = "Unable to fetch SP metadata for SP entity " + saml2Client; LOG.error(message, e); throw new ResolverException(message); } diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/services/WAServiceRegistry.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/services/WAServiceRegistry.java index ec4b79c3c48..258be9594a5 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/services/WAServiceRegistry.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/services/WAServiceRegistry.java @@ -43,13 +43,13 @@ public class WAServiceRegistry extends AbstractServiceRegistry { protected final RegisteredServiceMapper registeredServiceMapper; public WAServiceRegistry( - final WARestClient restClient, + final WARestClient waRestClient, final RegisteredServiceMapper registeredServiceMapper, final ConfigurableApplicationContext applicationContext, final Collection serviceRegistryListeners) { super(applicationContext, serviceRegistryListeners); - this.waRestClient = restClient; + this.waRestClient = waRestClient; this.registeredServiceMapper = registeredServiceMapper; } diff --git a/wa/starter/src/main/resources/wa.properties b/wa/starter/src/main/resources/wa.properties index a66c191167b..8ab439b6cd1 100644 --- a/wa/starter/src/main/resources/wa.properties +++ b/wa/starter/src/main/resources/wa.properties @@ -84,7 +84,6 @@ cas.authn.saml-idp.metadata.http.metadata-backup-location=file:${syncope.conf.di cas.authn.oidc.core.issuer=${cas.server.prefix}/oidc cas.authn.oidc.discovery.id-token-signing-alg-values-supported=RS256,RS384,RS512,PS256,PS384,PS512,ES256,ES384,ES512,HS256,HS384,HS512 cas.authn.oidc.discovery.user-info-signing-alg-values-supported=RS256,RS384,RS512,PS256,PS384,PS512,ES256,ES384,ES512,HS256,HS384,HS512 -cas.authn.oidc.discovery.scopes=openid,profile,email,address,phone,syncope cas.authn.oidc.logout.backchannel-logout-supported=true cas.authn.oidc.logout.frontchannel-logout-supported=true cas.authn.oauth.core.user-profile-view-type=FLAT diff --git a/wa/starter/src/test/java/org/apache/syncope/wa/starter/WAServiceRegistryTest.java b/wa/starter/src/test/java/org/apache/syncope/wa/starter/WAServiceRegistryTest.java index d2455f28cf3..69888267f6c 100644 --- a/wa/starter/src/test/java/org/apache/syncope/wa/starter/WAServiceRegistryTest.java +++ b/wa/starter/src/test/java/org/apache/syncope/wa/starter/WAServiceRegistryTest.java @@ -47,7 +47,6 @@ import org.apache.syncope.common.rest.api.service.AuthModuleService; import org.apache.syncope.common.rest.api.service.wa.WAClientAppService; import org.apache.syncope.wa.bootstrap.WARestClient; -import org.apereo.cas.authentication.AuthenticationEventExecutionPlan; import org.apereo.cas.services.AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria; import org.apereo.cas.services.ChainingAttributeReleasePolicy; import org.apereo.cas.services.DenyAllAttributeReleasePolicy; @@ -60,7 +59,6 @@ import org.apereo.cas.support.saml.services.SamlRegisteredService; import org.apereo.cas.util.RandomUtils; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.context.refresh.ContextRefresher; @@ -142,9 +140,6 @@ private static void addPolicies(final WAClientApp waClientApp, final boolean wit @Autowired private ServicesManager servicesManager; - @Autowired - private ObjectProvider authenticationEventExecutionPlan; - @Autowired private ContextRefresher contextRefresher; diff --git a/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/BaseWASAML2ClientTest.java b/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/BaseWASAML2ClientTest.java index 8ed84ab1308..255ece95b6b 100644 --- a/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/BaseWASAML2ClientTest.java +++ b/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/BaseWASAML2ClientTest.java @@ -56,7 +56,6 @@ import org.bouncycastle.asn1.x509.TBSCertificate; import org.bouncycastle.asn1.x509.Time; import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator; -import org.pac4j.saml.client.SAML2Client; import org.pac4j.saml.config.SAML2Configuration; import org.pac4j.saml.metadata.SAML2IdentityProviderMetadataResolver; import org.springframework.core.io.ClassPathResource; @@ -98,7 +97,7 @@ protected static Certificate createSelfSignedCert(final KeyPair keyPair) throws return cert; } - protected static SAML2Client getSAML2Client() throws Exception { + protected static SAML2Configuration getSAML2Configuration() throws Exception { SAML2Configuration cfg = new SAML2Configuration(); cfg.setKeystorePassword("password"); cfg.setPrivateKeyPassword("password"); @@ -111,10 +110,7 @@ protected static SAML2Client getSAML2Client() throws Exception { cfg.setServiceProviderMetadataResource(new FileSystemResource(File.createTempFile("sp-metadata", ".xml"))); - SAML2Client client = new SAML2Client(cfg); - client.setName("CAS"); - client.setCallbackUrl("https://syncope.apache.org"); - return client; + return cfg; } protected static KeyStore getKeystore() throws Exception { diff --git a/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientCustomizerTest.java b/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientCustomizerTest.java index 406452871e1..b7695f8215a 100644 --- a/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientCustomizerTest.java +++ b/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientCustomizerTest.java @@ -28,9 +28,13 @@ public class WASAML2ClientCustomizerTest extends BaseWASAML2ClientTest { @Test public void customize() throws Exception { WASAML2ClientCustomizer customizer = new WASAML2ClientCustomizer(getWARestClient()); - SAML2Client client = getSAML2Client(); + SAML2Client client = new SAML2Client(getSAML2Configuration()); + client.setName("CAS"); + client.setCallbackUrl("https://syncope.apache.org"); + customizer.customize(client); client.init(); + assertTrue(client.getConfiguration().getKeystoreGenerator() instanceof WASAML2ClientKeystoreGenerator); assertTrue(client.getConfiguration().toMetadataGenerator() instanceof WASAML2ClientMetadataGenerator); } diff --git a/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientKeystoreGeneratorTest.java b/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientKeystoreGeneratorTest.java index 99e5686e0b7..55d01d27762 100644 --- a/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientKeystoreGeneratorTest.java +++ b/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientKeystoreGeneratorTest.java @@ -27,7 +27,8 @@ public class WASAML2ClientKeystoreGeneratorTest extends BaseWASAML2ClientTest { @Test public void generate() throws Exception { - SAML2KeystoreGenerator generator = new WASAML2ClientKeystoreGenerator(getWARestClient(), getSAML2Client()); + SAML2KeystoreGenerator generator = new WASAML2ClientKeystoreGenerator( + getWARestClient(), "CAS", getSAML2Configuration()); assertDoesNotThrow(generator::generate); } } diff --git a/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientMetadataGeneratorTest.java b/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientMetadataGeneratorTest.java index d5449e96a89..edb0f3295cc 100644 --- a/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientMetadataGeneratorTest.java +++ b/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2ClientMetadataGeneratorTest.java @@ -20,21 +20,15 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; -import java.io.File; import org.junit.jupiter.api.Test; import org.opensaml.saml.saml2.metadata.EntityDescriptor; -import org.pac4j.saml.client.SAML2Client; import org.pac4j.saml.metadata.SAML2MetadataGenerator; public class WASAML2ClientMetadataGeneratorTest extends BaseWASAML2ClientTest { @Test public void storeMetadata() throws Exception { - SAML2Client client = getSAML2Client(); - String keystoreFile = File.createTempFile("keystore", "jks").getCanonicalPath(); - client.getConfiguration().setKeystoreResourceFilepath(keystoreFile); - - SAML2MetadataGenerator generator = new WASAML2ClientMetadataGenerator(getWARestClient(), client); + SAML2MetadataGenerator generator = new WASAML2ClientMetadataGenerator(getWARestClient(), "CAS"); EntityDescriptor entityDescriptor = generator.buildEntityDescriptor(); String metadata = generator.getMetadata(entityDescriptor); assertNotNull(generator.storeMetadata(metadata, false)); diff --git a/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2MetadataResolverTest.java b/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2MetadataResolverTest.java index 60d4e652bcc..047b83dbaf9 100644 --- a/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2MetadataResolverTest.java +++ b/wa/starter/src/test/java/org/apache/syncope/wa/starter/pac4j/saml/WASAML2MetadataResolverTest.java @@ -20,19 +20,13 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; -import java.io.File; import org.junit.jupiter.api.Test; -import org.pac4j.saml.client.SAML2Client; public class WASAML2MetadataResolverTest extends BaseWASAML2ClientTest { @Test public void fetchMetadata() throws Exception { - SAML2Client client = getSAML2Client(); - String keystoreFile = File.createTempFile("keystore", "jks").getCanonicalPath(); - client.getConfiguration().setKeystoreResourceFilepath(keystoreFile); - - WASAML2MetadataResolver resolver = new WASAML2MetadataResolver(getWARestClient(), client); + WASAML2MetadataResolver resolver = new WASAML2MetadataResolver(getWARestClient(), "CAS"); assertNotNull(resolver.fetchMetadata()); } }