[SYNCOPE-1061] Both POST and Redirect binding profiles are now supported; if both are available for a given IdP, the one to be used is configurable, with POST predefined
Project: http://git-wip-us.apache.org/repos/asf/syncope/repo Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/8be5d4d3 Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/8be5d4d3 Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/8be5d4d3 Branch: refs/heads/SYNCOPE-808 Commit: 8be5d4d347990681cb89c881f51208e7251b9d77 Parents: b292d3c Author: Francesco Chicchiriccò <ilgro...@apache.org> Authored: Wed Apr 12 17:03:46 2017 +0200 Committer: Francesco Chicchiriccò <ilgro...@apache.org> Committed: Wed Apr 12 17:03:46 2017 +0200 ---------------------------------------------------------------------- .../saml2lsp/agent/AbstractSAML2SPServlet.java | 94 ++++++++++ .../ext/saml2lsp/agent/AssertionConsumer.java | 9 +- .../syncope/ext/saml2lsp/agent/Login.java | 2 +- .../syncope/ext/saml2lsp/agent/Logout.java | 99 +++++++---- .../syncope/ext/saml2lsp/agent/Metadata.java | 2 +- .../ext/saml2lsp/agent/SAML2PostBinding.java | 51 ------ .../console/panels/SAML2IdPsDirectoryPanel.java | 2 + .../console/wizards/SAML2IdPWizardBuilder.java | 9 + .../panels/SAML2IdPsDirectoryPanel.properties | 1 + .../SAML2IdPsDirectoryPanel_it.properties | 1 + .../SAML2IdPsDirectoryPanel_pt_BR.properties | 1 + .../SAML2IdPsDirectoryPanel_ru.properties | 1 + .../apache/syncope/common/lib/SSOConstants.java | 36 ++++ .../syncope/common/lib/to/SAML2IdPTO.java | 11 ++ .../common/lib/to/SAML2ReceivedResponseTO.java | 62 +++++++ .../syncope/common/lib/to/SAML2RequestTO.java | 31 ++++ .../common/lib/types/SAML2BindingType.java | 56 ++++++ .../syncope/core/logic/SAML2IdPLogic.java | 49 ++++-- .../apache/syncope/core/logic/SAML2SPLogic.java | 171 +++++++++---------- .../syncope/core/logic/init/SAML2SPLoader.java | 5 - .../syncope/core/logic/saml2/SAML2IdPCache.java | 16 +- .../logic/saml2/SAML2IdPCallbackHandler.java | 45 ----- .../core/logic/saml2/SAML2IdPEntity.java | 23 ++- .../core/logic/saml2/SAML2ReaderWriter.java | 106 ++++++++++-- .../syncope/core/logic/saml2/SAML2Signer.java | 98 ----------- .../core/logic/saml2/SAMLSPCallbackHandler.java | 45 +++++ ext/saml2sp/persistence-api/pom.xml | 5 + .../core/persistence/api/entity/SAML2IdP.java | 5 + .../persistence/jpa/entity/JPASAML2IdP.java | 14 ++ .../java/data/SAML2IdPDataBinderImpl.java | 2 + .../common/rest/api/service/SAML2SPService.java | 16 +- .../rest/cxf/service/SAML2SPServiceImpl.java | 16 +- .../org/apache/syncope/fit/SAML2SPDetector.java | 2 +- .../apache/syncope/fit/core/SAML2ITCase.java | 2 +- 34 files changed, 701 insertions(+), 387 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java new file mode 100644 index 0000000..bd295e3 --- /dev/null +++ b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AbstractSAML2SPServlet.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.ext.saml2lsp.agent; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriBuilder; +import org.apache.cxf.helpers.IOUtils; +import org.apache.cxf.jaxrs.utils.JAXRSUtils; +import org.apache.syncope.common.lib.SSOConstants; +import org.apache.syncope.common.lib.to.SAML2ReceivedResponseTO; +import org.apache.syncope.common.lib.to.SAML2RequestTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractSAML2SPServlet extends HttpServlet { + + private static final long serialVersionUID = 7969539245875799817L; + + protected static final Logger LOG = LoggerFactory.getLogger(AbstractSAML2SPServlet.class); + + protected void prepare(final HttpServletResponse response, final SAML2RequestTO requestTO) throws IOException { + response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store"); + response.setHeader("Pragma", "no-cache"); + switch (requestTO.getBindingType()) { + case REDIRECT: + UriBuilder ub = UriBuilder.fromUri(requestTO.getIdpServiceAddress()); + ub.queryParam(SSOConstants.SAML_REQUEST, requestTO.getContent()); + ub.queryParam(SSOConstants.RELAY_STATE, requestTO.getRelayState()); + ub.queryParam(SSOConstants.SIG_ALG, requestTO.getSignAlg()); + ub.queryParam(SSOConstants.SIGNATURE, requestTO.getSignature()); + + response.setStatus(HttpServletResponse.SC_SEE_OTHER); + response.setHeader("Location", ub.build().toASCIIString()); + break; + + case POST: + default: + response.setContentType(MediaType.TEXT_HTML); + response.getWriter().write("" + + "<html xmlns=\"http://www.w3.org/1999/xhtml\">" + + " <body onLoad=\"document.forms[0].submit();\">" + + " <form action=\"" + requestTO.getIdpServiceAddress() + "\" method=\"POST\">" + + " <input type=\"hidden\" name=\"" + SSOConstants.SAML_REQUEST + "\"" + + " value=\"" + requestTO.getContent() + "\"/>" + + " <input type=\"hidden\" name=\"" + SSOConstants.RELAY_STATE + "\"" + + " value=\"" + requestTO.getRelayState() + "\"/>" + + " <input type=\"submit\" style=\"visibility: hidden;\"/>" + + " </form>" + + " </body>" + + "</html>"); + } + } + + protected SAML2ReceivedResponseTO extract(final InputStream response) throws IOException { + String strForm = IOUtils.toString(response); + MultivaluedMap<String, String> params = JAXRSUtils.getStructuredParams(strForm, "&", false, false); + + String samlResponse = URLDecoder.decode( + params.getFirst(SSOConstants.SAML_RESPONSE), StandardCharsets.UTF_8.name()); + LOG.debug("Received SAML Response: {}", samlResponse); + + String relayState = params.getFirst(SSOConstants.RELAY_STATE); + LOG.debug("Received Relay State: {}", relayState); + + SAML2ReceivedResponseTO receivedResponseTO = new SAML2ReceivedResponseTO(); + receivedResponseTO.setSamlResponse(samlResponse); + receivedResponseTO.setRelayState(relayState); + return receivedResponseTO; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AssertionConsumer.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AssertionConsumer.java b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AssertionConsumer.java index 00a853e..6407b4d 100644 --- a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AssertionConsumer.java +++ b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/AssertionConsumer.java @@ -23,22 +23,17 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.common.lib.to.SAML2LoginResponseTO; import org.apache.syncope.common.rest.api.service.SAML2SPService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @WebServlet(name = "assertionConsumer", urlPatterns = { "/saml2sp/assertion-consumer" }) -public class AssertionConsumer extends HttpServlet { +public class AssertionConsumer extends AbstractSAML2SPServlet { private static final long serialVersionUID = 968480296813639041L; - private static final Logger LOG = LoggerFactory.getLogger(AssertionConsumer.class); - @Override protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { @@ -47,7 +42,7 @@ public class AssertionConsumer extends HttpServlet { getAttribute(Constants.SYNCOPE_ANONYMOUS_CLIENT); try { SAML2LoginResponseTO responseTO = anonymous.getService(SAML2SPService.class). - validateLoginResponse(request.getInputStream()); + validateLoginResponse(extract(request.getInputStream())); request.getSession(true).setAttribute(Constants.SAML2SPJWT, responseTO.getAccessToken()); http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Login.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Login.java b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Login.java index a675ca7..e206ff7 100644 --- a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Login.java +++ b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Login.java @@ -31,7 +31,7 @@ import org.apache.syncope.common.lib.to.SAML2RequestTO; import org.apache.syncope.common.rest.api.service.SAML2SPService; @WebServlet(name = "login", urlPatterns = { "/saml2sp/login" }) -public class Login extends SAML2PostBinding { +public class Login extends AbstractSAML2SPServlet { private static final long serialVersionUID = 968480296813639041L; http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Logout.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Logout.java b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Logout.java index df4c5e5..3ad191c 100644 --- a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Logout.java +++ b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Logout.java @@ -28,17 +28,21 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.client.lib.SyncopeClientFactoryBean; +import org.apache.syncope.common.lib.SSOConstants; +import org.apache.syncope.common.lib.to.SAML2ReceivedResponseTO; import org.apache.syncope.common.lib.to.SAML2RequestTO; +import org.apache.syncope.common.lib.types.SAML2BindingType; import org.apache.syncope.common.rest.api.service.SAML2SPService; @WebServlet(name = "logout", urlPatterns = { "/saml2sp/logout" }) -public class Logout extends SAML2PostBinding { +public class Logout extends AbstractSAML2SPServlet { private static final long serialVersionUID = 3010286040376932117L; - @Override - protected void doGet(final HttpServletRequest request, final HttpServletResponse response) - throws ServletException, IOException { + private void doLogout( + final SAML2ReceivedResponseTO receivedResponse, + final HttpServletRequest request, + final HttpServletResponse response) throws ServletException, IOException { SyncopeClientFactoryBean clientFactory = (SyncopeClientFactoryBean) request.getServletContext(). getAttribute(Constants.SYNCOPE_CLIENT_FACTORY); @@ -49,12 +53,17 @@ public class Logout extends SAML2PostBinding { } SyncopeClient client = clientFactory.create(accessToken); - SAML2RequestTO requestTO = client.getService(SAML2SPService.class).createLogoutRequest( - StringUtils.substringBefore(request.getRequestURL().toString(), "/saml2sp")); + client.getService(SAML2SPService.class).validateLogoutResponse(receivedResponse); - prepare(response, requestTO); + String successURL = getServletContext().getInitParameter(Constants.CONTEXT_PARAM_LOGOUT_SUCCESS_URL); + if (successURL == null) { + request.getRequestDispatcher("logoutSuccess.jsp").forward(request, response); + } else { + response.sendRedirect(successURL); + } + request.getSession().removeAttribute(Constants.SAML2SPJWT); } catch (Exception e) { - LOG.error("While preparing logout request to IdP", e); + LOG.error("While processing authentication response from IdP", e); String errorURL = getServletContext().getInitParameter(Constants.CONTEXT_PARAM_LOGOUT_ERROR_URL); if (errorURL == null) { @@ -70,41 +79,57 @@ public class Logout extends SAML2PostBinding { } @Override - protected void doPost(final HttpServletRequest request, final HttpServletResponse response) + protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { - SyncopeClientFactoryBean clientFactory = (SyncopeClientFactoryBean) request.getServletContext(). - getAttribute(Constants.SYNCOPE_CLIENT_FACTORY); - try { - String accessToken = (String) request.getSession().getAttribute(Constants.SAML2SPJWT); - if (StringUtils.isBlank(accessToken)) { - throw new IllegalArgumentException("No access token found "); + String samlResponse = request.getParameter(SSOConstants.SAML_RESPONSE); + String relayState = request.getParameter(SSOConstants.RELAY_STATE); + if (samlResponse == null) { // prepare logout response + SyncopeClientFactoryBean clientFactory = (SyncopeClientFactoryBean) request.getServletContext(). + getAttribute(Constants.SYNCOPE_CLIENT_FACTORY); + try { + String accessToken = (String) request.getSession().getAttribute(Constants.SAML2SPJWT); + if (StringUtils.isBlank(accessToken)) { + throw new IllegalArgumentException("No access token found "); + } + + SyncopeClient client = clientFactory.create(accessToken); + SAML2RequestTO requestTO = client.getService(SAML2SPService.class).createLogoutRequest( + StringUtils.substringBefore(request.getRequestURL().toString(), "/saml2sp")); + + prepare(response, requestTO); + } catch (Exception e) { + LOG.error("While preparing logout request to IdP", e); + + String errorURL = getServletContext().getInitParameter(Constants.CONTEXT_PARAM_LOGOUT_ERROR_URL); + if (errorURL == null) { + request.setAttribute("exception", e); + request.getRequestDispatcher("logoutError.jsp").forward(request, response); + + e.printStackTrace(response.getWriter()); + } else { + response.sendRedirect(errorURL + "?errorMessage=" + + URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.name())); + } } + } else { // process REDIRECT binding logout response + SAML2ReceivedResponseTO receivedResponse = new SAML2ReceivedResponseTO(); + receivedResponse.setSamlResponse(samlResponse); + receivedResponse.setRelayState(relayState); + receivedResponse.setBindingType(SAML2BindingType.REDIRECT); - SyncopeClient client = clientFactory.create(accessToken); - client.getService(SAML2SPService.class).validateLogoutResponse(request.getInputStream()); - - String successURL = getServletContext().getInitParameter(Constants.CONTEXT_PARAM_LOGOUT_SUCCESS_URL); - if (successURL == null) { - request.getRequestDispatcher("logoutSuccess.jsp").forward(request, response); - } else { - response.sendRedirect(successURL); - } - request.getSession().removeAttribute(Constants.SAML2SPJWT); - } catch (Exception e) { - LOG.error("While processing authentication response from IdP", e); + doLogout(receivedResponse, request, response); + } + } - String errorURL = getServletContext().getInitParameter(Constants.CONTEXT_PARAM_LOGOUT_ERROR_URL); - if (errorURL == null) { - request.setAttribute("exception", e); - request.getRequestDispatcher("logoutError.jsp").forward(request, response); + @Override + protected void doPost(final HttpServletRequest request, final HttpServletResponse response) + throws ServletException, IOException { - e.printStackTrace(response.getWriter()); - } else { - response.sendRedirect(errorURL + "?errorMessage=" - + URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.name())); - } - } + // process POST binding logout response + SAML2ReceivedResponseTO receivedResponse = extract(request.getInputStream()); + receivedResponse.setBindingType(SAML2BindingType.POST); + doLogout(receivedResponse, request, response); } } http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Metadata.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Metadata.java b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Metadata.java index aaa3df6..73229f7 100644 --- a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Metadata.java +++ b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/Metadata.java @@ -47,7 +47,7 @@ public class Metadata extends HttpServlet { SAML2SPService service = anonymous.getService(SAML2SPService.class); WebClient.client(service).accept(MediaType.APPLICATION_XML_TYPE).type(MediaType.APPLICATION_XML_TYPE); Response metadataResponse = service.getMetadata( - StringUtils.substringBefore(request.getRequestURL().toString(), "/saml2sp")); + StringUtils.substringBefore(request.getRequestURL().toString(), "/saml2sp"), "saml2sp"); response.setContentType(metadataResponse.getMediaType().toString()); IOUtils.copy((InputStream) metadataResponse.getEntity(), response.getOutputStream()); http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/SAML2PostBinding.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/SAML2PostBinding.java b/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/SAML2PostBinding.java deleted file mode 100644 index 0868841..0000000 --- a/ext/saml2sp/agent/src/main/java/org/apache/syncope/ext/saml2lsp/agent/SAML2PostBinding.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.syncope.ext.saml2lsp.agent; - -import java.io.IOException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import org.apache.syncope.common.lib.to.SAML2RequestTO; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public abstract class SAML2PostBinding extends HttpServlet { - - private static final long serialVersionUID = 7969539245875799817L; - - protected static final Logger LOG = LoggerFactory.getLogger(SAML2PostBinding.class); - - protected void prepare(final HttpServletResponse response, final SAML2RequestTO requestTO) throws IOException { - response.setContentType(MediaType.TEXT_HTML); - response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store"); - response.setHeader("Pragma", "no-cache"); - response.getWriter().write("" - + "<html xmlns=\"http://www.w3.org/1999/xhtml\">" - + " <body onLoad=\"document.forms[0].submit();\">" - + " <form action=\"" + requestTO.getIdpServiceAddress() + "\" method=\"POST\">" - + " <input type=\"hidden\" name=\"SAMLRequest\" value=\"" + requestTO.getContent() + "\"/>" - + " <input type=\"hidden\" name=\"RelayState\" value=\"" + requestTO.getRelayState() + "\"/>" - + " <input type=\"submit\" style=\"visibility: hidden;\"/>" - + " </form>" - + " </body>" - + "</html>"); - } -} http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java index 2b3dd70..be93d28 100644 --- a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java +++ b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java @@ -140,6 +140,8 @@ public class SAML2IdPsDirectoryPanel extends DirectoryPanel< columns.add(new PropertyColumn<SAML2IdPTO, String>(new ResourceModel("entityID"), "entityID", "entityID")); columns.add(new BooleanPropertyColumn<SAML2IdPTO>( new ResourceModel("useDeflateEncoding"), "useDeflateEncoding", "useDeflateEncoding")); + columns.add(new PropertyColumn<SAML2IdPTO, String>( + new ResourceModel("bindingType"), "bindingType", "bindingType")); columns.add(new BooleanPropertyColumn<SAML2IdPTO>( new ResourceModel("logoutSupported"), "logoutSupported", "logoutSupported")); http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java index 333c00e..d824807 100644 --- a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java +++ b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/wizards/SAML2IdPWizardBuilder.java @@ -20,6 +20,7 @@ package org.apache.syncope.client.console.wizards; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.Predicate; @@ -27,12 +28,14 @@ import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.panels.SAML2IdPsDirectoryPanel; import org.apache.syncope.client.console.rest.SAML2IdPsRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel; +import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel; import org.apache.syncope.client.console.wizards.resources.JEXLTransformersTogglePanel; import org.apache.syncope.client.console.wizards.resources.MappingItemTransformersTogglePanel; import org.apache.syncope.common.lib.to.MappingItemTO; import org.apache.syncope.common.lib.to.SAML2IdPTO; +import org.apache.syncope.common.lib.types.SAML2BindingType; import org.apache.wicket.Component; import org.apache.wicket.PageReference; import org.apache.wicket.extensions.wizard.WizardModel; @@ -95,6 +98,12 @@ public class SAML2IdPWizardBuilder extends AjaxWizardBuilder<SAML2IdPTO> { "field", "useDeflateEncoding", new PropertyModel<Boolean>(idpTO, "useDeflateEncoding"), false); fields.add(useDeflateEncoding); + AjaxDropDownChoicePanel<SAML2BindingType> bindingType = + new AjaxDropDownChoicePanel<>("field", "bindingType", + new PropertyModel<SAML2BindingType>(idpTO, "bindingType"), false); + bindingType.setChoices(Arrays.asList(SAML2BindingType.values())); + fields.add(bindingType); + add(new ListView<Component>("fields", fields) { private static final long serialVersionUID = -9180479401817023838L; http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties ---------------------------------------------------------------------- diff --git a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties index e1ecb17..8c5125d 100644 --- a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties +++ b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.properties @@ -19,3 +19,4 @@ useDeflateEncoding=Deflate Encoding logoutSupported=Logout supported any.edit=Edit ${entityID} connObjectKeyValidation=There must be exactly one Remote Key +bindingType=Binding http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties ---------------------------------------------------------------------- diff --git a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties index 3fa90a9..d0ee370 100644 --- a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties +++ b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_it.properties @@ -19,3 +19,4 @@ useDeflateEncoding=Deflate Encoding logoutSupported=Logout supportato any.edit=Modifica ${entityID} connObjectKeyValidation=Deve essere definito esattamente una Chiave remota +bindingType=Binding http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties ---------------------------------------------------------------------- diff --git a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties index 9bb0b82..87a0c40 100644 --- a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties +++ b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_pt_BR.properties @@ -19,3 +19,4 @@ useDeflateEncoding=Deflate Encoding logoutSupported=Logout supported any.edit=Alterar ${entityID} connObjectKeyValidation=Precisa ser exatamente um Remote Key +bindingType=Binding http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties ---------------------------------------------------------------------- diff --git a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties index ba85289..479faea 100644 --- a/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties +++ b/ext/saml2sp/client-console/src/main/resources/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel_ru.properties @@ -19,3 +19,4 @@ useDeflateEncoding=Deflate Encoding logoutSupported=Logout supported any.edit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c ${entityID} connObjectKeyValidation=\u0422\u0430\u043c \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0440\u043e\u0432\u043d\u043e \u043e\u0434\u0438\u043d \u0434\u0438\u0441\u0442\u0430\u043d\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430 +bindingType=Binding http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/SSOConstants.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/SSOConstants.java b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/SSOConstants.java new file mode 100644 index 0000000..2390dde --- /dev/null +++ b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/SSOConstants.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.common.lib; + +public final class SSOConstants { + + public static final String SAML_REQUEST = "SAMLRequest"; + + public static final String SAML_RESPONSE = "SAMLResponse"; + + public static final String RELAY_STATE = "RelayState"; + + public static final String SIG_ALG = "SigAlg"; + + public static final String SIGNATURE = "Signature"; + + private SSOConstants() { + // private constructor for static utility class + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java index d22dfcd..109af15 100644 --- a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java +++ b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPTO.java @@ -29,6 +29,7 @@ import javax.xml.bind.annotation.XmlType; import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.Predicate; import org.apache.syncope.common.lib.AbstractBaseBean; +import org.apache.syncope.common.lib.types.SAML2BindingType; @XmlRootElement(name = "saml2idp") @XmlType @@ -46,6 +47,8 @@ public class SAML2IdPTO extends AbstractBaseBean implements EntityTO { private boolean useDeflateEncoding; + private SAML2BindingType bindingType; + private boolean logoutSupported; private final List<MappingItemTO> mappingItems = new ArrayList<>(); @@ -93,6 +96,14 @@ public class SAML2IdPTO extends AbstractBaseBean implements EntityTO { this.useDeflateEncoding = useDeflateEncoding; } + public SAML2BindingType getBindingType() { + return bindingType; + } + + public void setBindingType(final SAML2BindingType bindingType) { + this.bindingType = bindingType; + } + public boolean isLogoutSupported() { return logoutSupported; } http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java new file mode 100644 index 0000000..b8d82ae --- /dev/null +++ b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2ReceivedResponseTO.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.common.lib.to; + +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; +import org.apache.syncope.common.lib.AbstractBaseBean; +import org.apache.syncope.common.lib.types.SAML2BindingType; + +@XmlRootElement(name = "saml2ReceivedResponse") +@XmlType +public class SAML2ReceivedResponseTO extends AbstractBaseBean { + + private static final long serialVersionUID = 6102419133516694822L; + + private String samlResponse; + + private String relayState; + + private SAML2BindingType bindingType; + + public String getSamlResponse() { + return samlResponse; + } + + public void setSamlResponse(final String samlResponse) { + this.samlResponse = samlResponse; + } + + public String getRelayState() { + return relayState; + } + + public void setRelayState(final String relayState) { + this.relayState = relayState; + } + + public SAML2BindingType getBindingType() { + return bindingType; + } + + public void setBindingType(final SAML2BindingType bindingType) { + this.bindingType = bindingType; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2RequestTO.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2RequestTO.java b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2RequestTO.java index 136b58e..143a1b3 100644 --- a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2RequestTO.java +++ b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/to/SAML2RequestTO.java @@ -21,6 +21,7 @@ package org.apache.syncope.common.lib.to; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import org.apache.syncope.common.lib.AbstractBaseBean; +import org.apache.syncope.common.lib.types.SAML2BindingType; @XmlRootElement(name = "saml2request") @XmlType @@ -30,10 +31,16 @@ public class SAML2RequestTO extends AbstractBaseBean { private String idpServiceAddress; + private SAML2BindingType bindingType; + private String content; private String relayState; + private String signAlg; + + private String signature; + public String getIdpServiceAddress() { return idpServiceAddress; } @@ -42,6 +49,14 @@ public class SAML2RequestTO extends AbstractBaseBean { this.idpServiceAddress = idpServiceAddress; } + public SAML2BindingType getBindingType() { + return bindingType; + } + + public void setBindingType(final SAML2BindingType bindingType) { + this.bindingType = bindingType; + } + public String getContent() { return content; } @@ -58,4 +73,20 @@ public class SAML2RequestTO extends AbstractBaseBean { this.relayState = relayState; } + public String getSignAlg() { + return signAlg; + } + + public void setSignAlg(final String signAlg) { + this.signAlg = signAlg; + } + + public String getSignature() { + return signature; + } + + public void setSignature(final String signature) { + this.signature = signature; + } + } http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/types/SAML2BindingType.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/types/SAML2BindingType.java b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/types/SAML2BindingType.java new file mode 100644 index 0000000..04c0704 --- /dev/null +++ b/ext/saml2sp/common-lib/src/main/java/org/apache/syncope/common/lib/types/SAML2BindingType.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.common.lib.types; + +import javax.xml.bind.annotation.XmlEnum; + +@XmlEnum +public enum SAML2BindingType { + POST("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", 0), + REDIRECT("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", 1); + + private final String uri; + + private final int index; + + SAML2BindingType(final String uri, final int index) { + this.uri = uri; + this.index = index; + } + + public String getUri() { + return uri; + } + + public int getIndex() { + return index; + } + + public static SAML2BindingType fromUri(final String uri) { + SAML2BindingType bindingType = null; + + for (SAML2BindingType value : values()) { + if (value.getUri().equals(uri)) { + bindingType = value; + } + } + + return bindingType; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java index 7c8417e..95094e9 100644 --- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java +++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPLogic.java @@ -33,6 +33,7 @@ import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.to.MappingItemTO; import org.apache.syncope.common.lib.to.SAML2IdPTO; import org.apache.syncope.common.lib.types.ClientExceptionType; +import org.apache.syncope.common.lib.types.SAML2BindingType; import org.apache.syncope.common.lib.types.SAML2SPEntitlement; import org.apache.syncope.core.logic.saml2.SAML2ReaderWriter; import org.apache.syncope.core.logic.saml2.SAML2IdPCache; @@ -81,7 +82,8 @@ public class SAML2IdPLogic extends AbstractSAML2Logic<SAML2IdPTO> { idpTO.setLogoutSupported(idpEntity == null ? false - : idpEntity.getSLOLocation(SAMLConstants.SAML2_POST_BINDING_URI) != null); + : idpEntity.getSLOLocation(SAML2BindingType.POST) != null + || idpEntity.getSLOLocation(SAML2BindingType.REDIRECT) != null); return idpTO; } @@ -146,17 +148,27 @@ public class SAML2IdPLogic extends AbstractSAML2Logic<SAML2IdPTO> { idpTO.setEntityID(idpEntityDescriptor.getEntityID()); idpTO.setName(idpEntityDescriptor.getEntityID()); idpTO.setUseDeflateEncoding(false); + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { saml2rw.write(new OutputStreamWriter(baos), idpEntityDescriptor, false); idpTO.setMetadata(Base64.encodeBase64String(baos.toByteArray())); } + MappingItemTO connObjectKeyItem = new MappingItemTO(); connObjectKeyItem.setIntAttrName("username"); connObjectKeyItem.setExtAttrName("NameID"); idpTO.setConnObjectKeyItem(connObjectKeyItem); - result.add(idpTO); - cache.put(idpEntityDescriptor, connObjectKeyItem, false); + SAML2IdPEntity idp = cache.put(idpEntityDescriptor, connObjectKeyItem, false, SAML2BindingType.POST); + if (idp.getSSOLocation(SAML2BindingType.POST) != null) { + idpTO.setBindingType(SAML2BindingType.POST); + } else if (idp.getSSOLocation(SAML2BindingType.REDIRECT) != null) { + idpTO.setBindingType(SAML2BindingType.REDIRECT); + } else { + throw new IllegalArgumentException("Not POST nor REDIRECT artifacts supported by " + idp.getId()); + } + + result.add(idpTO); } return result; @@ -177,9 +189,9 @@ public class SAML2IdPLogic extends AbstractSAML2Logic<SAML2IdPTO> { throw e; } catch (Exception e) { LOG.error("Unexpected error while importing IdP metadata", e); - SyncopeClientException ex = SyncopeClientException.build(ClientExceptionType.InvalidEntity); - ex.getElements().add(e.getMessage()); - throw ex; + SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidEntity); + sce.getElements().add(e.getMessage()); + throw sce; } return imported; @@ -194,13 +206,28 @@ public class SAML2IdPLogic extends AbstractSAML2Logic<SAML2IdPTO> { throw new NotFoundException("SAML 2.0 IdP '" + saml2IdpTO.getKey() + "'"); } - saml2Idp = idpDAO.save(binder.update(saml2Idp, saml2IdpTO)); - SAML2IdPEntity idpEntity = cache.get(saml2Idp.getEntityID()); - if (idpEntity != null) { - idpEntity.setUseDeflateEncoding(saml2Idp.isUseDeflateEncoding()); - idpEntity.setConnObjectKeyItem(binder.getIdPTO(saml2Idp).getConnObjectKeyItem()); + if (idpEntity == null) { + try { + idpEntity = cache.put(saml2Idp); + } catch (Exception e) { + LOG.error("Unexpected error while updating {}", saml2Idp.getEntityID(), e); + SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidEntity); + sce.getElements().add(e.getMessage()); + throw sce; + } } + if (idpEntity.getSSOLocation(saml2IdpTO.getBindingType()) == null) { + SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidEntity); + sce.getElements().add(saml2IdpTO.getBindingType() + " not supported by " + saml2Idp.getEntityID()); + throw sce; + } + + saml2Idp = idpDAO.save(binder.update(saml2Idp, saml2IdpTO)); + + idpEntity.setUseDeflateEncoding(saml2Idp.isUseDeflateEncoding()); + idpEntity.setBindingType(saml2Idp.getBindingType()); + idpEntity.setConnObjectKeyItem(binder.getIdPTO(saml2Idp).getConnObjectKeyItem()); } @PreAuthorize("hasRole('" + SAML2SPEntitlement.IDP_DELETE + "')") http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java index 527d58c..61d272a 100644 --- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java +++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/SAML2SPLogic.java @@ -20,12 +20,10 @@ package org.apache.syncope.core.logic; import com.fasterxml.uuid.Generators; import com.fasterxml.uuid.impl.RandomBasedGenerator; -import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.lang.reflect.Method; -import java.net.URLDecoder; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; @@ -33,29 +31,25 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.ws.rs.core.MultivaluedMap; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; -import org.apache.cxf.helpers.IOUtils; -import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer; import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier; -import org.apache.cxf.rs.security.saml.sso.SSOConstants; import org.apache.syncope.common.lib.AbstractBaseBean; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.to.AttrTO; import org.apache.syncope.common.lib.to.SAML2RequestTO; import org.apache.syncope.common.lib.to.SAML2LoginResponseTO; import org.apache.syncope.common.lib.to.MappingItemTO; +import org.apache.syncope.common.lib.to.SAML2ReceivedResponseTO; import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.ClientExceptionType; +import org.apache.syncope.common.lib.types.SAML2BindingType; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.syncope.core.logic.saml2.SAML2ReaderWriter; import org.apache.syncope.core.logic.saml2.SAML2IdPCache; import org.apache.syncope.core.logic.saml2.SAML2IdPEntity; -import org.apache.syncope.core.logic.saml2.SAML2Signer; import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException; import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO; import org.apache.syncope.core.persistence.api.dao.NotFoundException; @@ -171,11 +165,8 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { @Autowired private SAML2ReaderWriter saml2rw; - @Autowired - private SAML2Signer saml2Signer; - @PreAuthorize("hasRole('" + StandardEntitlement.ANONYMOUS + "')") - public void getMetadata(final String spEntityID, final OutputStream os) { + public void getMetadata(final String spEntityID, final String urlContext, final OutputStream os) { check(); try { @@ -185,6 +176,7 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { SPSSODescriptor spSSODescriptor = new SPSSODescriptorBuilder().buildObject(); spSSODescriptor.setWantAssertionsSigned(true); spSSODescriptor.setAuthnRequestsSigned(true); + spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); X509KeyInfoGeneratorFactory keyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory(); keyInfoGeneratorFactory.setEmitEntityCertificate(true); @@ -195,12 +187,6 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { keyDescriptor.setKeyInfo(keyInfoGenerator.generate(loader.getCredential())); spSSODescriptor.getKeyDescriptors().add(keyDescriptor); - SingleLogoutService singleLogoutService = new SingleLogoutServiceBuilder().buildObject(); - singleLogoutService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); - singleLogoutService.setLocation(spEntityID + "saml2sp/logout"); - singleLogoutService.setResponseLocation(spEntityID + "saml2sp/logout"); - spSSODescriptor.getSingleLogoutServices().add(singleLogoutService); - NameIDFormat nameIDFormat = new NameIDFormatBuilder().buildObject(); nameIDFormat.setFormat(NameIDType.PERSISTENT); spSSODescriptor.getNameIDFormats().add(nameIDFormat); @@ -208,13 +194,20 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { nameIDFormat.setFormat(NameIDType.TRANSIENT); spSSODescriptor.getNameIDFormats().add(nameIDFormat); - AssertionConsumerService assertionConsumerService = new AssertionConsumerServiceBuilder().buildObject(); - assertionConsumerService.setIndex(0); - assertionConsumerService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); - assertionConsumerService.setLocation(spEntityID + "saml2sp/assertion-consumer"); - - spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService); - spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); + for (SAML2BindingType bindingType : SAML2BindingType.values()) { + AssertionConsumerService assertionConsumerService = new AssertionConsumerServiceBuilder().buildObject(); + assertionConsumerService.setIndex(bindingType.getIndex()); + assertionConsumerService.setBinding(bindingType.getUri()); + assertionConsumerService.setLocation(spEntityID + urlContext + "/assertion-consumer"); + spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService); + spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor); + + SingleLogoutService singleLogoutService = new SingleLogoutServiceBuilder().buildObject(); + singleLogoutService.setBinding(bindingType.getUri()); + singleLogoutService.setLocation(spEntityID + urlContext + "/logout"); + singleLogoutService.setResponseLocation(spEntityID + urlContext + "/logout"); + spSSODescriptor.getSingleLogoutServices().add(singleLogoutService); + } spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor); @@ -270,7 +263,7 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { : "SAML 2.0 IdP '" + idpEntityID + "'"); } - if (idp.getSSOLocation(SAMLConstants.SAML2_POST_BINDING_URI) == null) { + if (idp.getSSOLocation(idp.getBindingType()) == null) { throw new IllegalArgumentException("No SingleSignOnService available for " + idp.getId()); } @@ -297,29 +290,44 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { AuthnRequest authnRequest = new AuthnRequestBuilder().buildObject(); authnRequest.setID("_" + UUID_GENERATOR.generate().toString()); - authnRequest.setAssertionConsumerServiceURL(spEntityID + "saml2sp/assertion-consumer"); authnRequest.setForceAuthn(false); authnRequest.setIsPassive(false); authnRequest.setVersion(SAMLVersion.VERSION_20); - authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI); + authnRequest.setProtocolBinding(idp.getBindingType().getUri()); authnRequest.setIssueInstant(new DateTime()); authnRequest.setIssuer(issuer); authnRequest.setNameIDPolicy(nameIDPolicy); authnRequest.setRequestedAuthnContext(requestedAuthnContext); - authnRequest.setDestination(idp.getSSOLocation(SAMLConstants.SAML2_POST_BINDING_URI).getLocation()); + authnRequest.setDestination(idp.getSSOLocation(idp.getBindingType()).getLocation()); SAML2RequestTO requestTO = new SAML2RequestTO(); requestTO.setIdpServiceAddress(authnRequest.getDestination()); + requestTO.setBindingType(idp.getBindingType()); try { - // 3. sign and encode AuthnRequest - requestTO.setContent(saml2Signer.signAndEncode(authnRequest, idp.isUseDeflateEncoding())); - - // 4. generate relay state as JWT + // 3. generate relay state as JWT Map<String, Object> claims = new HashMap<>(); claims.put(JWT_CLAIM_IDP_DEFLATE, idp.isUseDeflateEncoding()); Triple<String, String, Date> relayState = accessTokenDataBinder.generateJWT(authnRequest.getID(), JWT_RELAY_STATE_DURATION, claims); - requestTO.setRelayState(relayState.getMiddle()); + + // 4. sign and encode AuthnRequest + switch (idp.getBindingType()) { + case REDIRECT: + requestTO.setRelayState(URLEncoder.encode(relayState.getMiddle(), StandardCharsets.UTF_8.name())); + requestTO.setContent(URLEncoder.encode( + saml2rw.encode(authnRequest, true), StandardCharsets.UTF_8.name())); + requestTO.setSignAlg(URLEncoder.encode(saml2rw.getSigAlgo(), StandardCharsets.UTF_8.name())); + requestTO.setSignature(URLEncoder.encode( + saml2rw.sign(requestTO.getContent(), requestTO.getRelayState()), + StandardCharsets.UTF_8.name())); + break; + + case POST: + default: + requestTO.setRelayState(relayState.getMiddle()); + saml2rw.sign(authnRequest); + requestTO.setContent(saml2rw.encode(authnRequest, idp.isUseDeflateEncoding())); + } } catch (Exception e) { LOG.error("While generating AuthnRequest", e); SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown); @@ -397,47 +405,23 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { return result; } - private Pair<String, String> extract(final InputStream response) throws IOException { - String strForm = IOUtils.toString(response); - MultivaluedMap<String, String> params = JAXRSUtils.getStructuredParams(strForm, "&", false, false); - - String samlResponse = URLDecoder.decode( - params.getFirst(SSOConstants.SAML_RESPONSE), StandardCharsets.UTF_8.name()); - LOG.debug("Received SAML Response: {}", samlResponse); - - String relayState = params.getFirst(SSOConstants.RELAY_STATE); - LOG.debug("Received Relay State: {}", relayState); - - return Pair.of(samlResponse, relayState); - } - @PreAuthorize("hasRole('" + StandardEntitlement.ANONYMOUS + "')") - public SAML2LoginResponseTO validateLoginResponse(final InputStream response) { + public SAML2LoginResponseTO validateLoginResponse(final SAML2ReceivedResponseTO response) { check(); - // 1. extract raw SAML response and relay state - Pair<String, String> extracted; - try { - extracted = extract(response); - } catch (Exception e) { - LOG.error("While reading AuthnResponse", e); - SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown); - sce.getElements().add(e.getMessage()); - throw sce; - } - - // 2. first checks for the provided relay state - JwsJwtCompactConsumer relayState = new JwsJwtCompactConsumer(extracted.getRight()); + // 1. first checks for the provided relay state + JwsJwtCompactConsumer relayState = new JwsJwtCompactConsumer(response.getRelayState()); if (!relayState.verifySignatureWith(jwsSignatureCerifier)) { throw new IllegalArgumentException("Invalid signature found in Relay State"); } Boolean useDeflateEncoding = Boolean.valueOf( relayState.getJwtClaims().getClaim(JWT_CLAIM_IDP_DEFLATE).toString()); - // 3. parse the provided SAML response + // 2. parse the provided SAML response Response samlResponse; try { - XMLObject responseObject = saml2rw.read(true, useDeflateEncoding, extracted.getLeft()); + XMLObject responseObject = saml2rw.read( + SAML2BindingType.POST, useDeflateEncoding, response.getSamlResponse()); if (!(responseObject instanceof Response)) { throw new IllegalArgumentException("Expected " + Response.class.getName() + ", got " + responseObject.getClass().getName()); @@ -450,18 +434,18 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { throw sce; } - // 4. further checks: - // 4a. the SAML Reponse's InResponseTo + // 3. further checks: + // 3a. the SAML Reponse's InResponseTo if (!relayState.getJwtClaims().getSubject().equals(samlResponse.getInResponseTo())) { throw new IllegalArgumentException("Unmatching request ID: " + samlResponse.getInResponseTo()); } - // 4b. the SAML Response status + // 3b. the SAML Response status if (!StatusCode.SUCCESS.equals(samlResponse.getStatus().getStatusCode().getValue())) { throw new BadCredentialsException("The SAML IdP replied with " + samlResponse.getStatus().getStatusCode().getValue()); } - // 5. validate the SAML response and, if needed, decrypt the provided assertion(s) + // 4. validate the SAML response and, if needed, decrypt the provided assertion(s) SAML2IdPEntity idp = getIdP(samlResponse.getIssuer().getValue()); if (idp.getConnObjectKeyItem() == null) { throw new IllegalArgumentException("No mapping provided for SAML 2.0 IdP '" + idp.getId() + "'"); @@ -475,10 +459,10 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { throw sce; } - // 6. prepare the result: find matching user (if any) and return the received attributes + // 5. prepare the result: find matching user (if any) and return the received attributes SAML2LoginResponseTO responseTO = new SAML2LoginResponseTO(); responseTO.setIdp(idp.getId()); - responseTO.setSloSupported(idp.getSLOLocation(SAMLConstants.SAML2_POST_BINDING_URI) != null); + responseTO.setSloSupported(idp.getSLOLocation(idp.getBindingType()) != null); NameID nameID = null; String keyValue = null; @@ -541,7 +525,7 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { responseTO.setUsername(userDAO.find(matchingUsers.get(0)).getUsername()); responseTO.setNameID(nameID.getValue()); - // 7. generate JWT for further access + // 6. generate JWT for further access Map<String, Object> claims = new HashMap<>(); claims.put(JWT_CLAIM_IDP_ENTITYID, idp.getId()); claims.put(JWT_CLAIM_NAMEID_FORMAT, nameID.getFormat()); @@ -571,14 +555,14 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { if (idp == null) { throw new NotFoundException("SAML 2.0 IdP '" + idpEntityID + "'"); } - if (idp.getSLOLocation(SAMLConstants.SAML2_POST_BINDING_URI) == null) { + if (idp.getSLOLocation(idp.getBindingType()) == null) { throw new IllegalArgumentException("No SingleLogoutService available for " + idp.getId()); } // 3. create LogoutRequest LogoutRequest logoutRequest = new LogoutRequestBuilder().buildObject(); logoutRequest.setID("_" + UUID_GENERATOR.generate().toString()); - logoutRequest.setDestination(idp.getSLOLocation(SAMLConstants.SAML2_POST_BINDING_URI).getLocation()); + logoutRequest.setDestination(idp.getSLOLocation(idp.getBindingType()).getLocation()); DateTime now = new DateTime(); logoutRequest.setIssueInstant(now); @@ -599,16 +583,28 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { SAML2RequestTO requestTO = new SAML2RequestTO(); requestTO.setIdpServiceAddress(logoutRequest.getDestination()); + requestTO.setBindingType(idp.getBindingType()); try { - // 3. sign and encode LogoutRequest - requestTO.setContent(saml2Signer.signAndEncode(logoutRequest, idp.isUseDeflateEncoding())); - - // 4. generate relay state as JWT + // 3. generate relay state as JWT Map<String, Object> claims = new HashMap<>(); claims.put(JWT_CLAIM_IDP_DEFLATE, idp.isUseDeflateEncoding()); Triple<String, String, Date> relayState = accessTokenDataBinder.generateJWT(logoutRequest.getID(), JWT_RELAY_STATE_DURATION, claims); requestTO.setRelayState(relayState.getMiddle()); + + // 4. sign and encode AuthnRequest + switch (idp.getBindingType()) { + case REDIRECT: + requestTO.setContent(saml2rw.encode(logoutRequest, true)); + requestTO.setSignAlg(saml2rw.getSigAlgo()); + requestTO.setSignature(saml2rw.sign(requestTO.getContent(), requestTO.getRelayState())); + break; + + case POST: + default: + saml2rw.sign(logoutRequest); + requestTO.setContent(saml2rw.encode(logoutRequest, idp.isUseDeflateEncoding())); + } } catch (Exception e) { LOG.error("While generating LogoutRequest", e); SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown); @@ -620,7 +616,7 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { } @PreAuthorize("isAuthenticated() and not(hasRole('" + StandardEntitlement.ANONYMOUS + "'))") - public void validateLogoutResponse(final String accessToken, final InputStream response) { + public void validateLogoutResponse(final String accessToken, final SAML2ReceivedResponseTO response) { check(); // 1. fetch the current JWT used for Syncope authentication @@ -630,21 +626,11 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { } // 2. extract raw SAML response and relay state - Pair<String, String> extracted; - try { - extracted = extract(response); - } catch (Exception e) { - LOG.error("While reading LogoutResponse", e); - SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown); - sce.getElements().add(e.getMessage()); - throw sce; - } - JwsJwtCompactConsumer relayState = null; Boolean useDeflateEncoding = false; - if (StringUtils.isNotBlank(extracted.getRight())) { + if (StringUtils.isNotBlank(response.getRelayState())) { // first checks for the provided relay state, if available - relayState = new JwsJwtCompactConsumer(extracted.getRight()); + relayState = new JwsJwtCompactConsumer(response.getRelayState()); if (!relayState.verifySignatureWith(jwsSignatureCerifier)) { throw new IllegalArgumentException("Invalid signature found in Relay State"); } @@ -655,7 +641,8 @@ public class SAML2SPLogic extends AbstractSAML2Logic<AbstractBaseBean> { // 3. parse the provided SAML response LogoutResponse logoutResponse; try { - XMLObject responseObject = saml2rw.read(true, useDeflateEncoding, extracted.getLeft()); + XMLObject responseObject = saml2rw.read( + response.getBindingType(), useDeflateEncoding, response.getSamlResponse()); if (!(responseObject instanceof LogoutResponse)) { throw new IllegalArgumentException("Expected " + LogoutResponse.class.getName() + ", got " + responseObject.getClass().getName()); http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPLoader.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPLoader.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPLoader.java index c4f4507..f9a5eec 100644 --- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPLoader.java +++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/init/SAML2SPLoader.java @@ -31,7 +31,6 @@ import org.apache.syncope.core.persistence.api.SyncopeLoader; import org.apache.syncope.core.provisioning.api.EntitlementsHolder; import org.apache.syncope.common.lib.types.SAML2SPEntitlement; import org.apache.syncope.core.logic.saml2.SAML2ReaderWriter; -import org.apache.syncope.core.logic.saml2.SAML2Signer; import org.apache.syncope.core.spring.ApplicationContextProvider; import org.apache.syncope.core.spring.ResourceWithFallbackLoader; import org.apache.wss4j.common.saml.OpenSAMLUtil; @@ -64,9 +63,6 @@ public class SAML2SPLoader implements SyncopeLoader { @Autowired private SAML2ReaderWriter saml2rw; - @Autowired - private SAML2Signer signer; - private boolean inited; private KeyStore keystore; @@ -138,7 +134,6 @@ public class SAML2SPLoader implements SyncopeLoader { LOG.debug("SAML 2.0 Service Provider certificate loaded"); saml2rw.init(); - signer.init(); inited = true; } catch (Exception e) { http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java index 21e185d..a5ab6c3 100644 --- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java +++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCache.java @@ -29,6 +29,7 @@ import java.util.HashMap; import java.util.Map; import net.shibboleth.utilities.java.support.xml.XMLParserException; import org.apache.syncope.common.lib.to.MappingItemTO; +import org.apache.syncope.common.lib.types.SAML2BindingType; import org.apache.syncope.core.logic.init.SAML2SPLoader; import org.apache.syncope.core.persistence.api.entity.SAML2IdP; import org.apache.syncope.core.provisioning.api.data.SAML2IdPDataBinder; @@ -66,11 +67,14 @@ public class SAML2IdPCache { public SAML2IdPEntity put( final EntityDescriptor entityDescriptor, final MappingItemTO connObjectKeyItem, - final boolean useDeflateEncoding) + final boolean useDeflateEncoding, + final SAML2BindingType bindingType) throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { - return cache.put(entityDescriptor.getEntityID(), - new SAML2IdPEntity(entityDescriptor, connObjectKeyItem, useDeflateEncoding, loader.getKeyPass())); + SAML2IdPEntity idp = new SAML2IdPEntity( + entityDescriptor, connObjectKeyItem, useDeflateEncoding, bindingType, loader.getKeyPass()); + cache.put(entityDescriptor.getEntityID(), idp); + return idp; } @Transactional(readOnly = true) @@ -81,7 +85,11 @@ public class SAML2IdPCache { Element element = OpenSAMLUtil.getParserPool().parse( new InputStreamReader(new ByteArrayInputStream(idp.getMetadata()))).getDocumentElement(); EntityDescriptor entityDescriptor = (EntityDescriptor) OpenSAMLUtil.fromDom(element); - return put(entityDescriptor, binder.getIdPTO(idp).getConnObjectKeyItem(), idp.isUseDeflateEncoding()); + return put( + entityDescriptor, + binder.getIdPTO(idp).getConnObjectKeyItem(), + idp.isUseDeflateEncoding(), + idp.getBindingType()); } public SAML2IdPEntity remove(final String entityID) { http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCallbackHandler.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCallbackHandler.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCallbackHandler.java deleted file mode 100644 index 17cf6f0..0000000 --- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPCallbackHandler.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.syncope.core.logic.saml2; - -import java.io.IOException; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.UnsupportedCallbackException; -import org.apache.wss4j.common.ext.WSPasswordCallback; - -public class SAML2IdPCallbackHandler implements CallbackHandler { - - private final String keyPass; - - public SAML2IdPCallbackHandler(final String keyPass) { - this.keyPass = keyPass; - } - - @Override - public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException { - for (Callback callback : callbacks) { - if (callback instanceof WSPasswordCallback) { - WSPasswordCallback wspc = (WSPasswordCallback) callback; - wspc.setPassword(keyPass); - } - } - } - -} http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java index 35eacaf..07b4f44 100644 --- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java +++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2IdPEntity.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Map; import org.apache.commons.codec.binary.Base64; import org.apache.syncope.common.lib.to.MappingItemTO; +import org.apache.syncope.common.lib.types.SAML2BindingType; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.metadata.Endpoint; import org.opensaml.saml.saml2.metadata.EntityDescriptor; @@ -52,6 +53,8 @@ public class SAML2IdPEntity { private boolean useDeflateEncoding; + private SAML2BindingType bindingType; + private MappingItemTO connObjectKeyItem; private final Map<String, Endpoint> ssoBindings = new HashMap<>(); @@ -66,12 +69,14 @@ public class SAML2IdPEntity { final EntityDescriptor entityDescriptor, final MappingItemTO connObjectKeyItem, final boolean useDeflateEncoding, + final SAML2BindingType bindingType, final String keyPass) throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { this.id = entityDescriptor.getEntityID(); this.connObjectKeyItem = connObjectKeyItem; this.useDeflateEncoding = useDeflateEncoding; + this.bindingType = bindingType; IDPSSODescriptor idpdescriptor = entityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS); @@ -121,13 +126,21 @@ public class SAML2IdPEntity { } public boolean isUseDeflateEncoding() { - return useDeflateEncoding; + return bindingType == SAML2BindingType.REDIRECT ? true : useDeflateEncoding; } public void setUseDeflateEncoding(final boolean useDeflateEncoding) { this.useDeflateEncoding = useDeflateEncoding; } + public SAML2BindingType getBindingType() { + return bindingType; + } + + public void setBindingType(final SAML2BindingType bindingType) { + this.bindingType = bindingType; + } + public MappingItemTO getConnObjectKeyItem() { return connObjectKeyItem; } @@ -136,12 +149,12 @@ public class SAML2IdPEntity { this.connObjectKeyItem = connObjectKeyItem; } - public Endpoint getSSOLocation(final String binding) { - return ssoBindings.get(binding); + public Endpoint getSSOLocation(final SAML2BindingType bindingType) { + return ssoBindings.get(bindingType.getUri()); } - public Endpoint getSLOLocation(final String binding) { - return sloBindings.get(binding); + public Endpoint getSLOLocation(final SAML2BindingType bindingType) { + return sloBindings.get(bindingType.getUri()); } public boolean supportsNameIDFormat(final String nameIDFormat) { http://git-wip-us.apache.org/repos/asf/syncope/blob/8be5d4d3/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java ---------------------------------------------------------------------- diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java index baa3882..11e83cf 100644 --- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java +++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java @@ -19,14 +19,19 @@ package org.apache.syncope.core.logic.saml2; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; -import java.net.URLDecoder; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.SignatureException; import java.util.zip.DataFormatException; import javax.xml.XMLConstants; import javax.xml.stream.XMLStreamException; @@ -40,12 +45,20 @@ import org.apache.commons.codec.binary.Base64; import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder; import org.apache.cxf.rs.security.saml.sso.SAMLProtocolResponseValidator; import org.apache.cxf.staxutils.StaxUtils; +import org.apache.syncope.common.lib.SSOConstants; +import org.apache.syncope.common.lib.types.SAML2BindingType; import org.apache.syncope.core.logic.init.SAML2SPLoader; import org.apache.wss4j.common.crypto.Merlin; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.saml.OpenSAMLUtil; import org.opensaml.core.xml.XMLObject; +import org.opensaml.saml.common.SignableSAMLObject; +import org.opensaml.saml.saml2.core.RequestAbstractType; import org.opensaml.saml.saml2.core.Response; +import org.opensaml.security.SecurityException; +import org.opensaml.xmlsec.keyinfo.KeyInfoGenerator; +import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; +import org.opensaml.xmlsec.signature.support.SignatureConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -71,15 +84,37 @@ public class SAML2ReaderWriter { @Autowired private SAML2SPLoader loader; + private KeyInfoGenerator keyInfoGenerator; + + private String sigAlgo; + + private String jceSigAlgo; + private SAMLProtocolResponseValidator protocolValidator; - private SAML2IdPCallbackHandler callbackHandler; + private SAMLSPCallbackHandler callbackHandler; public void init() { + X509KeyInfoGeneratorFactory keyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory(); + keyInfoGeneratorFactory.setEmitEntityCertificate(true); + keyInfoGenerator = keyInfoGeneratorFactory.newInstance(); + + sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1; + jceSigAlgo = "SHA1withRSA"; + String pubKeyAlgo = loader.getCredential().getPublicKey().getAlgorithm(); + if (pubKeyAlgo.equalsIgnoreCase("DSA")) { + sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_DSA_SHA1; + jceSigAlgo = "SHA1withDSA"; + } + protocolValidator = new SAMLProtocolResponseValidator(); protocolValidator.setKeyInfoMustBeAvailable(true); - callbackHandler = new SAML2IdPCallbackHandler(loader.getKeyPass()); + callbackHandler = new SAMLSPCallbackHandler(loader.getKeyPass()); + } + + public String getSigAlgo() { + return sigAlgo; } public void write(final Writer writer, final XMLObject object, final boolean signObject) @@ -91,18 +126,12 @@ public class SAML2ReaderWriter { transformer.transform(source, streamResult); } - public XMLObject read(final boolean postBinding, final boolean useDeflateEncoding, final String response) + public XMLObject read(final SAML2BindingType bindingType, final boolean useDeflateEncoding, final String response) throws DataFormatException, UnsupportedEncodingException, XMLStreamException, WSSecurityException { - String decodedResponse = response; - // URL Decoding only applies for the redirect binding - if (!postBinding) { - decodedResponse = URLDecoder.decode(response, StandardCharsets.UTF_8.name()); - } - InputStream tokenStream; - byte[] deflatedToken = Base64.decodeBase64(decodedResponse); - tokenStream = !postBinding && useDeflateEncoding + byte[] deflatedToken = Base64.decodeBase64(response); + tokenStream = bindingType != SAML2BindingType.POST && useDeflateEncoding ? new DeflateEncoderDecoder().inflateToken(deflatedToken) : new ByteArrayInputStream(deflatedToken); @@ -125,6 +154,58 @@ public class SAML2ReaderWriter { return responseObject; } + public void sign(final RequestAbstractType request) throws SecurityException { + org.opensaml.xmlsec.signature.Signature signature = OpenSAMLUtil.buildSignature(); + signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + signature.setSignatureAlgorithm(sigAlgo); + signature.setSigningCredential(loader.getCredential()); + signature.setKeyInfo(keyInfoGenerator.generate(loader.getCredential())); + + SignableSAMLObject signableObject = (SignableSAMLObject) request; + signableObject.setSignature(signature); + signableObject.releaseDOM(); + signableObject.releaseChildrenDOM(true); + } + + public String sign(final String request, final String relayState) + throws NoSuchAlgorithmException, WSSecurityException, InvalidKeyException, UnsupportedEncodingException, + SignatureException { + + Merlin crypto = new Merlin(); + crypto.setKeyStore(loader.getKeyStore()); + PrivateKey privateKey = crypto.getPrivateKey(loader.getCredential().getPublicKey(), callbackHandler); + + java.security.Signature signature = java.security.Signature.getInstance(jceSigAlgo); + signature.initSign(privateKey); + + String requestToSign = + SSOConstants.SAML_REQUEST + "=" + request + "&" + + SSOConstants.RELAY_STATE + "=" + relayState + "&" + + SSOConstants.SIG_ALG + "=" + URLEncoder.encode(sigAlgo, StandardCharsets.UTF_8.name()); + signature.update(requestToSign.getBytes(StandardCharsets.UTF_8)); + return Base64.encodeBase64String(signature.sign()); + } + + public String encode(final RequestAbstractType request, final boolean useDeflateEncoding) + throws WSSecurityException, TransformerException, IOException { + + StringWriter writer = new StringWriter(); + write(writer, request, true); + writer.close(); + + String requestMessage = writer.toString(); + byte[] deflatedBytes; + // not correct according to the spec but required by some IdPs. + if (useDeflateEncoding) { + deflatedBytes = new DeflateEncoderDecoder(). + deflateToken(requestMessage.getBytes(StandardCharsets.UTF_8)); + } else { + deflatedBytes = requestMessage.getBytes(StandardCharsets.UTF_8); + } + + return Base64.encodeBase64String(deflatedBytes); + } + public void validate(final Response samlResponse, final KeyStore idpTrustStore) throws WSSecurityException { // validate the SAML response and, if needed, decrypt the provided assertion(s) Merlin crypto = new Merlin(); @@ -145,5 +226,4 @@ public class SAML2ReaderWriter { } } } - }