diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/AttrWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/AttrWizardBuilder.java index 8eb63d4ef7..16009194b5 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/AttrWizardBuilder.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/AttrWizardBuilder.java @@ -44,7 +44,7 @@ protected WizardModel buildModelSteps(final Attr modelObject, final WizardModel protected static class AttrStep extends WizardStep { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 8145346883748040158L; AttrStep(final Attr modelObject) { AjaxTextFieldPanel schema = new AjaxTextFieldPanel( diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCScope.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCScope.java index a3771db4f9..57e6b58d73 100644 --- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCScope.java +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCScope.java @@ -19,10 +19,10 @@ package org.apache.syncope.common.lib.types; public enum OIDCScope { - OPENID, - PROFILE, - EMAIL, - ADDRESS, - PHONE + openid, + profile, + email, + address, + phone } diff --git a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml index d80034f400..579e6e6f0a 100644 --- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml +++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml @@ -58,14 +58,14 @@ under the License. + jsonConf='{"_class":"org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf","releaseAttrs":{},"allowedAttrs":[],"excludedAttrs":[],"includeOnlyAttrs":[],"principalIdAttr":null,"principalAttrRepoConf":{"mergingStrategy":"MULTIVALUED","ignoreResolvedAttributes":false,"expiration":0,"timeUnit":"HOURS","attrRepos":[]}}'/> + jsonConf='{"_class":"org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf","releaseAttrs":{},"allowedAttrs":["cn","givenName","uid"],"excludedAttrs":[],"includeOnlyAttrs":[],"principalIdAttr":null,"principalAttrRepoConf":{"mergingStrategy":"MULTIVALUED","ignoreResolvedAttributes":false,"expiration":0,"timeUnit":"HOURS","attrRepos":[]}}'/> + items='[{"intAttrName":"mail","extAttrName":"email","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]},{"intAttrName":"givenName","extAttrName":"given_name","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]},{"intAttrName":"sn","extAttrName":"family_name","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]},{"intAttrName":"cn","extAttrName":"name","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]}]'/> + jsonConf='{"_class":"org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf","releaseAttrs":{},"allowedAttrs":[],"excludedAttrs":[],"includeOnlyAttrs":[],"principalIdAttr":null,"principalAttrRepoConf":{"mergingStrategy":"MULTIVALUED","ignoreResolvedAttributes":false,"expiration":0,"timeUnit":"HOURS","attrRepos":[]}}'/> + jsonConf='{"_class":"org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf","releaseAttrs":{},"allowedAttrs":["cn","givenName","uid"],"excludedAttrs":[],"includeOnlyAttrs":[],"principalIdAttr":null,"principalAttrRepoConf":{"mergingStrategy":"MULTIVALUED","ignoreResolvedAttributes":false,"expiration":0,"timeUnit":"HOURS","attrRepos":[]}}'/> + items='[{"intAttrName":"mail","extAttrName":"email","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]},{"intAttrName":"givenName","extAttrName":"given_name","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]},{"intAttrName":"sn","extAttrName":"family_name","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]},{"intAttrName":"cn","extAttrName":"name","connObjectKey":false,"password":false,"mandatoryCondition":"false","purpose":"NONE","propagationJEXLTransformer":null,"pullJEXLTransformer":null,"transformers":[]}]'/> newInstance(final String id, this.addNewItemPanelBuilder(new OIDCProviderWizardBuilder( this, new OIDCC4UIProviderTO(), implementationRestClient, restClient, pageRef), true); - MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, OIDC4UIEntitlement.OP_CREATE); + MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, OIDCC4UIEntitlement.OP_CREATE); modal.size(Modal.Size.Large); @@ -184,7 +184,7 @@ public void onClick(final AjaxRequestTarget target, final OIDCC4UIProviderTO ign new AjaxWizard.EditItemActionEvent<>(object, target)); modal.header(Model.of(StringUtils.capitalize(("Edit " + object.getName())))); } - }, ActionLink.ActionType.EDIT, OIDC4UIEntitlement.OP_UPDATE); + }, ActionLink.ActionType.EDIT, OIDCC4UIEntitlement.OP_UPDATE); panel.add(new ActionLink<>() { @@ -218,7 +218,7 @@ protected Serializable onApplyInternal(final AnyWrapper modelObject) { target.add(templateModal); } - }, ActionLink.ActionType.TEMPLATE, OIDC4UIEntitlement.OP_UPDATE); + }, ActionLink.ActionType.TEMPLATE, OIDCC4UIEntitlement.OP_UPDATE); panel.add(new ActionLink<>() { @@ -236,7 +236,7 @@ public void onClick(final AjaxRequestTarget target, final OIDCC4UIProviderTO ign } ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target); } - }, ActionLink.ActionType.DELETE, OIDC4UIEntitlement.OP_DELETE, true); + }, ActionLink.ActionType.DELETE, OIDCC4UIEntitlement.OP_DELETE, true); return panel; } 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 6757241a1c..57d38a0fb1 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; @@ -30,6 +31,7 @@ import org.apache.syncope.client.console.panels.OIDCProvidersDirectoryPanel; import org.apache.syncope.client.console.rest.ImplementationRestClient; import org.apache.syncope.client.console.rest.OIDCProviderRestClient; +import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel; import org.apache.syncope.client.console.wizards.mapping.ItemTransformersTogglePanel; import org.apache.syncope.client.console.wizards.mapping.JEXLTransformersTogglePanel; import org.apache.syncope.client.console.wizards.mapping.OIDCProviderMappingPanel; @@ -42,6 +44,7 @@ import org.apache.syncope.common.lib.to.ImplementationTO; import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO; import org.apache.syncope.common.lib.types.OIDCClientImplementationType; +import org.apache.syncope.common.lib.types.OIDCScope; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.wizard.WizardModel; @@ -107,11 +110,7 @@ protected Serializable onApplyInternal(final OIDCC4UIProviderTO modelObject) { @Override protected WizardModel buildModelSteps(final OIDCC4UIProviderTO modelObject, final WizardModel wizardModel) { wizardModel.add(new OP(modelObject)); - if (modelObject.getKey() == null) { - wizardModel.add(new OPContinue(modelObject)); - } else { - wizardModel.add(new OPContinue(modelObject, true)); - } + wizardModel.add(new OPContinue(modelObject, modelObject.getKey() != null)); Mapping mapping = new Mapping(); mapping.setOutputMarkupId(true); @@ -145,6 +144,7 @@ protected void sendWarning(final String message) { @Override protected Future> execute( final Callable> future) { + return SyncopeConsoleSession.get().execute(future); } @@ -200,61 +200,63 @@ public static class OPContinue extends WizardStep { private static final long serialVersionUID = -7087008312629522790L; - public OPContinue(final OIDCC4UIProviderTO opTO) { - final WebMarkupContainer content = new WebMarkupContainer("content"); + public OPContinue(final OIDCC4UIProviderTO opTO, final boolean readOnly) { this.setOutputMarkupId(true); + + WebMarkupContainer content = new WebMarkupContainer("content"); content.setOutputMarkupId(true); add(content); UrlValidator urlValidator = new UrlValidator(); - final AjaxTextFieldPanel issuer = new AjaxTextFieldPanel( + + AjaxTextFieldPanel issuer = new AjaxTextFieldPanel( "issuer", "issuer", new PropertyModel<>(opTO, "issuer")); issuer.addValidator(urlValidator); issuer.addRequiredLabel(); - content.add(issuer); + content.add(issuer.setReadOnly(readOnly)); - final AjaxCheckBoxPanel hasDiscovery = new AjaxCheckBoxPanel( + AjaxCheckBoxPanel hasDiscovery = new AjaxCheckBoxPanel( "hasDiscovery", "hasDiscovery", new PropertyModel<>(opTO, "hasDiscovery")); content.add(hasDiscovery); - final AjaxTextFieldPanel authorizationEndpoint = new AjaxTextFieldPanel("authorizationEndpoint", + AjaxTextFieldPanel authorizationEndpoint = new AjaxTextFieldPanel("authorizationEndpoint", "authorizationEndpoint", new PropertyModel<>(opTO, "authorizationEndpoint")); authorizationEndpoint.addRequiredLabel(); authorizationEndpoint.addValidator(urlValidator); - content.add(authorizationEndpoint); + content.add(authorizationEndpoint.setReadOnly(readOnly)); - final AjaxTextFieldPanel userinfoEndpoint = new AjaxTextFieldPanel("userinfoEndpoint", + AjaxTextFieldPanel userinfoEndpoint = new AjaxTextFieldPanel("userinfoEndpoint", "userinfoEndpoint", new PropertyModel<>(opTO, "userinfoEndpoint")); userinfoEndpoint.addValidator(urlValidator); - content.add(userinfoEndpoint); + content.add(userinfoEndpoint.setReadOnly(readOnly)); - final AjaxTextFieldPanel tokenEndpoint = new AjaxTextFieldPanel("tokenEndpoint", + AjaxTextFieldPanel tokenEndpoint = new AjaxTextFieldPanel("tokenEndpoint", "tokenEndpoint", new PropertyModel<>(opTO, "tokenEndpoint")); tokenEndpoint.addRequiredLabel(); tokenEndpoint.addValidator(urlValidator); - content.add(tokenEndpoint); + content.add(tokenEndpoint.setReadOnly(readOnly)); - final AjaxTextFieldPanel jwksUri = new AjaxTextFieldPanel("jwksUri", + AjaxTextFieldPanel jwksUri = new AjaxTextFieldPanel("jwksUri", "jwksUri", new PropertyModel<>(opTO, "jwksUri")); jwksUri.addRequiredLabel(); jwksUri.addValidator(urlValidator); - content.add(jwksUri); + content.add(jwksUri.setReadOnly(readOnly)); - final AjaxTextFieldPanel endSessionEndpoint = new AjaxTextFieldPanel("endSessionEndpoint", + AjaxTextFieldPanel endSessionEndpoint = new AjaxTextFieldPanel("endSessionEndpoint", "endSessionEndpoint", new PropertyModel<>(opTO, "endSessionEndpoint")); endSessionEndpoint.addValidator(urlValidator); - content.add(endSessionEndpoint); + content.add(endSessionEndpoint.setReadOnly(readOnly)); - final WebMarkupContainer visibleParam = new WebMarkupContainer("visibleParams"); - visibleParam.setOutputMarkupPlaceholderTag(true); - visibleParam.add(authorizationEndpoint); - visibleParam.add(userinfoEndpoint); - visibleParam.add(tokenEndpoint); - visibleParam.add(jwksUri); - visibleParam.add(endSessionEndpoint); - content.add(visibleParam); + WebMarkupContainer visibleParams = new WebMarkupContainer("visibleParams"); + visibleParams.setOutputMarkupPlaceholderTag(true); + visibleParams.add(authorizationEndpoint); + visibleParams.add(userinfoEndpoint); + visibleParams.add(tokenEndpoint); + visibleParams.add(jwksUri); + visibleParams.add(endSessionEndpoint); + content.add(visibleParams); - showHide(hasDiscovery, visibleParam); + showHide(hasDiscovery, visibleParams); hasDiscovery.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) { @@ -262,70 +264,20 @@ public OPContinue(final OIDCC4UIProviderTO opTO) { @Override protected void onUpdate(final AjaxRequestTarget target) { - showHide(hasDiscovery, visibleParam); - target.add(visibleParam); + showHide(hasDiscovery, visibleParams); + target.add(visibleParams); } }); - } - - public OPContinue(final OIDCC4UIProviderTO opTO, final boolean readOnly) { - WebMarkupContainer content = new WebMarkupContainer("content"); - this.setOutputMarkupId(true); - content.setOutputMarkupId(true); - add(content); - final AjaxTextFieldPanel issuer = new AjaxTextFieldPanel( - "issuer", "issuer", new PropertyModel<>(opTO, "issuer")); - issuer.setReadOnly(readOnly); - content.add(issuer); - - final AjaxCheckBoxPanel hasDiscovery = new AjaxCheckBoxPanel( - "hasDiscovery", "hasDiscovery", new PropertyModel<>(opTO, "hasDiscovery")); - hasDiscovery.setReadOnly(readOnly); - content.add(hasDiscovery); - - final AjaxTextFieldPanel authorizationEndpoint = new AjaxTextFieldPanel("authorizationEndpoint", - "authorizationEndpoint", new PropertyModel<>(opTO, "authorizationEndpoint")); - authorizationEndpoint.setReadOnly(readOnly); - content.add(authorizationEndpoint); - - final AjaxTextFieldPanel userinfoEndpoint = new AjaxTextFieldPanel("userinfoEndpoint", - "userinfoEndpoint", new PropertyModel<>(opTO, "userinfoEndpoint")); - userinfoEndpoint.setReadOnly(readOnly); - content.add(userinfoEndpoint); - - final AjaxTextFieldPanel tokenEndpoint = new AjaxTextFieldPanel("tokenEndpoint", - "tokenEndpoint", new PropertyModel<>(opTO, "tokenEndpoint")); - tokenEndpoint.setReadOnly(readOnly); - content.add(tokenEndpoint); - - final AjaxTextFieldPanel jwksUri = new AjaxTextFieldPanel("jwksUri", - "jwksUri", new PropertyModel<>(opTO, "jwksUri")); - jwksUri.setReadOnly(readOnly); - content.add(jwksUri); - - final AjaxTextFieldPanel endSessionEndpoint = new AjaxTextFieldPanel("endSessionEndpoint", - "endSessionEndpoint", new PropertyModel<>(opTO, "endSessionEndpoint")); - endSessionEndpoint.setReadOnly(readOnly); - content.add(endSessionEndpoint); - - final WebMarkupContainer visibleParam = new WebMarkupContainer("visibleParams"); - visibleParam.setOutputMarkupPlaceholderTag(true); - visibleParam.add(authorizationEndpoint); - visibleParam.add(userinfoEndpoint); - visibleParam.add(tokenEndpoint); - visibleParam.add(jwksUri); - visibleParam.add(endSessionEndpoint); - content.add(visibleParam); + AjaxTextFieldPanel value = new AjaxTextFieldPanel("panel", "scopes", new Model<>()); + value.setChoices(Stream.of(OIDCScope.values()).map(OIDCScope::name).collect(Collectors.toList())); + content.add(new MultiFieldPanel.Builder( + new PropertyModel<>(opTO, "scopes")).build("scopes", "scopes", value)); } } private static void showHide(final AjaxCheckBoxPanel hasDiscovery, final WebMarkupContainer visibleParams) { - if (hasDiscovery.getField().getValue().equals("false")) { - visibleParams.setVisible(true); - } else { - visibleParams.setVisible(false); - } + visibleParams.setVisible("false".equals(hasDiscovery.getField().getValue())); } /** diff --git a/ext/oidcc4ui/client-console/src/main/resources/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder$OPContinue.html b/ext/oidcc4ui/client-console/src/main/resources/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder$OPContinue.html index 0f8de216f4..e36e9d2eee 100644 --- a/ext/oidcc4ui/client-console/src/main/resources/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder$OPContinue.html +++ b/ext/oidcc4ui/client-console/src/main/resources/org/apache/syncope/client/console/wizards/OIDCProviderWizardBuilder$OPContinue.html @@ -29,6 +29,8 @@ [userinfoEndpoint] [endSessionEndpoint] + + diff --git a/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/to/OIDCC4UIProviderTO.java b/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/to/OIDCC4UIProviderTO.java index 158f4a91b7..c082860a7d 100644 --- a/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/to/OIDCC4UIProviderTO.java +++ b/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/to/OIDCC4UIProviderTO.java @@ -48,6 +48,8 @@ public class OIDCC4UIProviderTO extends ItemContainer implements EntityTO { private String endSessionEndpoint; + private final List scopes = new ArrayList<>(); + private boolean hasDiscovery; private boolean createUnmatching; @@ -143,6 +145,12 @@ public void setEndSessionEndpoint(final String endSessionEndpoint) { this.endSessionEndpoint = endSessionEndpoint; } + @JacksonXmlElementWrapper(localName = "scopes") + @JacksonXmlProperty(localName = "scope") + public List getScopes() { + return scopes; + } + public UserTO getUserTemplate() { return userTemplate; } diff --git a/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/OIDC4UIEntitlement.java b/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/OIDCC4UIEntitlement.java similarity index 91% rename from ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/OIDC4UIEntitlement.java rename to ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/OIDCC4UIEntitlement.java index 3f6a0a98df..8b559ffd07 100644 --- a/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/OIDC4UIEntitlement.java +++ b/ext/oidcc4ui/common-lib/src/main/java/org/apache/syncope/common/lib/types/OIDCC4UIEntitlement.java @@ -24,7 +24,7 @@ import java.util.Set; import java.util.TreeSet; -public final class OIDC4UIEntitlement { +public final class OIDCC4UIEntitlement { public static final String OP_READ = "OP_READ"; @@ -38,7 +38,7 @@ public final class OIDC4UIEntitlement { static { Set values = new TreeSet<>(); - for (Field field : OIDC4UIEntitlement.class.getDeclaredFields()) { + for (Field field : OIDCC4UIEntitlement.class.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers()) && String.class.equals(field.getType())) { values.add(field.getName()); } @@ -50,7 +50,7 @@ public static Set values() { return VALUES; } - private OIDC4UIEntitlement() { + private OIDCC4UIEntitlement() { // private constructor for static utility class } } 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 b47bf5f3b5..222d92564e 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 @@ -41,7 +41,7 @@ import org.apache.syncope.common.lib.types.ClientExceptionType; import org.apache.syncope.common.lib.types.IdRepoEntitlement; import org.apache.syncope.core.logic.oidc.NoOpSessionStore; -import org.apache.syncope.core.logic.oidc.OIDC4UIContext; +import org.apache.syncope.core.logic.oidc.OIDCC4UIContext; import org.apache.syncope.core.logic.oidc.OIDCClientCache; import org.apache.syncope.core.logic.oidc.OIDCUserManager; import org.apache.syncope.core.persistence.api.dao.NotFoundException; @@ -112,7 +112,7 @@ public OIDCRequest createLoginRequest(final String redirectURI, final String opN OidcClient oidcClient = getOidcClient(oidcClientCacheLogin, op, redirectURI); // 2. create OIDCRequest - WithLocationAction action = oidcClient.getRedirectionAction(new OIDC4UIContext(), NoOpSessionStore.INSTANCE). + WithLocationAction action = oidcClient.getRedirectionAction(new OIDCC4UIContext(), NoOpSessionStore.INSTANCE). map(WithLocationAction.class::cast). orElseThrow(() -> { SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown); @@ -141,9 +141,7 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat OidcCredentials credentials = new OidcCredentials(); credentials.setCode(new AuthorizationCode(authorizationCode)); - OIDC4UIContext ctx = new OIDC4UIContext(); - - oidcClient.getAuthenticator().validate(credentials, ctx, NoOpSessionStore.INSTANCE); + oidcClient.getAuthenticator().validate(credentials, new OIDCC4UIContext(), NoOpSessionStore.INSTANCE); idToken = credentials.getIdToken().getJWTClaimsSet(); idTokenHint = credentials.getIdToken().serialize(); @@ -155,8 +153,8 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat } // 3. prepare the result - OIDCLoginResponse loginResponse = new OIDCLoginResponse(); - loginResponse.setLogoutSupported(StringUtils.isNotBlank(op.getEndSessionEndpoint())); + 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(); @@ -164,21 +162,21 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat Attr attrTO = new Attr(); attrTO.setSchema(item.getExtAttrName()); - String value = idToken.getClaim(item.getExtAttrName()) == null - ? null - : idToken.getClaim(item.getExtAttrName()).toString(); + String value = Optional.ofNullable(idToken.getClaim(item.getExtAttrName())). + map(Object::toString). + orElse(null); if (value != null) { attrTO.getValues().add(value); - loginResponse.getAttrs().add(attrTO); + loginResp.getAttrs().add(attrTO); if (item.isConnObjectKey()) { keyValue = value; } } } - List matchingUsers = keyValue == null - ? List.of() - : userManager.findMatchingUser(keyValue, op.getConnObjectKeyItem().get()); + List matchingUsers = Optional.ofNullable(keyValue). + map(k -> userManager.findMatchingUser(k, op.getConnObjectKeyItem().get())). + orElse(List.of()); LOG.debug("Found {} matching users for {}", matchingUsers.size(), keyValue); // 3b. not found: create or selfreg if configured @@ -189,23 +187,23 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat String defaultUsername = keyValue; username = AuthContextUtils.callAsAdmin(AuthContextUtils.getDomain(), - () -> userManager.create(op, loginResponse, defaultUsername)); + () -> userManager.create(op, loginResp, defaultUsername)); } else if (op.isSelfRegUnmatching()) { UserTO userTO = new UserTO(); - userManager.fill(op, loginResponse, userTO); + userManager.fill(op, loginResp, userTO); - loginResponse.getAttrs().clear(); - loginResponse.getAttrs().addAll(userTO.getPlainAttrs()); + loginResp.getAttrs().clear(); + loginResp.getAttrs().addAll(userTO.getPlainAttrs()); if (StringUtils.isNotBlank(userTO.getUsername())) { - loginResponse.setUsername(userTO.getUsername()); + loginResp.setUsername(userTO.getUsername()); } else { - loginResponse.setUsername(keyValue); + loginResp.setUsername(keyValue); } - loginResponse.setSelfReg(true); + loginResp.setSelfReg(true); - return loginResponse; + return loginResp; } else { throw new NotFoundException(Optional.ofNullable(keyValue). map(value -> "User matching the provided value " + value). @@ -218,13 +216,13 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat LOG.debug("About to update {} for {}", matchingUsers.get(0), keyValue); username = AuthContextUtils.callAsAdmin(AuthContextUtils.getDomain(), - () -> userManager.update(matchingUsers.get(0), op, loginResponse)); + () -> userManager.update(matchingUsers.get(0), op, loginResp)); } else { username = matchingUsers.get(0); } } - loginResponse.setUsername(username); + loginResp.setUsername(username); // 4. generate JWT for further access Map claims = new HashMap<>(); @@ -234,18 +232,18 @@ public OIDCLoginResponse login(final String redirectURI, final String authorizat byte[] authorities = null; try { authorities = ENCRYPTOR.encode(POJOHelper.serialize( - authDataAccessor.getAuthorities(loginResponse.getUsername(), null)), CipherAlgorithm.AES). + authDataAccessor.getAuthorities(loginResp.getUsername(), null)), CipherAlgorithm.AES). getBytes(); } catch (Exception e) { LOG.error("Could not fetch authorities", e); } Pair accessTokenInfo = - accessTokenDataBinder.create(loginResponse.getUsername(), claims, authorities, true); - loginResponse.setAccessToken(accessTokenInfo.getLeft()); - loginResponse.setAccessTokenExpiryTime(accessTokenInfo.getRight()); + accessTokenDataBinder.create(loginResp.getUsername(), claims, authorities, true); + loginResp.setAccessToken(accessTokenInfo.getLeft()); + loginResp.setAccessTokenExpiryTime(accessTokenInfo.getRight()); - return loginResponse; + return loginResp; } @PreAuthorize("isAuthenticated() and not(hasRole('" + IdRepoEntitlement.ANONYMOUS + "'))") @@ -272,7 +270,7 @@ public OIDCRequest createLogoutRequest(final String accessToken, final String re profile.setIdTokenString((String) claimsSet.getClaim(JWT_CLAIM_ID_TOKEN)); WithLocationAction action = oidcClient.getLogoutAction( - new OIDC4UIContext(), + new OIDCC4UIContext(), NoOpSessionStore.INSTANCE, profile, redirectURI). diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UIProviderLogic.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UIProviderLogic.java index 5a1d9f7de7..30ceda618f 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UIProviderLogic.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/OIDCC4UIProviderLogic.java @@ -29,7 +29,7 @@ import org.apache.syncope.common.lib.to.Item; import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO; import org.apache.syncope.common.lib.types.ClientExceptionType; -import org.apache.syncope.common.lib.types.OIDC4UIEntitlement; +import org.apache.syncope.common.lib.types.OIDCC4UIEntitlement; import org.apache.syncope.core.logic.oidc.OIDCClientCache; import org.apache.syncope.core.persistence.api.dao.NotFoundException; import org.apache.syncope.core.persistence.api.dao.OIDCC4UIProviderDAO; @@ -60,7 +60,7 @@ public OIDCC4UIProviderLogic( this.binder = binder; } - @PreAuthorize("hasRole('" + OIDC4UIEntitlement.OP_CREATE + "')") + @PreAuthorize("hasRole('" + OIDCC4UIEntitlement.OP_CREATE + "')") public String createFromDiscovery(final OIDCC4UIProviderTO opTO) { try { OIDCClientCache.importMetadata(opTO); @@ -74,7 +74,7 @@ public String createFromDiscovery(final OIDCC4UIProviderTO opTO) { } } - @PreAuthorize("hasRole('" + OIDC4UIEntitlement.OP_CREATE + "')") + @PreAuthorize("hasRole('" + OIDCC4UIEntitlement.OP_CREATE + "')") public String create(final OIDCC4UIProviderTO opTO) { if (opTO.getConnObjectKeyItem() == null) { Item connObjectKeyItem = new Item(); @@ -94,7 +94,7 @@ public List list() { return opDAO.findAll().stream().map(binder::getOIDCProviderTO).collect(Collectors.toList()); } - @PreAuthorize("hasRole('" + OIDC4UIEntitlement.OP_READ + "')") + @PreAuthorize("hasRole('" + OIDCC4UIEntitlement.OP_READ + "')") @Transactional(readOnly = true) public OIDCC4UIProviderTO read(final String key) { OIDCC4UIProvider op = Optional.ofNullable(opDAO.find(key)). @@ -103,7 +103,7 @@ public OIDCC4UIProviderTO read(final String key) { return binder.getOIDCProviderTO(op); } - @PreAuthorize("hasRole('" + OIDC4UIEntitlement.OP_UPDATE + "')") + @PreAuthorize("hasRole('" + OIDCC4UIEntitlement.OP_UPDATE + "')") public void update(final OIDCC4UIProviderTO opTO) { OIDCC4UIProvider op = Optional.ofNullable(opDAO.find(opTO.getKey())). orElseThrow(() -> new NotFoundException("OIDC Provider '" + opTO.getKey() + '\'')); @@ -121,7 +121,7 @@ public void update(final OIDCC4UIProviderTO opTO) { oidcClientCacheLogout.removeAll(op.getName()); } - @PreAuthorize("hasRole('" + OIDC4UIEntitlement.OP_DELETE + "')") + @PreAuthorize("hasRole('" + OIDCC4UIEntitlement.OP_DELETE + "')") public void delete(final String key) { OIDCC4UIProvider op = Optional.ofNullable(opDAO.find(key)). orElseThrow(() -> new NotFoundException("OIDC Provider '" + key + '\'')); diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCC4UILoader.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCC4UILoader.java index 93d240a484..d4e4f3e4d8 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCC4UILoader.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/init/OIDCC4UILoader.java @@ -20,7 +20,7 @@ import org.apache.syncope.common.lib.types.EntitlementsHolder; import org.apache.syncope.common.lib.types.ImplementationTypesHolder; -import org.apache.syncope.common.lib.types.OIDC4UIEntitlement; +import org.apache.syncope.common.lib.types.OIDCC4UIEntitlement; import org.apache.syncope.common.lib.types.OIDCClientImplementationType; import org.apache.syncope.core.persistence.api.SyncopeCoreLoader; import org.springframework.core.Ordered; @@ -34,7 +34,7 @@ public int getOrder() { @Override public void load() { - EntitlementsHolder.getInstance().addAll(OIDC4UIEntitlement.values()); + EntitlementsHolder.getInstance().addAll(OIDCC4UIEntitlement.values()); ImplementationTypesHolder.getInstance().putAll(OIDCClientImplementationType.values()); } } diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDC4UIContext.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCC4UIContext.java similarity index 98% rename from ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDC4UIContext.java rename to ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCC4UIContext.java index deaed5f6d7..9ee3f8aba8 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDC4UIContext.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCC4UIContext.java @@ -26,7 +26,7 @@ import org.pac4j.core.context.Cookie; import org.pac4j.core.context.WebContext; -public class OIDC4UIContext implements WebContext { +public class OIDCC4UIContext implements WebContext { @Override public String getRequestMethod() { diff --git a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java index 98bffe1f80..dbda7839b6 100644 --- a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java +++ b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Function; +import java.util.stream.Collectors; import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO; import org.apache.syncope.core.persistence.api.entity.OIDCC4UIProvider; import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver; @@ -72,6 +73,7 @@ public static void importMetadata(final OIDCC4UIProviderTO opTO) Optional.ofNullable(metadata.getUserInfoEndpointURI()).map(URI::toASCIIString).orElse(null)); opTO.setEndSessionEndpoint( Optional.ofNullable(metadata.getEndSessionEndpointURI()).map(URI::toASCIIString).orElse(null)); + Optional.ofNullable(metadata.getScopes()).ifPresent(s -> opTO.getScopes().addAll(s.toStringList())); } protected final List cache = Collections.synchronizedList(new ArrayList<>()); @@ -94,15 +96,15 @@ public OidcClient add(final OIDCC4UIProvider op, final String callbackUrl) { metadata.setEndSessionEndpointURI( Optional.ofNullable(op.getEndSessionEndpoint()).map(URI::create).orElse(null)); - OidcConfiguration config = new OidcConfiguration(); - config.setClientId(op.getClientID()); - config.setSecret(op.getClientSecret()); - config.setProviderMetadata(metadata); - config.setScope("openid profile email address phone offline_access"); - config.setUseNonce(false); - config.setLogoutHandler(new NoOpLogoutHandler()); + OidcConfiguration cfg = new OidcConfiguration(); + cfg.setClientId(op.getClientID()); + cfg.setSecret(op.getClientSecret()); + cfg.setProviderMetadata(metadata); + cfg.setScope(op.getScopes().stream().collect(Collectors.joining(" "))); + cfg.setUseNonce(false); + cfg.setLogoutHandler(new NoOpLogoutHandler()); - OidcClient client = new OidcClient(config); + OidcClient client = new OidcClient(cfg); client.setName(op.getName()); client.setCallbackUrlResolver(new NoParameterCallbackUrlResolver()); client.setCallbackUrl(callbackUrl); diff --git a/ext/oidcc4ui/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCC4UIProvider.java b/ext/oidcc4ui/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCC4UIProvider.java index 325ec18bee..07df700a16 100644 --- a/ext/oidcc4ui/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCC4UIProvider.java +++ b/ext/oidcc4ui/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/OIDCC4UIProvider.java @@ -60,6 +60,10 @@ public interface OIDCC4UIProvider extends Entity { void setEndSessionEndpoint(String endSessionEndpoint); + List getScopes(); + + void setScopes(List scopes); + boolean getHasDiscovery(); void setHasDiscovery(boolean hasDiscovery); diff --git a/ext/oidcc4ui/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCC4UIProvider.java b/ext/oidcc4ui/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCC4UIProvider.java index 152c277b6e..3301f9aeec 100644 --- a/ext/oidcc4ui/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCC4UIProvider.java +++ b/ext/oidcc4ui/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCC4UIProvider.java @@ -40,6 +40,8 @@ import javax.persistence.Table; import javax.persistence.Transient; import javax.validation.constraints.NotNull; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.syncope.common.lib.to.Item; import org.apache.syncope.common.lib.types.OIDCClientImplementationType; import org.apache.syncope.core.persistence.api.entity.Implementation; @@ -47,6 +49,7 @@ import org.apache.syncope.core.persistence.api.entity.OIDCC4UIUserTemplate; import org.apache.syncope.core.persistence.jpa.validation.entity.OIDCC4UIProviderCheck; import org.apache.syncope.core.provisioning.api.serialization.POJOHelper; +import org.springframework.util.CollectionUtils; @Entity @Table(name = JPAOIDCC4UIProvider.TABLE) @@ -85,6 +88,8 @@ public class JPAOIDCC4UIProvider extends AbstractGeneratedKeyEntity implements O @Column(nullable = true) private String endSessionEndpoint; + private String scopes; + @Column(nullable = false) private boolean hasDiscovery; @@ -204,6 +209,20 @@ public void setEndSessionEndpoint(final String endSessionEndpoint) { this.endSessionEndpoint = endSessionEndpoint; } + @Override + public List getScopes() { + return Optional.ofNullable(scopes). + map(s -> Stream.of(s.split(" ")).collect(Collectors.toList())). + orElse(List.of()); + } + + @Override + public void setScopes(final List scopes) { + this.scopes = CollectionUtils.isEmpty(scopes) + ? "" + : scopes.stream().collect(Collectors.joining(" ")); + } + @Override public boolean getHasDiscovery() { return hasDiscovery; diff --git a/ext/oidcc4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCC4UIProviderDataBinderImpl.java b/ext/oidcc4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCC4UIProviderDataBinderImpl.java index 38e9494c48..9c7d814ec6 100644 --- a/ext/oidcc4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCC4UIProviderDataBinderImpl.java +++ b/ext/oidcc4ui/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCC4UIProviderDataBinderImpl.java @@ -173,6 +173,7 @@ public OIDCC4UIProvider update(final OIDCC4UIProvider op, final OIDCC4UIProvider op.setTokenEndpoint(opTO.getTokenEndpoint()); op.setUserinfoEndpoint(opTO.getUserinfoEndpoint()); op.setEndSessionEndpoint(opTO.getEndSessionEndpoint()); + op.setScopes(opTO.getScopes()); op.setHasDiscovery(opTO.getHasDiscovery()); op.setCreateUnmatching(opTO.isCreateUnmatching()); op.setSelfRegUnmatching(opTO.isSelfRegUnmatching()); @@ -243,6 +244,7 @@ public OIDCC4UIProviderTO getOIDCProviderTO(final OIDCC4UIProvider op) { opTO.setTokenEndpoint(op.getTokenEndpoint()); opTO.setUserinfoEndpoint(op.getUserinfoEndpoint()); opTO.setEndSessionEndpoint(op.getEndSessionEndpoint()); + opTO.getScopes().addAll(op.getScopes()); opTO.setHasDiscovery(op.getHasDiscovery()); opTO.setCreateUnmatching(op.isCreateUnmatching()); opTO.setSelfRegUnmatching(op.isSelfRegUnmatching()); diff --git a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java index f2298075c4..985ec78ae5 100644 --- a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java +++ b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/SAML2SP4UILogic.java @@ -319,10 +319,8 @@ public AuthnRequest build(final SAML2MessageContext context) { @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')") public SAML2LoginResponse validateLoginResponse(final SAML2Response saml2Response) { // 0. look for IdP - SAML2SP4UIIdP idp = idpDAO.findByEntityID(saml2Response.getIdpEntityID()); - if (idp == null) { - throw new NotFoundException("SAML 2.0 IdP '" + saml2Response.getIdpEntityID() + '\''); - } + SAML2SP4UIIdP idp = Optional.ofNullable(idpDAO.findByEntityID(saml2Response.getIdpEntityID())). + orElseThrow(() -> new NotFoundException("SAML 2.0 IdP '" + saml2Response.getIdpEntityID() + '\'')); // 1. look for configured client SAML2Client saml2Client = getSAML2Client( @@ -373,7 +371,7 @@ public SAML2LoginResponse validateLoginResponse(final SAML2Response saml2Respons for (SAML2Credentials.SAMLAttribute attr : credentials.getAttributes()) { if (!attr.getAttributeValues().isEmpty()) { - String attrName = attr.getFriendlyName() == null ? attr.getName() : attr.getFriendlyName(); + String attrName = Optional.ofNullable(attr.getFriendlyName()).orElse(attr.getName()); if (connObjectKeyItem != null && attrName.equals(connObjectKeyItem.getExtAttrName())) { keyValue = attr.getAttributeValues().get(0); } @@ -382,9 +380,9 @@ public SAML2LoginResponse validateLoginResponse(final SAML2Response saml2Respons } } - List matchingUsers = keyValue == null - ? List.of() - : userManager.findMatchingUser(keyValue, idp.getKey()); + List matchingUsers = Optional.ofNullable(keyValue). + map(k -> userManager.findMatchingUser(k, idp.getKey())). + orElse(List.of()); LOG.debug("Found {} matching users for {}", matchingUsers.size(), keyValue); String username; diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BatchITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BatchITCase.java index ea7b169748..13b83a1809 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BatchITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BatchITCase.java @@ -28,9 +28,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -41,7 +39,6 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.apache.commons.io.IOUtils; import org.apache.cxf.jaxrs.client.Client; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.syncope.client.lib.batch.BatchRequest; @@ -201,7 +198,7 @@ public void webClientSync() throws IOException { assertTrue(response.getMediaType().toString(). startsWith(RESTHeaders.multipartMixedWith(boundary.substring(2)))); - String body = IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8); + String body = response.readEntity(String.class); LOG.debug("Batch response body:\n{}", body); check(BatchPayloadParser.parse( @@ -245,7 +242,7 @@ public void webClientAsync() throws IOException { assertTrue(response.getMediaType().toString(). startsWith(RESTHeaders.multipartMixedWith(boundary.substring(2)))); - String body = IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8); + String body = response.readEntity(String.class); LOG.debug("Batch response body:\n{}", body); check(BatchPayloadParser.parse( diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java index 725d8adaf6..e79b27af51 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java @@ -25,10 +25,7 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import javax.ws.rs.core.Response; -import org.apache.commons.io.IOUtils; import org.apache.syncope.client.lib.SyncopeClientFactoryBean; import org.apache.syncope.common.lib.to.BpmnProcess; import org.apache.syncope.fit.AbstractITCase; @@ -62,8 +59,7 @@ public void exportUserWorkflowProcess() throws IOException { Response response = BPMN_PROCESS_SERVICE.get(USER_WORKFLOW_KEY); assertTrue(response.getMediaType().toString(). startsWith(CLIENT_FACTORY.getContentType().getMediaType().toString())); - assertTrue(response.getEntity() instanceof InputStream); - String definition = IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8); + String definition = response.readEntity(String.class); assertNotNull(definition); assertFalse(definition.isEmpty()); } @@ -71,7 +67,7 @@ public void exportUserWorkflowProcess() throws IOException { @Test public void updateUserWorkflowProcess() throws IOException { Response response = BPMN_PROCESS_SERVICE.get(USER_WORKFLOW_KEY); - String definition = IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8); + String definition = response.readEntity(String.class); BPMN_PROCESS_SERVICE.set(USER_WORKFLOW_KEY, definition); } diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MailTemplateITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MailTemplateITCase.java index 3257e342d6..edebe001cd 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MailTemplateITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MailTemplateITCase.java @@ -25,7 +25,6 @@ import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.List; import javax.ws.rs.core.MediaType; @@ -89,10 +88,7 @@ public void crud() throws IOException { response = MAIL_TEMPLATE_SERVICE.getFormat(key, MailTemplateFormat.TEXT); assertEquals(200, response.getStatus()); assertTrue(response.getMediaType().toString().startsWith(MediaType.TEXT_PLAIN)); - assertTrue(response.getEntity() instanceof InputStream); - assertEquals( - textTemplate, - IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + assertEquals(textTemplate, response.readEntity(String.class)); // 3. set HTML String htmlTemplate = "Hi there, I am ${username}."; @@ -102,10 +98,7 @@ public void crud() throws IOException { response = MAIL_TEMPLATE_SERVICE.getFormat(key, MailTemplateFormat.HTML); assertEquals(200, response.getStatus()); assertTrue(response.getMediaType().toString().startsWith(MediaType.TEXT_HTML)); - assertTrue(response.getEntity() instanceof InputStream); - assertEquals( - htmlTemplate, - IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + assertEquals(htmlTemplate, response.readEntity(String.class)); // 4. remove HTML MAIL_TEMPLATE_SERVICE.removeFormat(key, MailTemplateFormat.HTML); @@ -120,10 +113,7 @@ public void crud() throws IOException { response = MAIL_TEMPLATE_SERVICE.getFormat(key, MailTemplateFormat.TEXT); assertEquals(200, response.getStatus()); assertTrue(response.getMediaType().toString().startsWith(MediaType.TEXT_PLAIN)); - assertTrue(response.getEntity() instanceof InputStream); - assertEquals( - textTemplate, - IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + assertEquals(textTemplate, response.readEntity(String.class)); // 5. remove mail template MAIL_TEMPLATE_SERVICE.delete(key); diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RESTITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RESTITCase.java index e3378b530d..6a8c168b05 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RESTITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RESTITCase.java @@ -25,8 +25,6 @@ import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.security.AccessControlException; import java.util.List; import javax.ws.rs.ForbiddenException; @@ -36,7 +34,6 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.cxf.jaxrs.client.WebClient; @@ -108,9 +105,7 @@ public void noContent() throws IOException { Response response = noContentService.create(groupCR); assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); assertEquals(Preference.RETURN_NO_CONTENT.toString(), response.getHeaderString(RESTHeaders.PREFERENCE_APPLIED)); - assertEquals( - StringUtils.EMPTY, - IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + assertEquals(StringUtils.EMPTY, response.readEntity(String.class)); GroupTO group = getObject(response.getLocation(), GroupService.class, GroupTO.class); assertNotNull(group); @@ -122,16 +117,12 @@ public void noContent() throws IOException { response = noContentService.update(groupUR); assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); assertEquals(Preference.RETURN_NO_CONTENT.toString(), response.getHeaderString(RESTHeaders.PREFERENCE_APPLIED)); - assertEquals( - StringUtils.EMPTY, - IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + assertEquals(StringUtils.EMPTY, response.readEntity(String.class)); response = noContentService.delete(group.getKey()); assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); assertEquals(Preference.RETURN_NO_CONTENT.toString(), response.getHeaderString(RESTHeaders.PREFERENCE_APPLIED)); - assertEquals( - StringUtils.EMPTY, - IOUtils.toString((InputStream) response.getEntity(), StandardCharsets.UTF_8)); + assertEquals(StringUtils.EMPTY, response.readEntity(String.class)); } @Test @@ -203,9 +194,7 @@ public void exportInternalStorageContent() throws IOException { String contentDisposition = response.getHeaderString(HttpHeaders.CONTENT_DISPOSITION); assertNotNull(contentDisposition); - Object entity = response.getEntity(); - assertTrue(entity instanceof InputStream); - String configExport = IOUtils.toString((InputStream) entity, StandardCharsets.UTF_8.name()); + String configExport = response.readEntity(String.class); assertFalse(configExport.isEmpty()); assertTrue(configExport.length() > 1000); } diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java index 4a726321a5..110cadc546 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java @@ -26,8 +26,6 @@ import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.time.OffsetDateTime; import java.util.List; import java.util.Set; @@ -35,7 +33,6 @@ import java.util.concurrent.atomic.AtomicReference; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; -import org.apache.commons.io.IOUtils; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.to.ExecTO; import org.apache.syncope.common.lib.to.ReportTO; @@ -158,9 +155,7 @@ public void executeAndExport() throws IOException { assertNotNull(response.getHeaderString(HttpHeaders.CONTENT_DISPOSITION)); assertTrue(response.getHeaderString(HttpHeaders.CONTENT_DISPOSITION).endsWith(".pdf")); - Object entity = response.getEntity(); - assertTrue(entity instanceof InputStream); - assertFalse(IOUtils.toString((InputStream) entity, StandardCharsets.UTF_8.name()).isEmpty()); + assertFalse(response.readEntity(String.class).isEmpty()); } @Test diff --git a/fit/wa-reference/src/main/resources/log4j2.xml b/fit/wa-reference/src/main/resources/log4j2.xml index 9254c8ecab..e1468f37ed 100644 --- a/fit/wa-reference/src/main/resources/log4j2.xml +++ b/fit/wa-reference/src/main/resources/log4j2.xml @@ -52,6 +52,10 @@ under the License. + + + + 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 af705c303e..188d516894 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 @@ -22,10 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.nimbusds.jose.util.IOUtils; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -53,6 +50,7 @@ import org.apache.syncope.common.rest.api.service.UserService; import org.apache.syncope.common.rest.api.service.wa.WAConfigService; import org.apache.syncope.fit.sra.AbstractSRAITCase; +import org.apereo.cas.oidc.OidcConstants; import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.nodes.FormElement; @@ -117,26 +115,22 @@ public static void waitForWARefresh() { await().atMost(60, TimeUnit.SECONDS).pollInterval(20, TimeUnit.SECONDS).until(() -> { boolean refreshed = false; try { - String metadata = IOUtils.readInputStreamToString( - (InputStream) WebClient.create( - WA_ADDRESS + "/idp/metadata").get().getEntity(), - StandardCharsets.UTF_8); + String metadata = WebClient.create( + WA_ADDRESS + "/idp/metadata").get().readEntity(String.class); if (metadata.contains("localhost:8080")) { WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of()); throw new IllegalStateException(); } - metadata = IOUtils.readInputStreamToString( - (InputStream) WebClient.create( - WA_ADDRESS + "/oidc/.well-known/openid-configuration").get().getEntity(), - StandardCharsets.UTF_8); + metadata = WebClient.create( + WA_ADDRESS + "/oidc/" + OidcConstants.WELL_KNOWN_OPENID_CONFIGURATION_URL). + get().readEntity(String.class); if (metadata.contains("localhost:8080")) { WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of()); throw new IllegalStateException(); } - metadata = IOUtils.readInputStreamToString( - (InputStream) WebClient.create( - WA_ADDRESS + "/actuator/registeredServices", "anonymous", "anonymousKey", null). - get().getEntity(), StandardCharsets.UTF_8); + metadata = WebClient.create( + WA_ADDRESS + "/actuator/registeredServices", "anonymous", "anonymousKey", null). + get().readEntity(String.class); if (metadata.contains("localhost:8080/syncope-wa")) { WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.conf, List.of()); throw new IllegalStateException(); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java index cfa3cf3321..bb2a7d4d3f 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java @@ -45,6 +45,7 @@ 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.SyncopeConstants; import org.apache.syncope.common.lib.to.CASSPClientAppTO; import org.apache.syncope.common.lib.types.ClientAppType; import org.apache.syncope.common.rest.api.RESTHeaders; @@ -71,6 +72,7 @@ public static void clientAppSetup() { orElseGet(() -> { CASSPClientAppTO app = new CASSPClientAppTO(); app.setName(appName); + app.setRealm(SyncopeConstants.ROOT_REALM); app.setClientAppId(4L); app.setServiceId("http://127.0.0.1:8080/.*"); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java index bcd7369cc0..4f6cec2040 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java @@ -19,16 +19,13 @@ package org.apache.syncope.fit.sra; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; -import java.text.ParseException; import java.util.Properties; import java.util.concurrent.TimeoutException; import org.apache.http.HttpStatus; @@ -54,6 +51,8 @@ public static void clientAppSetup() { } catch (Exception e) { fail("Could not load /sra-oauth2.properties", e); } + SRA_REGISTRATION_ID = "OAUTH2"; + CLIENT_APP_ID = 2L; CLIENT_ID = props.getProperty("sra.oauth2.client-id"); assertNotNull(CLIENT_ID); CLIENT_SECRET = props.getProperty("sra.oauth2.client-secret"); @@ -61,7 +60,8 @@ public static void clientAppSetup() { TOKEN_URI = props.getProperty("sra.oauth2.tokenUri"); assertNotNull(TOKEN_URI); - oidcClientAppSetup(OAUTH2SRAITCase.class.getName(), "OAUTH2", 2L, CLIENT_ID, CLIENT_SECRET); + oidcClientAppSetup( + OAUTH2SRAITCase.class.getName(), SRA_REGISTRATION_ID, CLIENT_APP_ID, CLIENT_ID, CLIENT_SECRET); } @Override @@ -70,7 +70,7 @@ protected void checkLogout(final CloseableHttpResponse response) { } @Override - protected void checkIdToken(final JsonNode json) throws ParseException { - assertFalse(json.has("id_token")); + protected boolean checkIdToken() { + return false; } } diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java index 9736e3b070..12fdbb7717 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java @@ -18,6 +18,7 @@ */ package org.apache.syncope.fit.sra; +import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.oneOf; @@ -40,6 +41,7 @@ import java.util.List; import java.util.Properties; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.ws.rs.core.Form; import javax.ws.rs.core.HttpHeaders; @@ -59,17 +61,25 @@ 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.SyncopeConstants; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apache.syncope.common.lib.types.ClientAppType; +import org.apache.syncope.common.lib.types.OIDCGrantType; +import org.apache.syncope.common.lib.types.OIDCScope; import org.apache.syncope.common.lib.types.OIDCSubjectType; 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.BeforeAll; import org.junit.jupiter.api.Test; public class OIDCSRAITCase extends AbstractSRAITCase { + protected static String SRA_REGISTRATION_ID; + + protected static Long CLIENT_APP_ID; + protected static String CLIENT_ID; protected static String CLIENT_SECRET; @@ -97,6 +107,7 @@ protected static void oidcClientAppSetup( orElseGet(() -> { OIDCRPClientAppTO app = new OIDCRPClientAppTO(); app.setName(appName); + app.setRealm(SyncopeConstants.ROOT_REALM); app.setClientAppId(clientAppId); app.setClientId(clientId); app.setClientSecret(clientSecret); @@ -121,8 +132,30 @@ protected static void oidcClientAppSetup( clientApp.setLogoutUri(SRA_ADDRESS + "/logout"); clientApp.setAuthPolicy(getAuthPolicy().getKey()); clientApp.setAttrReleasePolicy(getAttrReleasePolicy().getKey()); + clientApp.getScopes().add(OIDCScope.openid); + clientApp.getScopes().add(OIDCScope.profile); + clientApp.getScopes().add(OIDCScope.email); + clientApp.getSupportedGrantTypes().add(OIDCGrantType.password); + clientApp.getSupportedGrantTypes().add(OIDCGrantType.authorization_code); CLIENT_APP_SERVICE.update(ClientAppType.OIDCRP, clientApp); + + await().atMost(60, 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(); + } + + return true; + } catch (Exception e) { + // ignore + } + return false; + }); WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, List.of()); } @@ -136,13 +169,16 @@ public static void clientAppSetup() { } catch (Exception e) { fail("Could not load /sra-oidc.properties", e); } + SRA_REGISTRATION_ID = "OIDC"; + CLIENT_APP_ID = 1L; CLIENT_ID = props.getProperty("sra.oidc.client-id"); assertNotNull(CLIENT_ID); CLIENT_SECRET = props.getProperty("sra.oidc.client-secret"); assertNotNull(CLIENT_SECRET); TOKEN_URI = WA_ADDRESS + "/oidc/accessToken"; - oidcClientAppSetup(OIDCSRAITCase.class.getName(), "OIDC", 1L, CLIENT_ID, CLIENT_SECRET); + oidcClientAppSetup( + OIDCSRAITCase.class.getName(), SRA_REGISTRATION_ID, CLIENT_APP_ID, CLIENT_ID, CLIENT_SECRET); } @Test @@ -222,11 +258,15 @@ public void web() throws IOException { checkLogout(response); } - protected void checkIdToken(final JsonNode json) throws ParseException { - SignedJWT idToken = SignedJWT.parse(json.get("id_token").asText()); - assertNotNull(idToken); - JWTClaimsSet idTokenClaimsSet = idToken.getJWTClaimsSet(); - assertEquals("verdi", idTokenClaimsSet.getStringClaim("preferred_username")); + private void checkJWT(final String token, final boolean idToken) throws ParseException { + assertNotNull(token); + SignedJWT jwt = SignedJWT.parse(token); + assertNotNull(jwt); + JWTClaimsSet idTokenClaimsSet = jwt.getJWTClaimsSet(); + assertEquals("verdi", idTokenClaimsSet.getSubject()); + if (idToken) { + assertEquals("verdi", idTokenClaimsSet.getStringClaim("preferred_username")); + } assertEquals("verdi@syncope.org", idTokenClaimsSet.getStringClaim("email")); assertEquals("Verdi", idTokenClaimsSet.getStringClaim("family_name")); assertEquals("Giuseppe", idTokenClaimsSet.getStringClaim("given_name")); @@ -234,6 +274,10 @@ protected void checkIdToken(final JsonNode json) throws ParseException { assertEquals(Set.of("root", "child", "citizen"), Set.of(idTokenClaimsSet.getStringArrayClaim("groups"))); } + protected boolean checkIdToken() { + return true; + } + @Test public void rest() throws IOException, ParseException { // 0. access public route @@ -249,19 +293,23 @@ public void rest() throws IOException, ParseException { param("client_secret", CLIENT_SECRET). param("username", "verdi"). param("password", "password"). - param("scope", "openid profile email address phone offline_access syncope"); + param("scope", "openid profile email syncope"); response = WebClient.create(TOKEN_URI).post(form); assertEquals(HttpStatus.SC_OK, response.getStatus()); assertTrue(response.getHeaderString(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.APPLICATION_JSON)); JsonNode json = MAPPER.readTree(response.readEntity(String.class)); - // 1a. verify id_token - checkIdToken(json); + if (checkIdToken()) { + // 1a. take and verify id_token + String idToken = json.get("id_token").asText(); + assertNotNull(idToken); + checkJWT(idToken, true); + } - // 1b. take access_token + // 1b. take and verify access_token String accessToken = json.get("access_token").asText(); - assertNotNull(accessToken); + checkJWT(accessToken, false); // 2. access protected route client = WebClient.create(SRA_ADDRESS + "/protected/post"). diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java index 8497326e81..63243f5890 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java @@ -46,6 +46,7 @@ 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.SyncopeConstants; import org.apache.syncope.common.lib.to.SAML2SPClientAppTO; import org.apache.syncope.common.lib.types.ClientAppType; import org.apache.syncope.common.lib.types.SAML2SPNameId; @@ -73,6 +74,7 @@ public static void clientAppSetup() { orElseGet(() -> { SAML2SPClientAppTO app = new SAML2SPClientAppTO(); app.setName(appName); + app.setRealm(SyncopeConstants.ROOT_REALM); app.setClientAppId(3L); app.setEntityId(SRA_ADDRESS); app.setMetadataLocation(SRA_ADDRESS + "/saml2/metadata"); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUIITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUIITCase.java index bdbf909069..a30dfd4551 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUIITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUIITCase.java @@ -84,10 +84,10 @@ protected static AttrReleasePolicyTO getAttrReleasePolicy() { DefaultAttrReleasePolicyConf policyConf = new DefaultAttrReleasePolicyConf(); policyConf.getPrincipalAttrRepoConf().getAttrRepos().add(stubAttrRepo); policyConf.getReleaseAttrs().put("attr1", "identifier"); - policyConf.getReleaseAttrs().put("firstname", "givenName"); - policyConf.getReleaseAttrs().put("surname", "sn"); - policyConf.getReleaseAttrs().put("fullname", "cn"); - policyConf.getReleaseAttrs().put("email", "mail"); + policyConf.getReleaseAttrs().put("firstname", "given_name"); + policyConf.getReleaseAttrs().put("surname", "family_name"); + policyConf.getReleaseAttrs().put("fullname", "name"); + policyConf.getReleaseAttrs().put("email", "email"); AttrReleasePolicyTO policy = new AttrReleasePolicyTO(); policy.setName(description); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java similarity index 91% rename from fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java rename to fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java index 2b40a82e24..45cc41e2f9 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDC4UIITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java @@ -28,8 +28,10 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.util.stream.Stream; import org.apache.http.Consts; import org.apache.http.HttpHeaders; import org.apache.http.HttpStatus; @@ -45,18 +47,20 @@ 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.SyncopeConstants; import org.apache.syncope.common.lib.to.Item; import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apache.syncope.common.lib.types.ClientAppType; import org.apache.syncope.common.lib.types.OIDCResponseType; +import org.apache.syncope.common.lib.types.OIDCScope; import org.apache.syncope.common.lib.types.OIDCSubjectType; import org.apache.syncope.common.rest.api.RESTHeaders; import org.apache.syncope.common.rest.api.service.wa.WAConfigService; import org.jsoup.Jsoup; import org.junit.jupiter.api.BeforeAll; -public class OIDC4UIITCase extends AbstractUIITCase { +public class OIDCC4UIITCase extends AbstractUIITCase { private static void clientAppSetup(final String appName, final String baseAddress, final long appId) { OIDCRPClientAppTO clientApp = CLIENT_APP_SERVICE.list(ClientAppType.OIDCRP).stream(). @@ -66,6 +70,7 @@ private static void clientAppSetup(final String appName, final String baseAddres orElseGet(() -> { OIDCRPClientAppTO app = new OIDCRPClientAppTO(); app.setName(appName); + app.setRealm(SyncopeConstants.ROOT_REALM); app.setClientAppId(appId); app.setClientId(appName); app.setClientSecret(appName); @@ -92,6 +97,9 @@ 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(OIDCScope.openid); + clientApp.getScopes().add(OIDCScope.profile); + clientApp.getScopes().add(OIDCScope.email); CLIENT_APP_SERVICE.update(ClientAppType.OIDCRP, clientApp); WA_CONFIG_SERVICE.pushToWA(WAConfigService.PushSubject.clientApps, List.of()); @@ -99,8 +107,8 @@ private static void clientAppSetup(final String appName, final String baseAddres private static String getAppName(final String address) { return CONSOLE_ADDRESS.equals(address) - ? OIDC4UIITCase.class.getName() + "_Console" - : OIDC4UIITCase.class.getName() + "_Enduser"; + ? OIDCC4UIITCase.class.getName() + "_Console" + : OIDCC4UIITCase.class.getName() + "_Enduser"; } @BeforeAll @@ -134,6 +142,9 @@ private static void oidcSetup( cas.setUserinfoEndpoint(cas.getIssuer() + "/profile"); cas.setEndSessionEndpoint(cas.getIssuer() + "/logout"); + cas.getScopes().addAll(Stream.of(OIDCScope.values()).map(OIDCScope::name).collect(Collectors.toList())); + cas.getScopes().add("syncope"); + cas.setCreateUnmatching(createUnmatching); cas.setSelfRegUnmatching(selfRegUnmatching); @@ -145,27 +156,27 @@ private static void oidcSetup( item = new Item(); item.setIntAttrName("email"); - item.setExtAttrName("mail"); + item.setExtAttrName("email"); cas.add(item); item = new Item(); item.setIntAttrName("userId"); - item.setExtAttrName("mail"); + item.setExtAttrName("email"); cas.add(item); item = new Item(); item.setIntAttrName("firstname"); - item.setExtAttrName("givenName"); + item.setExtAttrName("given_name"); cas.add(item); item = new Item(); item.setIntAttrName("surname"); - item.setExtAttrName("sn"); + item.setExtAttrName("family_name"); cas.add(item); item = new Item(); item.setIntAttrName("fullname"); - item.setExtAttrName("cn"); + item.setExtAttrName("name"); cas.add(item); OIDCC4UI_PROVIDER_SERVICE.create(cas); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/SAML2SP4UIITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/SAML2SP4UIITCase.java index 2c63604369..caf1aae069 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/SAML2SP4UIITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/SAML2SP4UIITCase.java @@ -48,6 +48,7 @@ import org.apache.http.util.EntityUtils; import org.apache.syncope.client.ui.commons.SAML2SP4UIConstants; import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.to.Item; import org.apache.syncope.common.lib.to.SAML2SP4UIIdPTO; import org.apache.syncope.common.lib.to.SAML2SPClientAppTO; @@ -68,6 +69,7 @@ private static void clientAppSetup(final String appName, final String entityId, orElseGet(() -> { SAML2SPClientAppTO app = new SAML2SPClientAppTO(); app.setName(appName); + app.setRealm(SyncopeConstants.ROOT_REALM); app.setClientAppId(appId); app.setEntityId(entityId); app.setMetadataLocation(entityId + SAML2SP4UIConstants.URL_CONTEXT + "/metadata"); @@ -136,27 +138,27 @@ public static void idpSetup() { item = new Item(); item.setIntAttrName("email"); - item.setExtAttrName("mail"); + item.setExtAttrName("email"); cas.add(item); item = new Item(); item.setIntAttrName("userId"); - item.setExtAttrName("mail"); + item.setExtAttrName("email"); cas.add(item); item = new Item(); item.setIntAttrName("firstname"); - item.setExtAttrName("givenName"); + item.setExtAttrName("given_name"); cas.add(item); item = new Item(); item.setIntAttrName("surname"); - item.setExtAttrName("sn"); + item.setExtAttrName("family_name"); cas.add(item); item = new Item(); item.setIntAttrName("fullname"); - item.setExtAttrName("cn"); + item.setExtAttrName("name"); cas.add(item); SAML2SP4UI_IDP_SERVICE.update(cas); diff --git a/fit/wa-reference/src/test/resources/sra-oidc.properties b/fit/wa-reference/src/test/resources/sra-oidc.properties index 9f7b8f3ea3..c4a60b99ba 100644 --- a/fit/wa-reference/src/test/resources/sra-oidc.properties +++ b/fit/wa-reference/src/test/resources/sra-oidc.properties @@ -22,5 +22,6 @@ sra.am-type=OIDC sra.oidc.configuration=https://localhost:9443/syncope-wa/oidc sra.oidc.client-id=oidcTestClientId sra.oidc.client-secret=oidcTestClientSecret +sra.oidc.scopes=openid,profile,email sra.global.postLogout=http://localhost:8080/logout diff --git a/sra/src/test/resources/debug/sra-debug.properties b/sra/src/test/resources/debug/sra-debug.properties index f73cd07e94..a95b6b9538 100644 --- a/sra/src/test/resources/debug/sra-debug.properties +++ b/sra/src/test/resources/debug/sra-debug.properties @@ -22,6 +22,7 @@ sra.am-type=OIDC sra.oidc.configuration=https://localhost:9443/syncope-wa/oidc sra.oidc.client-id=oidcTestClientId sra.oidc.client-secret=oidcTestClientSecret +sra.oidc.scopes=openid,profile,email #sra.am-type=OAUTH2 #sra.oauth2.tokenUri=https://localhost:9443/syncope-wa/oauth2.0/accessToken diff --git a/wa/bootstrap/pom.xml b/wa/bootstrap/pom.xml index 5cf252bd37..0127400461 100644 --- a/wa/bootstrap/pom.xml +++ b/wa/bootstrap/pom.xml @@ -57,6 +57,14 @@ under the License. org.apereo.cas cas-server-core-util-api + + org.apereo.cas + cas-server-core-authentication-attributes + + + org.apereo.cas + cas-server-support-oidc-core + org.slf4j 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 f470e9a778..dcd0fb7a21 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 @@ -18,6 +18,10 @@ */ package org.apache.syncope.wa.bootstrap; +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.DefaultAttrReleaseMapper; import org.apereo.cas.configuration.support.CasConfigurationJasyptCipherExecutor; import org.apereo.cas.util.crypto.CipherExecutor; import org.springframework.beans.factory.annotation.Qualifier; @@ -58,8 +62,8 @@ public WARestClient waRestClient() { @Configuration(proxyBeanMethods = false) public static class PropertySourceConfiguration { - @Bean @ConditionalOnMissingBean(name = "waConfigurationCipher") + @Bean public CipherExecutor waConfigurationCipher(final Environment environment) { return new CasConfigurationJasyptCipherExecutor(environment); } @@ -76,17 +80,27 @@ public AttrRepoPropertySourceMapper attrRepoPropertySourceMapper(final WARestCli return new AttrRepoPropertySourceMapper(waRestClient); } + @ConditionalOnMissingBean + @Bean + public AttrReleaseMapper attrReleaseMapper() { + return new DefaultAttrReleaseMapper(); + } + @Bean public PropertySourceLocator configPropertySourceLocator( @Qualifier("waConfigurationCipher") final CipherExecutor waConfigurationCipher, final WARestClient waRestClient, final AuthModulePropertySourceMapper authModulePropertySourceMapper, - final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper) { + final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper, + final AttrReleaseMapper attrReleaseMapper) { return new WAPropertySourceLocator( - waRestClient, authModulePropertySourceMapper, - attrRepoPropertySourceMapper, waConfigurationCipher); + waRestClient, + authModulePropertySourceMapper, + attrRepoPropertySourceMapper, + 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 21b8ff3c2f..96e372a8a9 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 @@ -19,16 +19,35 @@ package org.apache.syncope.wa.bootstrap; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang3.ArrayUtils; 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.types.OIDCScope; 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.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.apereo.cas.configuration.model.support.oidc.OidcDiscoveryProperties; +import org.apereo.cas.oidc.claims.OidcAddressScopeAttributeReleasePolicy; +import org.apereo.cas.oidc.claims.OidcEmailScopeAttributeReleasePolicy; +import org.apereo.cas.oidc.claims.OidcPhoneScopeAttributeReleasePolicy; +import org.apereo.cas.oidc.claims.OidcProfileScopeAttributeReleasePolicy; +import org.apereo.cas.services.BaseMappedAttributeReleasePolicy; +import org.apereo.cas.services.ChainingAttributeReleasePolicy; +import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy; +import org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy; import org.apereo.cas.util.crypto.CipherExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,17 +68,21 @@ public class WAPropertySourceLocator implements PropertySourceLocator { protected final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper; + protected final AttrReleaseMapper attrReleaseMapper; + protected final CipherExecutor configurationCipher; public WAPropertySourceLocator( final WARestClient waRestClient, final AuthModulePropertySourceMapper authModulePropertySourceMapper, final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper, + final AttrReleaseMapper attrReleaseMapper, final CipherExecutor configurationCipher) { this.waRestClient = waRestClient; this.authModulePropertySourceMapper = authModulePropertySourceMapper; this.attrRepoPropertySourceMapper = attrRepoPropertySourceMapper; + this.attrReleaseMapper = attrReleaseMapper; this.configurationCipher = configurationCipher; } @@ -109,6 +132,56 @@ public PropertySource locate(final Environment environment) { properties.putAll(index(map, prefixes)); }); + Set customClaims = syncopeClient.getService(WAClientAppService.class).list().stream(). + filter(clientApp -> clientApp.getAttrReleasePolicy() != null + && clientApp.getClientAppTO() instanceof OIDCRPClientAppTO). + flatMap(clientApp -> { + OIDCRPClientAppTO rp = OIDCRPClientAppTO.class.cast(clientApp.getClientAppTO()); + + RegisteredServiceAttributeReleasePolicy attributeReleasePolicy = + attrReleaseMapper.build(clientApp.getAttrReleasePolicy()); + + Set claims = new HashSet<>(); + if (attributeReleasePolicy instanceof BaseMappedAttributeReleasePolicy) { + claims.addAll(((BaseMappedAttributeReleasePolicy) attributeReleasePolicy). + getAllowedAttributes().values().stream(). + map(Objects::toString).collect(Collectors.toSet())); + } else if (attributeReleasePolicy instanceof ReturnAllowedAttributeReleasePolicy) { + claims.addAll(((ReturnAllowedAttributeReleasePolicy) attributeReleasePolicy). + getAllowedAttributes().stream().collect(Collectors.toSet())); + } else if (attributeReleasePolicy instanceof ChainingAttributeReleasePolicy) { + ((ChainingAttributeReleasePolicy) attributeReleasePolicy).getPolicies().stream(). + filter(ReturnAllowedAttributeReleasePolicy.class::isInstance). + findFirst().map(ReturnAllowedAttributeReleasePolicy.class::cast). + map(p -> p.getAllowedAttributes().stream().collect(Collectors.toSet())). + ifPresent(claims::addAll); + } + if (rp.getScopes().contains(OIDCScope.profile)) { + claims.removeAll(OidcProfileScopeAttributeReleasePolicy.ALLOWED_CLAIMS); + } + if (rp.getScopes().contains(OIDCScope.address)) { + claims.removeAll(OidcAddressScopeAttributeReleasePolicy.ALLOWED_CLAIMS); + } + if (rp.getScopes().contains(OIDCScope.email)) { + claims.removeAll(OidcEmailScopeAttributeReleasePolicy.ALLOWED_CLAIMS); + } + if (rp.getScopes().contains(OIDCScope.phone)) { + claims.removeAll(OidcPhoneScopeAttributeReleasePolicy.ALLOWED_CLAIMS); + } + + return claims.stream(); + }).collect(Collectors.toSet()); + if (!customClaims.isEmpty()) { + Stream.concat(new OidcDiscoveryProperties().getClaims().stream(), customClaims.stream()). + collect(Collectors.joining(",")); + + properties.put("cas.authn.oidc.discovery.claims", + Stream.concat(new OidcDiscoveryProperties().getClaims().stream(), customClaims.stream()). + collect(Collectors.joining(","))); + properties.put("cas.authn.oidc.core.user-defined-scopes.syncope", + customClaims.stream().collect(Collectors.joining(","))); + } + syncopeClient.getService(WAConfigService.class).list().forEach(attr -> properties.put( attr.getSchema(), attr.getValues().stream().collect(Collectors.joining(",")))); diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WARestClient.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WARestClient.java index 8d48df5a02..cd7c227782 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WARestClient.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WARestClient.java @@ -89,7 +89,7 @@ protected Optional getCore() { return Optional.empty(); } - protected SyncopeClient getSyncopeClient() { + public SyncopeClient getSyncopeClient() { synchronized (this) { if (client == null) { getCore().ifPresent(core -> { diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/AttrReleaseMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java similarity index 96% rename from wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/AttrReleaseMapper.java rename to wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java index 7b54d36734..021968582a 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/AttrReleaseMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrReleaseMapper.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.wa.starter.mapping; +package org.apache.syncope.wa.bootstrap.mapping; import org.apache.syncope.common.lib.policy.AttrReleasePolicyConf; import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO; diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AttrRepoPropertySourceMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrRepoPropertySourceMapper.java similarity index 98% rename from wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AttrRepoPropertySourceMapper.java rename to wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrRepoPropertySourceMapper.java index 28e0e239ea..cbdf8bd665 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AttrRepoPropertySourceMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrRepoPropertySourceMapper.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.wa.bootstrap; +package org.apache.syncope.wa.bootstrap.mapping; import java.util.Map; import java.util.stream.Collectors; @@ -29,6 +29,7 @@ import org.apache.syncope.common.lib.attr.SyncopeAttrRepoConf; import org.apache.syncope.common.lib.to.AttrRepoTO; import org.apache.syncope.common.lib.to.Item; +import org.apache.syncope.wa.bootstrap.WARestClient; import org.apereo.cas.configuration.CasCoreConfigurationUtils; import org.apereo.cas.configuration.model.core.authentication.AttributeRepositoryStates; import org.apereo.cas.configuration.model.core.authentication.StubPrincipalAttributesProperties; diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java similarity index 99% rename from wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapper.java rename to wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java index c723556ed0..e3c4c876a4 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.wa.bootstrap; +package org.apache.syncope.wa.bootstrap.mapping; import java.util.Map; import java.util.Optional; @@ -45,6 +45,7 @@ import org.apache.syncope.common.lib.to.AuthModuleTO; import org.apache.syncope.common.lib.to.Item; import org.apache.syncope.common.lib.types.AuthModuleState; +import org.apache.syncope.wa.bootstrap.WARestClient; import org.apereo.cas.configuration.CasCoreConfigurationUtils; import org.apereo.cas.configuration.model.core.authentication.AuthenticationHandlerStates; import org.apereo.cas.configuration.model.support.generic.AcceptAuthenticationProperties; diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/DefaultAttrReleaseMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java similarity index 99% rename from wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/DefaultAttrReleaseMapper.java rename to wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java index 5279c15232..aa03074c32 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/DefaultAttrReleaseMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/DefaultAttrReleaseMapper.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.wa.starter.mapping; +package org.apache.syncope.wa.bootstrap.mapping; import java.util.HashSet; import org.apache.syncope.common.lib.policy.AttrReleasePolicyConf; diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/PropertySourceMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/PropertySourceMapper.java similarity index 99% rename from wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/PropertySourceMapper.java rename to wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/PropertySourceMapper.java index 1cf4f6cd98..ce2f3ac627 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/PropertySourceMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/PropertySourceMapper.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.syncope.wa.bootstrap; +package org.apache.syncope.wa.bootstrap.mapping; import java.util.Map; import java.util.Optional; diff --git a/wa/bootstrap/src/test/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapperTest.java b/wa/bootstrap/src/test/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapperTest.java index b495d3c642..f1288c7e0a 100644 --- a/wa/bootstrap/src/test/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapperTest.java +++ b/wa/bootstrap/src/test/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapperTest.java @@ -24,6 +24,7 @@ import org.apache.syncope.common.lib.auth.OAuth20AuthModuleConf; import org.apache.syncope.common.lib.auth.SimpleMfaAuthModuleConf; import org.apache.syncope.common.lib.to.AuthModuleTO; +import org.apache.syncope.wa.bootstrap.mapping.AuthModulePropertySourceMapper; import org.junit.jupiter.api.Test; public class AuthModulePropertySourceMapperTest { diff --git a/wa/starter/pom.xml b/wa/starter/pom.xml index af48c5318c..494494a7be 100644 --- a/wa/starter/pom.xml +++ b/wa/starter/pom.xml @@ -186,10 +186,6 @@ under the License. org.apereo.cas cas-server-support-oidc-core-api - - org.apereo.cas - cas-server-support-oidc-core - org.apereo.cas cas-server-support-oauth-services @@ -306,10 +302,6 @@ under the License. org.apereo.cas cas-server-support-swagger - - org.apereo.cas - cas-server-core-authentication-attributes - org.apereo.cas cas-server-core-services-authentication 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 8871cfb159..122597d506 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 @@ -36,6 +36,7 @@ import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop; import org.apache.syncope.wa.bootstrap.WAProperties; import org.apache.syncope.wa.bootstrap.WARestClient; +import org.apache.syncope.wa.bootstrap.mapping.AttrReleaseMapper; import org.apache.syncope.wa.starter.actuate.SyncopeCoreHealthIndicator; import org.apache.syncope.wa.starter.actuate.SyncopeWAInfoContributor; import org.apache.syncope.wa.starter.audit.WAAuditTrailManager; @@ -43,12 +44,10 @@ import org.apache.syncope.wa.starter.gauth.WAGoogleMfaAuthCredentialRepository; import org.apache.syncope.wa.starter.gauth.WAGoogleMfaAuthTokenRepository; import org.apache.syncope.wa.starter.mapping.AccessMapper; -import org.apache.syncope.wa.starter.mapping.AttrReleaseMapper; import org.apache.syncope.wa.starter.mapping.AuthMapper; import org.apache.syncope.wa.starter.mapping.CASSPClientAppTOMapper; import org.apache.syncope.wa.starter.mapping.ClientAppMapper; import org.apache.syncope.wa.starter.mapping.DefaultAccessMapper; -import org.apache.syncope.wa.starter.mapping.DefaultAttrReleaseMapper; import org.apache.syncope.wa.starter.mapping.DefaultAuthMapper; import org.apache.syncope.wa.starter.mapping.DefaultTicketExpirationMapper; import org.apache.syncope.wa.starter.mapping.HttpRequestAccessMapper; @@ -159,19 +158,13 @@ public TimeBasedAccessMapper timeBasedAccessMapper() { @ConditionalOnMissingBean @Bean - public AttrReleaseMapper defaultAttrReleaseMapper() { - return new DefaultAttrReleaseMapper(); - } - - @ConditionalOnMissingBean - @Bean - public AuthMapper defaultAuthMapper() { + public AuthMapper authMapper() { return new DefaultAuthMapper(); } @ConditionalOnMissingBean @Bean - public TicketExpirationMapper defaultTicketExpirationMapper() { + public TicketExpirationMapper ticketExpirationMapper() { return new DefaultTicketExpirationMapper(); } @@ -204,8 +197,7 @@ public RegisteredServiceMapper registeredServiceMapper( final List accessMappers, final List attrReleaseMappers, final List ticketExpirationMappers, - final List clientAppMappers, - final CasConfigurationProperties properties) { + final List clientAppMappers) { return new RegisteredServiceMapper( Optional.ofNullable(casProperties.getAuthn().getPac4j().getCore().getName()). @@ -216,8 +208,7 @@ public RegisteredServiceMapper registeredServiceMapper( accessMappers, attrReleaseMappers, ticketExpirationMappers, - clientAppMappers, - properties); + clientAppMappers); } @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPClientAppTOMapper.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPClientAppTOMapper.java index 26b8042b80..02f3fd505f 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPClientAppTOMapper.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPClientAppTOMapper.java @@ -21,7 +21,6 @@ import org.apache.syncope.common.lib.to.CASSPClientAppTO; import org.apache.syncope.common.lib.to.ClientAppTO; import org.apache.syncope.common.lib.wa.WAClientApp; -import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.services.CasRegisteredService; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAccessStrategy; @@ -50,8 +49,7 @@ public RegisteredService map( final RegisteredServiceTicketGrantingTicketExpirationPolicy tgtExpirationPolicy, final RegisteredServiceServiceTicketExpirationPolicy stExpirationPolicy, final RegisteredServiceProxyGrantingTicketExpirationPolicy tgtProxyExpirationPolicy, - final RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy, - final CasConfigurationProperties properties) { + final RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy) { CASSPClientAppTO cas = CASSPClientAppTO.class.cast(clientApp.getClientAppTO()); diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/ClientAppMapper.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/ClientAppMapper.java index e9fb97a459..79cf79a6de 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/ClientAppMapper.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/ClientAppMapper.java @@ -20,7 +20,6 @@ import org.apache.syncope.common.lib.to.ClientAppTO; import org.apache.syncope.common.lib.wa.WAClientApp; -import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAccessStrategy; import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy; @@ -44,6 +43,5 @@ RegisteredService map( RegisteredServiceTicketGrantingTicketExpirationPolicy tgtExpirationPolicy, RegisteredServiceServiceTicketExpirationPolicy stExpirationPolicy, RegisteredServiceProxyGrantingTicketExpirationPolicy tgtProxyExpirationPolicy, - RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy, - CasConfigurationProperties properties); + RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy); } 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 e3f3a881ad..5342d60eca 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 @@ -19,22 +19,18 @@ package org.apache.syncope.wa.starter.mapping; import java.util.HashSet; -import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.apache.syncope.common.lib.to.ClientAppTO; import org.apache.syncope.common.lib.to.OIDCRPClientAppTO; import org.apache.syncope.common.lib.types.OIDCGrantType; import org.apache.syncope.common.lib.types.OIDCResponseType; import org.apache.syncope.common.lib.types.OIDCScope; import org.apache.syncope.common.lib.wa.WAClientApp; -import org.apereo.cas.configuration.CasConfigurationProperties; 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.BaseMappedAttributeReleasePolicy; @@ -70,8 +66,7 @@ public RegisteredService map( final RegisteredServiceTicketGrantingTicketExpirationPolicy tgtExpirationPolicy, final RegisteredServiceServiceTicketExpirationPolicy stExpirationPolicy, final RegisteredServiceProxyGrantingTicketExpirationPolicy tgtProxyExpirationPolicy, - final RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy, - final CasConfigurationProperties properties) { + final RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy) { OIDCRPClientAppTO rp = OIDCRPClientAppTO.class.cast(clientApp.getClientAppTO()); OidcRegisteredService service = new OidcRegisteredService(); @@ -89,39 +84,15 @@ public RegisteredService map( service.setJwtAccessToken(rp.isJwtAccessToken()); service.setBypassApprovalPrompt(rp.isBypassApprovalPrompt()); service.setSupportedGrantTypes(rp.getSupportedGrantTypes().stream(). - map(OIDCGrantType::name).collect(Collectors.toCollection(HashSet::new))); + map(OIDCGrantType::name).collect(Collectors.toSet())); service.setSupportedResponseTypes(rp.getSupportedResponseTypes().stream(). - map(OIDCResponseType::getExternalForm).collect(Collectors.toCollection(HashSet::new))); - if (rp.getSubjectType() != null) { - service.setSubjectType(rp.getSubjectType().name()); - } + map(OIDCResponseType::getExternalForm).collect(Collectors.toSet())); + Optional.ofNullable(rp.getSubjectType()).ifPresent(st -> service.setSubjectType(st.name())); service.setLogoutUrl(rp.getLogoutUri()); - ChainingAttributeReleasePolicy chain; - if (attributeReleasePolicy instanceof ChainingAttributeReleasePolicy) { - chain = (ChainingAttributeReleasePolicy) attributeReleasePolicy; - } else { - chain = new ChainingAttributeReleasePolicy(); - if (attributeReleasePolicy != null) { - chain.addPolicies(attributeReleasePolicy); - } - } - - if (rp.getScopes().contains(OIDCScope.OPENID)) { - chain.addPolicies(new OidcOpenIdScopeAttributeReleasePolicy()); - } - if (rp.getScopes().contains(OIDCScope.PROFILE)) { - chain.addPolicies(new OidcProfileScopeAttributeReleasePolicy()); - } - if (rp.getScopes().contains(OIDCScope.ADDRESS)) { - chain.addPolicies(new OidcAddressScopeAttributeReleasePolicy()); - } - if (rp.getScopes().contains(OIDCScope.EMAIL)) { - chain.addPolicies(new OidcEmailScopeAttributeReleasePolicy()); - } - if (rp.getScopes().contains(OIDCScope.PHONE)) { - chain.addPolicies(new OidcPhoneScopeAttributeReleasePolicy()); - } + service.setScopes(rp.getScopes().stream(). + map(OIDCScope::name). + collect(Collectors.toSet())); Set customClaims = new HashSet<>(); if (attributeReleasePolicy instanceof BaseMappedAttributeReleasePolicy) { @@ -138,31 +109,25 @@ public RegisteredService map( map(p -> p.getAllowedAttributes().stream().collect(Collectors.toSet())). ifPresent(customClaims::addAll); } - if (rp.getScopes().contains(OIDCScope.PROFILE)) { + if (rp.getScopes().contains(OIDCScope.profile)) { customClaims.removeAll(OidcProfileScopeAttributeReleasePolicy.ALLOWED_CLAIMS); } - if (rp.getScopes().contains(OIDCScope.ADDRESS)) { + if (rp.getScopes().contains(OIDCScope.address)) { customClaims.removeAll(OidcAddressScopeAttributeReleasePolicy.ALLOWED_CLAIMS); } - if (rp.getScopes().contains(OIDCScope.EMAIL)) { + if (rp.getScopes().contains(OIDCScope.email)) { customClaims.removeAll(OidcEmailScopeAttributeReleasePolicy.ALLOWED_CLAIMS); } - if (rp.getScopes().contains(OIDCScope.PHONE)) { + if (rp.getScopes().contains(OIDCScope.phone)) { customClaims.removeAll(OidcPhoneScopeAttributeReleasePolicy.ALLOWED_CLAIMS); } - if (!customClaims.isEmpty()) { - List supportedClaims = properties.getAuthn().getOidc().getDiscovery().getClaims(); - if (!supportedClaims.containsAll(customClaims)) { - properties.getAuthn().getOidc().getDiscovery().setClaims( - Stream.concat(supportedClaims.stream(), customClaims.stream()). - distinct().collect(Collectors.toList())); - } - chain.addPolicies(new OidcCustomScopeAttributeReleasePolicy( - CUSTOM_SCOPE, customClaims.stream().collect(Collectors.toList()))); + if (!customClaims.isEmpty()) { + service.getScopes().add(CUSTOM_SCOPE); } - setPolicies(service, authPolicy, mfaPolicy, accessStrategy, chain, + // never set attribute relase policy for OIDC services to avoid becoming scope-free for CAS + setPolicies(service, authPolicy, mfaPolicy, accessStrategy, null, tgtExpirationPolicy, stExpirationPolicy, tgtProxyExpirationPolicy, stProxyExpirationPolicy); 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 c7e8ca34e9..536147167e 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 @@ -23,9 +23,9 @@ import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO; import org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf; import org.apache.syncope.common.lib.wa.WAClientApp; +import org.apache.syncope.wa.bootstrap.mapping.AttrReleaseMapper; import org.apereo.cas.authentication.AuthenticationEventExecutionPlan; import org.apereo.cas.authentication.MultifactorAuthenticationProvider; -import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAccessStrategy; @@ -43,7 +43,7 @@ public class RegisteredServiceMapper { - private static final Logger LOG = LoggerFactory.getLogger(RegisteredServiceMapper.class); + protected static final Logger LOG = LoggerFactory.getLogger(RegisteredServiceMapper.class); protected final String pac4jCoreName; @@ -61,8 +61,6 @@ public class RegisteredServiceMapper { protected final List clientAppMappers; - protected final CasConfigurationProperties properties; - public RegisteredServiceMapper( final String pac4jCoreName, final ObjectProvider authEventExecPlan, @@ -71,8 +69,7 @@ public RegisteredServiceMapper( final List accessMappers, final List attrReleaseMappers, final List ticketExpirationMappers, - final List clientAppMappers, - final CasConfigurationProperties properties) { + final List clientAppMappers) { this.pac4jCoreName = pac4jCoreName; this.authEventExecPlan = authEventExecPlan; @@ -82,19 +79,20 @@ public RegisteredServiceMapper( this.attrReleaseMappers = attrReleaseMappers; this.ticketExpirationMappers = ticketExpirationMappers; this.clientAppMappers = clientAppMappers; - this.properties = properties; } public RegisteredService toRegisteredService(final WAClientApp clientApp) { - ClientAppMapper clientAppMapper = clientAppMappers.stream(). + return clientAppMappers.stream(). filter(m -> m.supports(clientApp.getClientAppTO())). findFirst(). - orElse(null); - if (clientAppMapper == null) { - LOG.warn("Unable to locate ClientAppMapper for {}", clientApp.getClientAppTO().getClass().getName()); - return null; - } + map(clientAppMapper -> toRegisteredService(clientApp, clientAppMapper)). + orElseGet(() -> { + LOG.warn("Unable to locate mapper for {}", clientApp.getClientAppTO().getClass().getName()); + return null; + }); + } + public RegisteredService toRegisteredService(final WAClientApp clientApp, final ClientAppMapper clientAppMapper) { RegisteredServiceAuthenticationPolicy authPolicy = null; RegisteredServiceMultifactorPolicy mfaPolicy = null; RegisteredServiceDelegatedAuthenticationPolicy delegatedAuthPolicy = null; @@ -168,7 +166,6 @@ public RegisteredService toRegisteredService(final WAClientApp clientApp) { tgtExpirationPolicy, stExpirationPolicy, tgtProxyExpirationPolicy, - stProxyExpirationPolicy, - properties); + stProxyExpirationPolicy); } } diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPClientAppTOMapper.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPClientAppTOMapper.java index 4510592800..97cec97d6f 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPClientAppTOMapper.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPClientAppTOMapper.java @@ -23,7 +23,6 @@ import org.apache.syncope.common.lib.to.ClientAppTO; import org.apache.syncope.common.lib.to.SAML2SPClientAppTO; import org.apache.syncope.common.lib.wa.WAClientApp; -import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAccessStrategy; import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy; @@ -53,8 +52,7 @@ public RegisteredService map( final RegisteredServiceTicketGrantingTicketExpirationPolicy tgtExpirationPolicy, final RegisteredServiceServiceTicketExpirationPolicy stExpirationPolicy, final RegisteredServiceProxyGrantingTicketExpirationPolicy tgtProxyExpirationPolicy, - final RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy, - final CasConfigurationProperties properties) { + final RegisteredServiceProxyTicketExpirationPolicy stProxyExpirationPolicy) { SAML2SPClientAppTO sp = SAML2SPClientAppTO.class.cast(clientApp.getClientAppTO()); SamlRegisteredService service = new SamlRegisteredService(); diff --git a/wa/starter/src/main/resources/wa.properties b/wa/starter/src/main/resources/wa.properties index a0e0b6a38c..48593af3d8 100644 --- a/wa/starter/src/main/resources/wa.properties +++ b/wa/starter/src/main/resources/wa.properties @@ -82,6 +82,7 @@ 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.oauth.core.user-profile-view-type=FLAT # Disable access to the login endpoint 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 7eaff948e3..aa11214f1a 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 @@ -54,6 +54,7 @@ import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAccessStrategy; import org.apereo.cas.services.RegisteredServiceDelegatedAuthenticationPolicy; +import org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.support.saml.services.SamlRegisteredService; import org.apereo.cas.util.RandomUtils; @@ -184,7 +185,7 @@ public void addClientApp() { assertTrue(oidc.getAuthenticationPolicy().getRequiredAuthenticationHandlers().contains("TestAuthModule")); assertTrue(((AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria) oidc. getAuthenticationPolicy().getCriteria()).isTryAll()); - assertTrue(oidc.getAttributeReleasePolicy() instanceof ChainingAttributeReleasePolicy); + assertTrue(oidc.getAttributeReleasePolicy() instanceof ReturnAllowedAttributeReleasePolicy); // 5. more client with different attributes waClientApp = new WAClientApp();