diff --git a/components/org.wso2.carbon.identity.sso.saml.common/pom.xml b/components/org.wso2.carbon.identity.sso.saml.common/pom.xml index 3592e4cf5..32927560a 100644 --- a/components/org.wso2.carbon.identity.sso.saml.common/pom.xml +++ b/components/org.wso2.carbon.identity.sso.saml.common/pom.xml @@ -32,18 +32,6 @@ http://www.wso2.com - - org.wso2.carbon.identity.framework - org.wso2.carbon.identity.core - - - org.wso2.carbon - org.wso2.carbon.ui - - - org.wso2.carbon.identity.framework - org.wso2.carbon.identity.base - org.wso2.orbit.org.opensaml opensaml @@ -52,40 +40,6 @@ org.wso2.carbon org.wso2.carbon.logging - - org.wso2.carbon.identity.inbound.auth.saml2 - org.wso2.carbon.identity.sso.saml.stub - - - org.apache.httpcomponents.wso2 - httpcore - - - org.testng - testng - test - - - org.powermock - powermock-module-testng - test - - - org.powermock - powermock-api-mockito - test - - - org.jacoco - org.jacoco.agent - runtime - test - - - org.slf4j - slf4j-api - test - @@ -100,13 +54,6 @@ ${project.artifactId} javax.servlet.http; version="${imp.pkg.version.javax.servlet}", - org.wso2.carbon.ui.util; version="${carbon.kernel.package.import.version.range}", - org.wso2.carbon.identity.base; - version="${carbon.identity.framework.imp.pkg.version.range}", - org.wso2.carbon.identity.core.util; - version="${carbon.identity.framework.imp.pkg.version.range}", - org.wso2.carbon.identity.sso.saml.stub.types; - version="${identity.inbound.auth.saml.imp.pkg.version.range}", org.apache.commons.logging; version="${commons-logging.osgi.version.range}", @@ -116,67 +63,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - ${maven.surefire.plugin.version} - - - src/test/resources/testng.xml - - - - - org.jacoco - jacoco-maven-plugin - ${jacoco.version} - - - default-prepare-agent - - prepare-agent - - - - default-prepare-agent-integration - - prepare-agent-integration - - - - default-report - - report - - - - default-report-integration - - report-integration - - - - default-check - - check - - - - - BUNDLE - - - COMPLEXITY - COVEREDRATIO - - - - - - - - - diff --git a/components/org.wso2.carbon.identity.sso.saml.common/src/main/java/org/wso2/carbon/identity/sso/saml/common/SAMLSSOProviderConstants.java b/components/org.wso2.carbon.identity.sso.saml.common/src/main/java/org/wso2/carbon/identity/sso/saml/common/SAMLSSOProviderConstants.java index 8fde54aeb..7bb1362e3 100644 --- a/components/org.wso2.carbon.identity.sso.saml.common/src/main/java/org/wso2/carbon/identity/sso/saml/common/SAMLSSOProviderConstants.java +++ b/components/org.wso2.carbon.identity.sso.saml.common/src/main/java/org/wso2/carbon/identity/sso/saml/common/SAMLSSOProviderConstants.java @@ -62,6 +62,10 @@ public class SAMLSSOProviderConstants { public static final String LOGIN_PAGE = "customLoginPage"; + //Front Channel Logout Methods + public static final String HTTP_REDIRECT_BINDING = "HTTPRedirectBinding"; + public static final String HTTP_POST_BINDING = "HTTPPostBinding"; + private SAMLSSOProviderConstants() { } diff --git a/components/org.wso2.carbon.identity.sso.saml.common/src/main/java/org/wso2/carbon/identity/sso/saml/common/Util.java b/components/org.wso2.carbon.identity.sso.saml.common/src/main/java/org/wso2/carbon/identity/sso/saml/common/Util.java deleted file mode 100644 index c1ed223f0..000000000 --- a/components/org.wso2.carbon.identity.sso.saml.common/src/main/java/org/wso2/carbon/identity/sso/saml/common/Util.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * - * Copyright (c) 2011, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. 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.wso2.carbon.identity.sso.saml.common; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.wso2.carbon.identity.base.IdentityConstants; -import org.wso2.carbon.identity.base.IdentityException; -import org.wso2.carbon.identity.core.util.IdentityUtil; -import org.wso2.carbon.identity.sso.saml.stub.types.SAMLSSOServiceProviderDTO; -import org.wso2.carbon.ui.util.CharacterEncoder; - -import javax.servlet.http.HttpServletRequest; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -public class Util { - - private static final Set UNRESERVED_CHARACTERS = new HashSet(); - private static final Log log = LogFactory.getLog(Util.class); - - static { - for (char c = 'a'; c <= 'z'; c++) - UNRESERVED_CHARACTERS.add(Character.valueOf(c)); - - for (char c = 'A'; c <= 'A'; c++) - UNRESERVED_CHARACTERS.add(Character.valueOf(c)); - - for (char c = '0'; c <= '9'; c++) - UNRESERVED_CHARACTERS.add(Character.valueOf(c)); - - UNRESERVED_CHARACTERS.add(Character.valueOf('-')); - UNRESERVED_CHARACTERS.add(Character.valueOf('.')); - UNRESERVED_CHARACTERS.add(Character.valueOf('_')); - UNRESERVED_CHARACTERS.add(Character.valueOf('~')); - } - - private static int singleLogoutRetryCount = 5; - private static long singleLogoutRetryInterval = 60000; - - private Util() { - } - - public static int getSingleLogoutRetryCount() { - return singleLogoutRetryCount; - } - - public static void setSingleLogoutRetryCount(int singleLogoutRetryCount) { - Util.singleLogoutRetryCount = singleLogoutRetryCount; - } - - public static long getSingleLogoutRetryInterval() { - return singleLogoutRetryInterval; - } - - public static void setSingleLogoutRetryInterval(long singleLogoutRetryInterval) { - Util.singleLogoutRetryInterval = singleLogoutRetryInterval; - } - - /** - * This check if the status code is 2XX, check value between 200 and 300 - * - * @param status - * @return - */ - public static boolean isHttpSuccessStatusCode(int status) { - - return status >= 200 && status < 300; - } - - public static SAMLSSOServiceProviderDTO[] doPaging(int pageNumber, - SAMLSSOServiceProviderDTO[] serviceProviderSet) { - - int itemsPerPageInt = SAMLSSOProviderConstants.DEFAULT_ITEMS_PER_PAGE; - SAMLSSOServiceProviderDTO[] returnedServiceProviderSet; - - int startIndex = pageNumber * itemsPerPageInt; - int endIndex = (pageNumber + 1) * itemsPerPageInt; - if (serviceProviderSet.length > itemsPerPageInt) { - - returnedServiceProviderSet = new SAMLSSOServiceProviderDTO[itemsPerPageInt]; - } else { - returnedServiceProviderSet = new SAMLSSOServiceProviderDTO[serviceProviderSet.length]; - } - - for (int i = startIndex, j = 0; i < endIndex && i < serviceProviderSet.length; i++, j++) { - returnedServiceProviderSet[j] = serviceProviderSet[i]; - } - - return returnedServiceProviderSet; - } - - public static SAMLSSOServiceProviderDTO[] doFilter(String filter, - SAMLSSOServiceProviderDTO[] serviceProviderSet) { - String regPattern = filter.replace("*", ".*"); - List list = new ArrayList(); - for (SAMLSSOServiceProviderDTO serviceProvider : serviceProviderSet) { - if (serviceProvider.getIssuer().toLowerCase().matches(regPattern.toLowerCase())) { - list.add(serviceProvider); - } - } - SAMLSSOServiceProviderDTO[] filteredProviders = new SAMLSSOServiceProviderDTO[list.size()]; - for (int i = 0; i < list.size(); i++) { - filteredProviders[i] = list.get(i); - - } - - return filteredProviders; - } - - /** - * This is deprecated because this repo is saml and this is a openid method. - */ - @Deprecated - public static String getUserNameFromOpenID(String openid) throws IdentityException { - String caller = null; - String path = null; - URI uri = null; - String contextPath = "/openid/"; - - try { - uri = new URI(openid); - path = uri.getPath(); - } catch (URISyntaxException e) { - throw IdentityException.error("Invalid OpenID", e); - } - caller = path.substring(path.indexOf(contextPath) + contextPath.length(), path.length()); - return caller; - } - - /** - * Find the OpenID corresponding to the given user name. - * - * @param userName User name - * @return OpenID corresponding the given user name. - * @throws org.wso2.carbon.identity.base.IdentityException - * This is deprecated because this repo is saml and this is a openid method. - */ - @Deprecated - public static String getOpenID(String userName) throws IdentityException { - return generateOpenID(userName); - } - - /** - * Generate OpenID for a given user. - * - * @param user User - * @return Generated OpenID - * @throws org.wso2.carbon.identity.base.IdentityException - */ - //This is deprecated because this repo is saml and this is a openID method - @Deprecated - public static String generateOpenID(String user) throws IdentityException { - String openIDUserUrl = null; - String openID = null; - URI uri = null; - URL url = null; - openIDUserUrl = IdentityUtil.getProperty(IdentityConstants.ServerConfig.OPENID_USER_PATTERN); - user = normalizeUrlEncoding(user); - openID = openIDUserUrl + user; - try { - uri = new URI(openID); - } catch (URISyntaxException e) { - throw IdentityException.error("Invalid OpenID URL :" + openID, e); - } - try { - url = uri.normalize().toURL(); - if (url.getQuery() != null || url.getRef() != null) { - throw IdentityException.error("Invalid user name for OpenID :" + openID); - } - } catch (MalformedURLException e) { - throw IdentityException.error("Malformed OpenID URL :" + openID, e); - } - openID = url.toString(); - return openID; - } - - //this is deprecated because this repo is saml and this is a openID method - @Deprecated - private static String normalizeUrlEncoding(String text) { - - if (text == null) - return null; - - int len = text.length(); - StringBuilder normalized = new StringBuilder(len); - - for (int i = 0; i < len; i++) { - char current = text.charAt(i); - if (current == '%' && i < len - 2) { - String percentCode = text.substring(i, i + 3).toUpperCase(); - try { - String str = URLDecoder.decode(percentCode, "ISO-8859-1"); - char chr = str.charAt(0); - if (UNRESERVED_CHARACTERS.contains(Character.valueOf(chr))) - normalized.append(chr); - else - normalized.append(percentCode); - } catch (UnsupportedEncodingException e) { - if (log.isDebugEnabled()) { - log.debug("Url Encoding not supported.", e); - } - normalized.append(percentCode); - } - i += 2; - } else { - normalized.append(current); - } - } - return normalized.toString(); - } - -} diff --git a/components/org.wso2.carbon.identity.sso.saml.common/src/test/java/org/wso2/carbon/identity/sso/saml/common/UtilTest.java b/components/org.wso2.carbon.identity.sso.saml.common/src/test/java/org/wso2/carbon/identity/sso/saml/common/UtilTest.java deleted file mode 100644 index 8fb016510..000000000 --- a/components/org.wso2.carbon.identity.sso.saml.common/src/test/java/org/wso2/carbon/identity/sso/saml/common/UtilTest.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. 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.wso2.carbon.identity.sso.saml.common; - -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import org.wso2.carbon.identity.sso.saml.stub.types.SAMLSSOServiceProviderDTO; - -import java.util.Arrays; -import java.util.List; - -import static org.testng.Assert.assertEquals; - -/** - * Test Class for the Util. - */ -public class UtilTest { - - private static int singleLogoutRetryCount = 5; - private static long singleLogoutRetryInterval = 60000; - - @Test - public void testGetSingleLogoutRetryCount() throws Exception { - - int singleLogoutRetryC = Util.getSingleLogoutRetryCount(); - assertEquals(singleLogoutRetryC, singleLogoutRetryCount); - } - - @Test - public void testSetSingleLogoutRetryCount() throws Exception { - - Util.setSingleLogoutRetryCount(6); - assertEquals(Util.getSingleLogoutRetryCount(), 6); - Util.setSingleLogoutRetryCount(singleLogoutRetryCount); - } - - @Test - public void testGetSingleLogoutRetryInterval() throws Exception { - - long singleLogoutRetryInt = Util.getSingleLogoutRetryInterval(); - assertEquals(singleLogoutRetryInt, singleLogoutRetryInterval); - } - - @Test - public void testSetSingleLogoutRetryInterval() throws Exception { - - Util.setSingleLogoutRetryInterval(70000); - assertEquals(Util.getSingleLogoutRetryInterval(), 70000); - Util.setSingleLogoutRetryInterval(singleLogoutRetryInterval); - } - - @DataProvider(name = "provideHttpStatusCode") - public Object[][] createData1() { - return new Object[][]{ - {200, true}, - {302, false}, - {100, false}, - {500, false}, - {404, false}, - {202, true}, - {0, false}, - }; - } - - @Test(dataProvider = "provideHttpStatusCode") - public void testIsHttpSuccessStatusCode(int status, boolean value) { - - assertEquals(Util.isHttpSuccessStatusCode(status), value); - } - - @DataProvider(name = "provideServiceProvider") - public Object[][] createServiceProvider() { - - SAMLSSOServiceProviderDTO SP1 = new SAMLSSOServiceProviderDTO(); - SP1.setIssuer("test1"); - SAMLSSOServiceProviderDTO SP2 = new SAMLSSOServiceProviderDTO(); - SP2.setIssuer("test2="); - SAMLSSOServiceProviderDTO SP3 = new SAMLSSOServiceProviderDTO(); - SP3.setIssuer("test3"); - SAMLSSOServiceProviderDTO SP4 = new SAMLSSOServiceProviderDTO(); - SP4.setIssuer("test4"); - SAMLSSOServiceProviderDTO SP5 = new SAMLSSOServiceProviderDTO(); - SP5.setIssuer("test5="); - SAMLSSOServiceProviderDTO SP6 = new SAMLSSOServiceProviderDTO(); - SP6.setIssuer("test6="); - SAMLSSOServiceProviderDTO[] serviceProviderSet1 = new SAMLSSOServiceProviderDTO[]{SP1, SP2, SP3}; - SAMLSSOServiceProviderDTO[] serviceProviderSet1pattern = new SAMLSSOServiceProviderDTO[]{SP2}; - SAMLSSOServiceProviderDTO[] serviceProviderSet2 = new SAMLSSOServiceProviderDTO[]{SP1, SP2, SP3, SP4, SP5, SP6}; - SAMLSSOServiceProviderDTO[] serviceProviderSet2pattern = new SAMLSSOServiceProviderDTO[]{SP2, SP5, SP6}; - - return new Object[][]{ - {serviceProviderSet1, serviceProviderSet1pattern}, - {serviceProviderSet2, serviceProviderSet2pattern}}; - } - - @Test(dataProvider = "provideServiceProvider") - public void testDoPaging(SAMLSSOServiceProviderDTO[] serviceProviderSet, - SAMLSSOServiceProviderDTO[] serviceProviderSetpattern) throws Exception { - - SAMLSSOServiceProviderDTO[] returnServiceProviderSet = Util.doPaging(0, serviceProviderSet); - Assert.assertTrue(assertSSOproviderArray(returnServiceProviderSet, serviceProviderSet)); - } - - @Test(dataProvider = "provideServiceProvider") - public void testDoFilter(SAMLSSOServiceProviderDTO[] serviceProviderSet, - SAMLSSOServiceProviderDTO[] serviceProviderSetpattern) throws Exception { - - SAMLSSOServiceProviderDTO[] returnServiceProviderSet = - Util.doFilter("^([A-Za-z0-9+/])*=$", serviceProviderSet); - Assert.assertTrue(assertSSOproviderArray(returnServiceProviderSet, serviceProviderSetpattern)); - } - - public boolean assertSSOproviderArray(SAMLSSOServiceProviderDTO[] actual, SAMLSSOServiceProviderDTO[] expected) { - - SAMLSSOServiceProviderDTO[] expectedaArray = Arrays.copyOfRange(expected, 0, actual.length); - return Arrays.deepEquals(actual, expectedaArray); - } - -} diff --git a/components/org.wso2.carbon.identity.sso.saml.common/src/test/resources/testng.xml b/components/org.wso2.carbon.identity.sso.saml.common/src/test/resources/testng.xml deleted file mode 100644 index f06fabd9c..000000000 --- a/components/org.wso2.carbon.identity.sso.saml.common/src/test/resources/testng.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - diff --git a/components/org.wso2.carbon.identity.sso.saml.stub/src/main/resources/IdentitySAMLSSOConfigService.wsdl b/components/org.wso2.carbon.identity.sso.saml.stub/src/main/resources/IdentitySAMLSSOConfigService.wsdl index 3323e2722..a68dc6894 100644 --- a/components/org.wso2.carbon.identity.sso.saml.stub/src/main/resources/IdentitySAMLSSOConfigService.wsdl +++ b/components/org.wso2.carbon.identity.sso.saml.stub/src/main/resources/IdentitySAMLSSOConfigService.wsdl @@ -1,21 +1,21 @@ - + IdentitySAMLSSOConfigService - + - + - + @@ -26,36 +26,36 @@ - + - - - + - + - + - + - + - + - + + + - + - + @@ -207,6 +207,7 @@ + @@ -215,6 +216,7 @@ + @@ -236,7 +238,7 @@ - + @@ -248,11 +250,11 @@ - + - + @@ -847,4 +849,4 @@ - + \ No newline at end of file diff --git a/components/org.wso2.carbon.identity.sso.saml.stub/src/main/resources/IdentitySAMLSSOService.wsdl b/components/org.wso2.carbon.identity.sso.saml.stub/src/main/resources/IdentitySAMLSSOService.wsdl index a280a99d4..fcd116863 100644 --- a/components/org.wso2.carbon.identity.sso.saml.stub/src/main/resources/IdentitySAMLSSOService.wsdl +++ b/components/org.wso2.carbon.identity.sso.saml.stub/src/main/resources/IdentitySAMLSSOService.wsdl @@ -1,130 +1,135 @@ - + IdentitySAMLSSOService - + - + - + + + + + + - + - + - - - + - + - - - - - - + - + - - - + - + - + + + - + - + - + - + + + + + + + - + - + - + - + - - - + - + - + - + + + + + + + + - + - + - + - - - - - + - + @@ -138,84 +143,64 @@ - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + @@ -225,56 +210,115 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + - - + + + + + + + + + + + @@ -284,30 +328,21 @@ - - - - - - - - - - - - - + + + + - - + + @@ -318,8 +353,8 @@ - - + + @@ -327,8 +362,8 @@ - - + + @@ -339,8 +374,8 @@ - - + + @@ -360,8 +395,8 @@ - - + + @@ -375,8 +410,8 @@ - - + + @@ -387,8 +422,8 @@ - - + + @@ -396,8 +431,8 @@ - - + + @@ -408,8 +443,8 @@ - - + + @@ -429,8 +464,8 @@ - - + + @@ -444,8 +479,8 @@ - - + + @@ -453,8 +488,8 @@ - - + + @@ -462,8 +497,8 @@ - - + + @@ -471,8 +506,8 @@ - - + + @@ -489,8 +524,8 @@ - - + + diff --git a/components/org.wso2.carbon.identity.sso.saml.ui/pom.xml b/components/org.wso2.carbon.identity.sso.saml.ui/pom.xml index e5ed9180c..25d07148b 100644 --- a/components/org.wso2.carbon.identity.sso.saml.ui/pom.xml +++ b/components/org.wso2.carbon.identity.sso.saml.ui/pom.xml @@ -82,6 +82,10 @@ org.wso2.carbon.identity.metadata.saml2 org.wso2.carbon.identity.idp.metadata.saml2 + + org.wso2.carbon.identity.inbound.auth.saml2 + org.wso2.carbon.identity.sso.saml.common + diff --git a/components/org.wso2.carbon.identity.sso.saml.ui/src/main/java/org/wso2/carbon/identity/sso/saml/ui/SAMLSSOUIConstants.java b/components/org.wso2.carbon.identity.sso.saml.ui/src/main/java/org/wso2/carbon/identity/sso/saml/ui/SAMLSSOUIConstants.java index 1df361b71..ce72bbac8 100644 --- a/components/org.wso2.carbon.identity.sso.saml.ui/src/main/java/org/wso2/carbon/identity/sso/saml/ui/SAMLSSOUIConstants.java +++ b/components/org.wso2.carbon.identity.sso.saml.ui/src/main/java/org/wso2/carbon/identity/sso/saml/ui/SAMLSSOUIConstants.java @@ -63,6 +63,8 @@ public class SAMLSSOUIConstants { public static final String SESSION_ATTRIBUTE_NAME_APPLICATION_CERTIFICATE = "applicationCertificate"; public static final String ENABLE_SAML2_ECP = "enableSAML2ECP"; + public static final String SLO_TYPE = "singleLogoutType"; + private SAMLSSOUIConstants() { } } diff --git a/components/org.wso2.carbon.identity.sso.saml.ui/src/main/java/org/wso2/carbon/identity/sso/saml/ui/SAMLSSOUIUtil.java b/components/org.wso2.carbon.identity.sso.saml.ui/src/main/java/org/wso2/carbon/identity/sso/saml/ui/SAMLSSOUIUtil.java index 1fb51d141..df1b579c8 100644 --- a/components/org.wso2.carbon.identity.sso.saml.ui/src/main/java/org/wso2/carbon/identity/sso/saml/ui/SAMLSSOUIUtil.java +++ b/components/org.wso2.carbon.identity.sso.saml.ui/src/main/java/org/wso2/carbon/identity/sso/saml/ui/SAMLSSOUIUtil.java @@ -18,6 +18,7 @@ package org.wso2.carbon.identity.sso.saml.ui; +import org.wso2.carbon.identity.sso.saml.common.SAMLSSOProviderConstants; import org.wso2.carbon.identity.sso.saml.stub.types.SAMLSSOServiceProviderDTO; import java.util.ArrayList; @@ -136,6 +137,42 @@ public static boolean isSingleLogoutEnabled(boolean isSpEdit, SAMLSSOServiceProv return false; } + /** + * Check front-Channel logout enable and if not enable return false. + * @param isSpEdit Operation on service provider, create or edit. + * @param provider SAML2 service provider configuration. + * @return boolean true if front channel logout enabled. + */ + public static boolean isFrontChannelLogoutEnabled(boolean isSpEdit, SAMLSSOServiceProviderDTO provider) { + + return (isSpEdit && provider != null && provider.getDoFrontChannelLogout()); + } + + /** + * Check front-Channel logout HTTP Redirect Binding enable and if not enable return false. + * @param isSpEdit Operation on service provider, create or edit. + * @param provider SAML2 service provider configuration. + * @return boolean true if redirect binding enabled. + */ + public static boolean isHTTPRedirectBindingEnabled(boolean isSpEdit, SAMLSSOServiceProviderDTO provider) { + + return (isSpEdit && provider != null && SAMLSSOProviderConstants.HTTP_REDIRECT_BINDING.equals + (provider.getFrontChannelLogoutBinding())); + + } + + /** + * Check front-Channel logout HTTP Post Binding enable and if not enable return false. + * @param isSpEdit Operation on service provider, create or edit. + * @param provider SAML2 service provider configuration + * @return boolean true if post binding enabled. + */ + public static boolean isHTTPPostBindingEnabled(boolean isSpEdit, SAMLSSOServiceProviderDTO provider) { + + return (isSpEdit && provider != null && SAMLSSOProviderConstants.HTTP_POST_BINDING.equals + (provider.getFrontChannelLogoutBinding())) ; + } + public static boolean isAttributeProfileEnabled(boolean isSpEdit, SAMLSSOServiceProviderDTO provider) { if (isSpEdit) { diff --git a/components/org.wso2.carbon.identity.sso.saml.ui/src/main/resources/org/wso2/carbon/identity/sso/saml/ui/i18n/Resources.properties b/components/org.wso2.carbon.identity.sso.saml.ui/src/main/resources/org/wso2/carbon/identity/sso/saml/ui/i18n/Resources.properties index edcff928a..e7db587cc 100644 --- a/components/org.wso2.carbon.identity.sso.saml.ui/src/main/resources/org/wso2/carbon/identity/sso/saml/ui/i18n/Resources.properties +++ b/components/org.wso2.carbon.identity.sso.saml.ui/src/main/resources/org/wso2/carbon/identity/sso/saml/ui/i18n/Resources.properties @@ -115,3 +115,7 @@ sp.saml.metadata.certificate.warn=Since this the super tenant, Keystore is manag sp.enable.saml2.artifact.binding=Enable SAML2 Artifact Binding sp.enable.signature.validation.artifact.resolve=Enable Signature Validation in Artifact Resolve Request enable.saml2.ecp=Enable SAML Enhanced Client or Proxy (ECP) +enable.front.channel.http.redirect.binding = Front-Channel Logout (HTTP Redirect Binding) +enable.front.channel.http.post.binding = Front-Channel Logout (HTTP POST Binding) +enable.back.channel.logout = Back-Channel Logout +single.logout.type = Logout Method diff --git a/components/org.wso2.carbon.identity.sso.saml.ui/src/main/resources/web/sso-saml/add_service_provider.jsp b/components/org.wso2.carbon.identity.sso.saml.ui/src/main/resources/web/sso-saml/add_service_provider.jsp index b87c8133f..d7a0aca76 100644 --- a/components/org.wso2.carbon.identity.sso.saml.ui/src/main/resources/web/sso-saml/add_service_provider.jsp +++ b/components/org.wso2.carbon.identity.sso.saml.ui/src/main/resources/web/sso-saml/add_service_provider.jsp @@ -127,15 +127,25 @@ } - function disableLogoutUrl(chkbx) { + function disableSingleLogout(chkbx) { if ($(chkbx).is(':checked')) { + if(!$("#enableFrontChannelHTTPRedirectBinding").is(':checked') && !$("#enableFrontChannelHTTPPostBinding").is(':checked')) { + $("#enableBackChannelLogout").prop('checked',true); + } $("#sloResponseURL").prop('disabled', false); $("#sloRequestURL").prop('disabled', false); + $("#enableBackChannelLogout").prop('disabled',false); + $("#enableFrontChannelHTTPRedirectBinding").prop('disabled', false); + $("#enableFrontChannelHTTPPostBinding").prop('disabled', false); } else { $("#sloResponseURL").prop('disabled', true); $("#sloRequestURL").prop('disabled', true); + $("#enableBackChannelLogout").prop('disabled',true); + $("#enableFrontChannelHTTPRedirectBinding").prop('disabled', true); + $("#enableFrontChannelHTTPPostBinding").prop('disabled', true); $("#sloResponseURL").val(""); $("#sloRequestURL").val(""); + } } @@ -1271,11 +1281,14 @@ /> + onclick="disableSingleLogout(this);" + <%= isSingleLogoutEnabled(isEditSP, provider) ? "checked": ""%> + /> + + @@ -1284,7 +1297,8 @@ " - class="text-box-big" <%=(isEditSP && provider.getDoSingleLogout()) ? "" : "disabled=\"disabled\""%>> + class="text-box-big" <%=isSingleLogoutEnabled(isEditSP, provider) ? "" : "disabled=\"disabled\""%>> +
Single logout response accepting endpoint
@@ -1298,12 +1312,51 @@ " - class="text-box-big" <%=(isEditSP && provider.getDoSingleLogout()) ? "" : "disabled=\"disabled\""%>> + class="text-box-big" <%=isSingleLogoutEnabled(isEditSP, provider) ? "" : "disabled=\"disabled\""%>>
Single logout request accepting endpoint
+ + + + + + + + + + + + + + + + + + +
+ + <% boolean show = false; diff --git a/components/org.wso2.carbon.identity.sso.saml.ui/src/main/resources/web/sso-saml/add_service_provider_finish-ajaxprocessor.jsp b/components/org.wso2.carbon.identity.sso.saml.ui/src/main/resources/web/sso-saml/add_service_provider_finish-ajaxprocessor.jsp index ba5d0c1b6..f5b6dd64f 100644 --- a/components/org.wso2.carbon.identity.sso.saml.ui/src/main/resources/web/sso-saml/add_service_provider_finish-ajaxprocessor.jsp +++ b/components/org.wso2.carbon.identity.sso.saml.ui/src/main/resources/web/sso-saml/add_service_provider_finish-ajaxprocessor.jsp @@ -28,6 +28,7 @@ <%@ page import="java.util.ResourceBundle" %> <%@ page import="org.owasp.encoder.Encode" %> <%@ page import="org.wso2.carbon.identity.core.util.IdentityUtil" %> +<%@ page import="org.wso2.carbon.identity.sso.saml.common.SAMLSSOProviderConstants" %> <%@ page import="org.wso2.carbon.utils.ServerConstants" %> <%@ page import="java.util.ResourceBundle" %> +<%@ page import="org.wso2.carbon.identity.sso.saml.common.SAMLSSOProviderConstants" %> org.wso2.carbon.identity.metadata.saml2 org.wso2.carbon.identity.sp.metadata.saml2 + + org.wso2.carbon.identity.inbound.auth.saml2 + org.wso2.carbon.identity.sso.saml.common + org.testng testng diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/FrontChannelSLOParticipantInfo.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/FrontChannelSLOParticipantInfo.java new file mode 100644 index 000000000..2f10c8544 --- /dev/null +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/FrontChannelSLOParticipantInfo.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * Licensed 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.wso2.carbon.identity.sso.saml; + +import org.wso2.carbon.identity.sso.saml.cache.CacheEntry; + +/** + * Information of front-channel enabled session participants in a single logout. + */ +public class FrontChannelSLOParticipantInfo extends CacheEntry { + + private static final long serialVersionUID = -3909575392953155294L; + + private String originalIssuerLogoutRequestId; + private String originalLogoutRequestIssuer; + private String currentSLOInvokedParticipant; + private String sessionIndex; + private boolean isIdPInitSLO; + private String relayState; + private String returnToURL; + + public FrontChannelSLOParticipantInfo() { + + } + + public FrontChannelSLOParticipantInfo(String originalIssuerLogoutRequestId, String originalLogoutRequestIssuer, + String currentSLOInvokedParticipant, String sessionIndex, + boolean isIdPInitSLO, String relayState, String returnToURL) { + + this.originalIssuerLogoutRequestId = originalIssuerLogoutRequestId; + this.originalLogoutRequestIssuer = originalLogoutRequestIssuer; + this.currentSLOInvokedParticipant = currentSLOInvokedParticipant; + this.sessionIndex = sessionIndex; + this.isIdPInitSLO = isIdPInitSLO; + this.relayState = relayState; + this.returnToURL = returnToURL; + } + + public String getOriginalIssuerLogoutRequestId() { + + return originalIssuerLogoutRequestId; + } + + public void setOriginalIssuerLogoutRequestId(String originalIssuerLogoutRequestId) { + + this.originalIssuerLogoutRequestId = originalIssuerLogoutRequestId; + } + + public String getCurrentSLOInvokedParticipant() { + + return currentSLOInvokedParticipant; + } + + public void setCurrentSLOInvokedParticipant(String currentSLOInvokedParticipant) { + + this.currentSLOInvokedParticipant = currentSLOInvokedParticipant; + } + + public String getSessionIndex() { + + return sessionIndex; + } + + public void setSessionIndex(String sessionIndex) { + + this.sessionIndex = sessionIndex; + } + + public String getOriginalLogoutRequestIssuer() { + + return originalLogoutRequestIssuer; + } + + public void setOriginalLogoutRequestIssuer(String originalLogoutRequestIssuer) { + + this.originalLogoutRequestIssuer = originalLogoutRequestIssuer; + } + + public boolean isIdPInitSLO() { + + return isIdPInitSLO; + } + + public void setIdPInitSLO(boolean idPInitSLO) { + + isIdPInitSLO = idPInitSLO; + } + + public String getRelayState() { + + return relayState; + } + + public void setRelayState(String relayState) { + + this.relayState = relayState; + } + + public String getReturnToURL() { + + return returnToURL; + } + + public void setReturnToURL(String returnToURL) { + + this.returnToURL = returnToURL; + } +} diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/FrontChannelSLOParticipantStore.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/FrontChannelSLOParticipantStore.java new file mode 100644 index 000000000..9397dd626 --- /dev/null +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/FrontChannelSLOParticipantStore.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * Licensed 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.wso2.carbon.identity.sso.saml; + +import org.wso2.carbon.identity.application.authentication.framework.store.SessionDataStore; +import org.wso2.carbon.identity.application.common.cache.BaseCache; +import org.wso2.carbon.identity.core.util.IdentityUtil; + +import java.util.concurrent.TimeUnit; + +/** + * This is to store information of front-channel enabled session participants in a single logout. + * Although this is extended from BaseCache this does not act as a cache. This is implemented just to act as + * an in-memory storage. + */ +public class FrontChannelSLOParticipantStore extends BaseCache { + + private static final String CACHE_NAME = "FrontChannelSLOParticipantStore"; + private static volatile FrontChannelSLOParticipantStore instance = new FrontChannelSLOParticipantStore(); + private boolean isTemporarySessionDataPersistEnabled = false; + + private FrontChannelSLOParticipantStore() { + + super(CACHE_NAME); + if (IdentityUtil.getProperty("JDBCPersistenceManager.SessionDataPersist.Temporary") != null) { + isTemporarySessionDataPersistEnabled = Boolean.parseBoolean( + IdentityUtil.getProperty("JDBCPersistenceManager.SessionDataPersist.Temporary")); + } + } + + /** + * Return instance of FrontChannelSLOParticipantStore. + * + * @return FrontChannelSLOParticipantStore instance. + */ + public static FrontChannelSLOParticipantStore getInstance() { + + return instance; + } + + /** + * Store FrontChannelSLOParticipantInfo against logout request id of the current SLO invoked session participant. + * + * @param key Logout request id of the current SLO invoked session participant. + * @param entry FrontChannelSLOParticipantInfo. + */ + public void addToCache(String key, FrontChannelSLOParticipantInfo entry) { + + super.addToCache(key, entry); + if (isTemporarySessionDataPersistEnabled) { + long validityPeriod = TimeUnit.MINUTES.toNanos(IdentityUtil.getTempDataCleanUpTimeout()); + entry.setValidityPeriod(validityPeriod); + SessionDataStore.getInstance().storeSessionData(key, CACHE_NAME, entry); + } + } + + /** + * Retrieve FrontChannelSLOParticipantInfo from the store using logout request id of the current SLO invoked + * session participant. + * + * @param key Logout request id of the current SLO invoked session participant. + * @return FrontChannelSLOParticipantInfo. + */ + public FrontChannelSLOParticipantInfo getValueFromCache(String key) { + + FrontChannelSLOParticipantInfo cacheEntry = super.getValueFromCache(key); + if (cacheEntry == null && isTemporarySessionDataPersistEnabled) { + cacheEntry = (FrontChannelSLOParticipantInfo) SessionDataStore.getInstance(). + getSessionData(key, CACHE_NAME); + } + return cacheEntry; + } + + /** + * Remove FrontChannelSLOParticipantInfo from the store for the given logout request id. + * + * @param key Logout request id of the current SLO invoked session participant. + */ + public void clearCacheEntry(String key) { + + super.clearCacheEntry(key); + if (isTemporarySessionDataPersistEnabled) { + SessionDataStore.getInstance().clearSessionData(key, CACHE_NAME); + } + } +} diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOConstants.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOConstants.java index 211e49a67..f8930a7a2 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOConstants.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOConstants.java @@ -97,6 +97,8 @@ public class SAMLSSOConstants { public static final String CACHE_CONTROL_PARAM_KEY = "Cache-Control"; public static final String CACHE_CONTROL_VALUE_NO_CACHE = "no-cache"; + public static final String IS_POST = "isPost"; + private SAMLSSOConstants() { } @@ -160,6 +162,8 @@ public static class FileBasedSPConfig { public static final String USE_AUTHENTICATED_USER_DOMAIN_CRYPTO = "SSOService.UseAuthenticatedUserDomainCrypto"; public static final String RETURN_TO_URL_LIST = "ReturnToURLList"; public static final String RETURN_TO_URL = "ReturnToURL"; + public static final String FRONT_CHANNEL_LOGOUT = "EnableFrontChannelLogout"; + public static final String FRONT_CHANNEL_LOGOUT_BINDING = "FrontChannelLogoutBinding"; private FileBasedSPConfig() { } diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOService.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOService.java index 35315fa08..f41f051c9 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOService.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/SAMLSSOService.java @@ -17,7 +17,6 @@ */ package org.wso2.carbon.identity.sso.saml; -import org.apache.commons.lang.StringUtils; import org.opensaml.saml2.common.Extensions; import org.opensaml.saml2.core.AuthnRequest; import org.opensaml.saml2.core.LogoutRequest; @@ -226,8 +225,9 @@ public void doSingleLogout(String sessionId, String issuer) throws IdentityExcep String key = entry.getKey(); SAMLSSOServiceProviderDO serviceProviderDO = entry.getValue(); - // if issuer is the logout request initiator, then not sending the logout request to the issuer. - if (!key.equals(issuer) && serviceProviderDO.isDoSingleLogout()) { + // If issuer is the logout request initiator, then not sending the logout request to the issuer. + if (!key.equals(issuer) && serviceProviderDO.isDoSingleLogout() + && !serviceProviderDO.isDoFrontChannelLogout()) { SingleLogoutRequestDTO logoutReqDTO = SAMLSSOUtil.createLogoutRequestDTO(serviceProviderDO, sessionInfoData.getSubject(key), sessionIndex, rpSessionsList.get(key), serviceProviderDO.getCertAlias(), serviceProviderDO.getTenantDomain()); @@ -235,7 +235,7 @@ public void doSingleLogout(String sessionId, String issuer) throws IdentityExcep } } - //send logout requests to all session participants + // Send logout requests to all session participants. LogoutRequestSender.getInstance().sendLogoutRequests(singleLogoutReqDTOs.toArray( new SingleLogoutRequestDTO[singleLogoutReqDTOs.size()])); SAMLSSOUtil.removeSession(sessionId, issuer); diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/admin/FileBasedConfigManager.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/admin/FileBasedConfigManager.java index f141470c1..d3c479817 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/admin/FileBasedConfigManager.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/admin/FileBasedConfigManager.java @@ -34,6 +34,7 @@ import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.sso.saml.SAMLSSOConstants; import org.wso2.carbon.identity.sso.saml.SSOServiceProviderConfigManager; +import org.wso2.carbon.identity.sso.saml.common.SAMLSSOProviderConstants; import org.wso2.carbon.identity.sso.saml.util.SAMLSSOUtil; import java.io.File; @@ -158,6 +159,20 @@ private SAMLSSOServiceProviderDO[] readServiceProvidersFromFile() { .fillURLPlaceholders(getTextValue(elem, SAMLSSOConstants.FileBasedSPConfig.SLO_REQUEST_URL))); } + if ((getTextValue(elem, SAMLSSOConstants.FileBasedSPConfig.FRONT_CHANNEL_LOGOUT)) != null) { + spDO.setDoFrontChannelLogout(Boolean.valueOf(getTextValue(elem, SAMLSSOConstants.FileBasedSPConfig + .FRONT_CHANNEL_LOGOUT))); + if(spDO.isDoFrontChannelLogout()) { + if (getTextValue(elem, SAMLSSOConstants.FileBasedSPConfig.FRONT_CHANNEL_LOGOUT_BINDING) != null) { + spDO.setFrontChannelLogoutBinding(getTextValue(elem, SAMLSSOConstants.FileBasedSPConfig + .FRONT_CHANNEL_LOGOUT_BINDING)); + } else { + // Default is redirect-binding. + spDO.setFrontChannelLogoutBinding(SAMLSSOProviderConstants.HTTP_REDIRECT_BINDING); + } + } + } + if ((getTextValue(elem, SAMLSSOConstants.FileBasedSPConfig.SIGN_ASSERTION)) != null) { signAssertion = Boolean.valueOf(getTextValue(elem, SAMLSSOConstants.FileBasedSPConfig.SIGN_ASSERTION)); } diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/admin/SAMLSSOConfigAdmin.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/admin/SAMLSSOConfigAdmin.java index 40074425d..003b6693b 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/admin/SAMLSSOConfigAdmin.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/admin/SAMLSSOConfigAdmin.java @@ -192,6 +192,8 @@ private SAMLSSOServiceProviderDO createSAMLSSOServiceProviderDO(SAMLSSOServicePr serviceProviderDO.setDefaultAssertionConsumerUrl(serviceProviderDTO.getDefaultAssertionConsumerUrl()); serviceProviderDO.setCertAlias(serviceProviderDTO.getCertAlias()); serviceProviderDO.setDoSingleLogout(serviceProviderDTO.isDoSingleLogout()); + serviceProviderDO.setDoFrontChannelLogout(serviceProviderDTO.isDoFrontChannelLogout()); + serviceProviderDO.setFrontChannelLogoutBinding(serviceProviderDTO.getFrontChannelLogoutBinding()); serviceProviderDO.setSloResponseURL(serviceProviderDTO.getSloResponseURL()); serviceProviderDO.setSloRequestURL(serviceProviderDTO.getSloRequestURL()); serviceProviderDO.setLoginPageURL(serviceProviderDTO.getLoginPageURL()); @@ -282,6 +284,8 @@ private SAMLSSOServiceProviderDTO createSAMLSSOServiceProviderDTO(SAMLSSOService } serviceProviderDTO.setDoSingleLogout(serviceProviderDO.isDoSingleLogout()); + serviceProviderDTO.setDoFrontChannelLogout(serviceProviderDO.isDoFrontChannelLogout()); + serviceProviderDTO.setFrontChannelLogoutBinding(serviceProviderDO.getFrontChannelLogoutBinding()); serviceProviderDTO.setLoginPageURL(serviceProviderDO.getLoginPageURL()); serviceProviderDTO.setSloRequestURL(serviceProviderDO.getSloRequestURL()); serviceProviderDTO.setSloResponseURL(serviceProviderDO.getSloResponseURL()); @@ -358,6 +362,8 @@ public SAMLSSOServiceProviderInfoDTO getServiceProviders() throws IdentityExcept providerDTO.setDoSignResponse(providerDO.isDoSignResponse()); providerDTO.setDoSignAssertions(providerDO.isDoSignAssertions()); providerDTO.setDoSingleLogout(providerDO.isDoSingleLogout()); + providerDTO.setDoFrontChannelLogout(providerDO.isDoFrontChannelLogout()); + providerDTO.setFrontChannelLogoutBinding(providerDO.getFrontChannelLogoutBinding()); providerDTO.setAssertionQueryRequestProfileEnabled(providerDO.isAssertionQueryRequestProfileEnabled()); providerDTO.setSupportedAssertionQueryRequestTypes(providerDO.getSupportedAssertionQueryRequestTypes()); providerDTO.setEnableSAML2ArtifactBinding(providerDO.isEnableSAML2ArtifactBinding()); diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/builders/SingleLogoutMessageBuilder.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/builders/SingleLogoutMessageBuilder.java index e3c158f58..f1f0c0802 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/builders/SingleLogoutMessageBuilder.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/builders/SingleLogoutMessageBuilder.java @@ -56,29 +56,8 @@ public LogoutRequest buildLogoutRequest(String subject, String sessionId, String requestsigningAlgorithmUri, String requestDigestAlgoUri) throws IdentityException { - LogoutRequest logoutReq = new LogoutRequestBuilder().buildObject(); - - logoutReq.setID(SAMLSSOUtil.createID()); - - DateTime issueInstant = new DateTime(); - logoutReq.setIssueInstant(issueInstant); - logoutReq.setIssuer(SAMLSSOUtil.getIssuerFromTenantDomain(tenantDomain)); - logoutReq.setNotOnOrAfter(new DateTime(issueInstant.getMillis() + 5 * 60 * 1000)); - - NameID nameId = new NameIDBuilder().buildObject(); - nameId.setFormat(nameIDFormat); - nameId.setValue(subject); - logoutReq.setNameID(nameId); - - SessionIndex sessionIndex = new SessionIndexBuilder().buildObject(); - sessionIndex.setSessionIndex(sessionId); - logoutReq.getSessionIndexes().add(sessionIndex); - - if (destination != null) { - logoutReq.setDestination(destination); - } - - logoutReq.setReason(reason); + LogoutRequest logoutReq = this.buildLogoutRequest(destination, tenantDomain, sessionId, subject, nameIDFormat, + reason); int tenantId; if (StringUtils.isEmpty(tenantDomain) || "null".equals(tenantDomain)) { @@ -109,6 +88,49 @@ public LogoutRequest buildLogoutRequest(String subject, String sessionId, String return logoutReq; } + /** + * Build SAML logout request without setting the signature. + * + * @param destination Destination of the logout request. + * @param tenantDomain Tenant domain. + * @param sessionId Session index. + * @param subject Subject identifier. + * @param nameIDFormat Name Id format of the logout request. + * @param reason Single logout reason. + * @return Logout request. + * @throws IdentityException If the tenant domain is invalid. + */ + public LogoutRequest buildLogoutRequest(String destination, String tenantDomain, String sessionId, String subject, + String nameIDFormat, String reason) throws IdentityException { + + LogoutRequest logoutReq = new LogoutRequestBuilder().buildObject(); + + logoutReq.setID(SAMLSSOUtil.createID()); + + DateTime issueInstant = new DateTime(); + logoutReq.setIssueInstant(issueInstant); + logoutReq.setIssuer(SAMLSSOUtil.getIssuerFromTenantDomain(tenantDomain)); + logoutReq.setNotOnOrAfter(new DateTime(issueInstant.getMillis() + + SAMLSSOUtil.getSAMLResponseValidityPeriod() * 60 * 1000L)); + + NameID nameId = new NameIDBuilder().buildObject(); + nameId.setFormat(nameIDFormat); + nameId.setValue(subject); + logoutReq.setNameID(nameId); + + SessionIndex sessionIndex = new SessionIndexBuilder().buildObject(); + sessionIndex.setSessionIndex(sessionId); + logoutReq.getSessionIndexes().add(sessionIndex); + + if (destination != null) { + logoutReq.setDestination(destination); + } + + logoutReq.setReason(reason); + + return logoutReq; + } + public LogoutResponse buildLogoutResponse(String id, String status, String statMsg, String destination, boolean isSignResponse, String tenantDomain, String responseSigningAlgorithmUri, String responseDigestAlgoUri) throws IdentityException { diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/builders/X509CredentialImpl.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/builders/X509CredentialImpl.java index e6e9b4874..19e7b231a 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/builders/X509CredentialImpl.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/builders/X509CredentialImpl.java @@ -18,23 +18,40 @@ package org.wso2.carbon.identity.sso.saml.builders; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.opensaml.xml.security.credential.Credential; import org.opensaml.xml.security.credential.CredentialContextSet; import org.opensaml.xml.security.credential.UsageType; import org.opensaml.xml.security.x509.X509Credential; +import org.wso2.carbon.base.MultitenantConstants; +import org.wso2.carbon.base.ServerConfiguration; +import org.wso2.carbon.core.util.KeyStoreManager; +import org.wso2.carbon.identity.base.IdentityException; +import org.wso2.carbon.identity.sso.saml.util.SAMLSSOUtil; +import org.wso2.carbon.user.api.UserStoreException; -import javax.crypto.SecretKey; +import java.io.FileInputStream; +import java.io.IOException; import java.math.BigInteger; +import java.security.Key; import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPublicKeySpec; import java.util.Collection; import java.util.Collections; +import javax.crypto.SecretKey; /** * X509Credential implementation for signature verification of self issued tokens. The key is @@ -43,8 +60,185 @@ public class X509CredentialImpl implements X509Credential { private PublicKey publicKey = null; + private PrivateKey privateKey = null; private X509Certificate signingCert = null; + private static KeyStore superTenantSignKeyStore = null; + + private static Log log = LogFactory.getLog(X509CredentialImpl.class); + + private static final String SECURITY_SAML_SIGN_KEY_STORE_LOCATION = "Security.SAMLSignKeyStore.Location"; + private static final String SECURITY_SAML_SIGN_KEY_STORE_TYPE = "Security.SAMLSignKeyStore.Type"; + private static final String SECURITY_SAML_SIGN_KEY_STORE_PASSWORD = "Security.SAMLSignKeyStore.Password"; + private static final String SECURITY_SAML_SIGN_KEY_STORE_KEY_ALIAS = "Security.SAMLSignKeyStore.KeyAlias"; + private static final String SECURITY_SAML_SIGN_KEY_STORE_KEY_PASSWORD = "Security.SAMLSignKeyStore.KeyPassword"; + + /** + * Instantiates X509Credential. + * This credential object will hold the private key, public key and the cert for the respective tenant domain. + * + * @param tenantDomain tenant domain + */ + public X509CredentialImpl(String tenantDomain) throws IdentityException { + + int tenantId; + try { + tenantId = SAMLSSOUtil.getRealmService().getTenantManager().getTenantId(tenantDomain); + } catch (UserStoreException e) { + throw new IdentityException("Exception occurred while retrieving Tenant ID from tenant domain " + + tenantDomain, e); + } + + KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId); + + // Get the private key and the cert for the respective tenant domain. + if (MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { + if (isSignKeyStoreConfigured()) { + initCredentialForSuperTenantFromSignKeyStore(); + } else { + initCredentialFromSuperTenantKeyStore(keyStoreManager); + } + } else { + initCredentialForTenant(tenantDomain, keyStoreManager); + } + + if (privateKey == null) { + throw new IdentityException("Cannot find the private key for tenant " + tenantDomain); + } + + if (signingCert == null) { + throw new IdentityException("Cannot find the certificate."); + } + + publicKey = signingCert.getPublicKey(); + } + + /** + * Set private key and X509Certificate from the default KeyStore. + * + * @param keyStoreManager keyStore Manager. + * @throws IdentityException Error in retrieving private key and certificate. + */ + private void initCredentialFromSuperTenantKeyStore(KeyStoreManager keyStoreManager) + throws IdentityException { + + try { + privateKey = keyStoreManager.getDefaultPrivateKey(); + signingCert = keyStoreManager.getDefaultPrimaryCertificate(); + // This Exception is thrown from the KeyStoreManager. + } catch (Exception e) { + throw new IdentityException("Error retrieving private key and the certificate for tenant " + + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, e); + } + } + + /** + * Set private key and X509Certificate from the Sign KeyStore which is defined under Security.SAMLSignKeyStore + * in carbon.xml. + * + * @throws IdentityException Error in keystore. + */ + private void initCredentialForSuperTenantFromSignKeyStore() throws IdentityException { + + if (log.isDebugEnabled()) { + log.debug("Initializing Key Data for super tenant using separate sign key store."); + } + + try { + if (superTenantSignKeyStore == null) { + initSuperTenantSignKeyStore(); + } + + String keyAlias = ServerConfiguration.getInstance().getFirstProperty( + SECURITY_SAML_SIGN_KEY_STORE_KEY_ALIAS); + char[] keyPassword = ServerConfiguration.getInstance().getFirstProperty( + SECURITY_SAML_SIGN_KEY_STORE_KEY_PASSWORD).toCharArray(); + Key privateKey = superTenantSignKeyStore.getKey(keyAlias, keyPassword); + Certificate publicKey = superTenantSignKeyStore.getCertificate(keyAlias); + + if (privateKey instanceof PrivateKey) { + this.privateKey = (PrivateKey) privateKey; + } else { + throw new IdentityException("Configured signing KeyStore private key is invalid."); + } + + if (publicKey instanceof X509Certificate) { + this.signingCert = (X509Certificate) publicKey; + } else { + throw new IdentityException("Configured signing KeyStore X509Certificate is invalid."); + } + } catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) { + throw new IdentityException("Unable to load signing keystore for tenant " + + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, e); + } + } + + private void initSuperTenantSignKeyStore() throws IdentityException { + + String keyStoreLocation = ServerConfiguration.getInstance().getFirstProperty( + SECURITY_SAML_SIGN_KEY_STORE_LOCATION); + try (FileInputStream is = new FileInputStream(keyStoreLocation)) { + String keyStoreType = ServerConfiguration.getInstance().getFirstProperty( + SECURITY_SAML_SIGN_KEY_STORE_TYPE); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + char[] keyStorePassword = ServerConfiguration.getInstance().getFirstProperty( + SECURITY_SAML_SIGN_KEY_STORE_PASSWORD).toCharArray(); + keyStore.load(is, keyStorePassword); + superTenantSignKeyStore = keyStore; + } catch (IOException | CertificateException | NoSuchAlgorithmException e) { + throw new IdentityException("Unable to load keystore.", e); + } catch (KeyStoreException e) { + throw new IdentityException("Unable to get an instance of keystore.", e); + } + } + + /** + * Set private key and X509Certificate from the tenant KeyStore. + * + * @param tenantDomain tenant domain. + * @param keyStoreManager KeyStore Manager. + * @throws IdentityException Error in retrieving private key and certificate. + */ + private void initCredentialForTenant(String tenantDomain, KeyStoreManager keyStoreManager) + throws IdentityException { + + try { + // Derive key store name. + String ksName = tenantDomain.trim().replace(".", "-"); + // Derive JKS name. + String jksName = ksName + ".jks"; + privateKey = (PrivateKey) keyStoreManager.getPrivateKey(jksName, tenantDomain); + signingCert = (X509Certificate) keyStoreManager.getKeyStore(jksName).getCertificate(tenantDomain); + // This Exception is thrown from the KeyStoreManager. + } catch (Exception e) { + throw new IdentityException("Error retrieving private key and the certificate for tenant " + + tenantDomain, e); + } + } + + /** + * Check whether separate configurations for sign KeyStore available. + * + * @return true if necessary configurations are defined for sign KeyStore; false otherwise. + */ + private boolean isSignKeyStoreConfigured() { + + String keyStoreLocation = ServerConfiguration.getInstance().getFirstProperty( + SECURITY_SAML_SIGN_KEY_STORE_LOCATION); + String keyStoreType = ServerConfiguration.getInstance().getFirstProperty( + SECURITY_SAML_SIGN_KEY_STORE_TYPE); + String keyStorePassword = ServerConfiguration.getInstance().getFirstProperty( + SECURITY_SAML_SIGN_KEY_STORE_PASSWORD); + String keyAlias = ServerConfiguration.getInstance().getFirstProperty( + SECURITY_SAML_SIGN_KEY_STORE_KEY_ALIAS); + String keyPassword = ServerConfiguration.getInstance().getFirstProperty( + SECURITY_SAML_SIGN_KEY_STORE_KEY_PASSWORD); + + return StringUtils.isNotBlank(keyStoreLocation) && StringUtils.isNotBlank(keyStoreType) + && StringUtils.isNotBlank(keyStorePassword) && StringUtils.isNotBlank(keyAlias) + && StringUtils.isNotBlank(keyPassword); + } + /** * The key is constructed form modulus and exponent. * @@ -122,8 +316,8 @@ public Collection getKeyNames() { @Override public PrivateKey getPrivateKey() { - // TODO Auto-generated method stub - return null; + + return privateKey; } @Override diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAMLSSOAuthnReqDTO.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAMLSSOAuthnReqDTO.java index 0fd36b65d..35d9ff169 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAMLSSOAuthnReqDTO.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAMLSSOAuthnReqDTO.java @@ -56,6 +56,8 @@ public class SAMLSSOAuthnReqDTO implements Serializable { private String[] requestedAudiences; private String[] requestedRecipients; private boolean doSingleLogout; + private boolean doFrontChannelLogout; + private String frontChannelLogoutBinding; private boolean doSignResponse; private boolean doSignAssertions; private boolean isStratosDeployment = false; @@ -287,6 +289,26 @@ public void setDoSignAssertions(boolean doSignAssertions) { this.doSignAssertions = doSignAssertions; } + public boolean isDoFrontChannelLogout() { + + return doFrontChannelLogout; + } + + public void setDoFrontChannelLogout(boolean doFrontChannelLogout) { + + this.doFrontChannelLogout = doFrontChannelLogout; + } + + public String getFrontChannelLogoutBinding() { + + return frontChannelLogoutBinding; + } + + public void setFrontChannelLogoutBinding(String frontChannelLogoutBinding) { + + this.frontChannelLogoutBinding = frontChannelLogoutBinding; + } + /** * @return */ diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAMLSSOServiceProviderDTO.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAMLSSOServiceProviderDTO.java index 5d2737908..df46c34f0 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAMLSSOServiceProviderDTO.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/dto/SAMLSSOServiceProviderDTO.java @@ -46,9 +46,11 @@ public class SAMLSSOServiceProviderDTO implements Serializable { private String sloRequestURL; private String loginPageURL; private String attributeConsumingServiceIndex; + private String frontChannelLogoutBinding; private boolean doSingleLogout; private boolean doSignAssertions; private boolean doSignResponse; + private boolean doFrontChannelLogout; @XmlElementWrapper(name="requestedClaims") @XmlElement(name = "requestedClaim") private String[] requestedClaims; @@ -243,6 +245,26 @@ public void setLoginPageURL(String loginPageURL) { this.loginPageURL = loginPageURL; } + public boolean isDoFrontChannelLogout() { + + return doFrontChannelLogout; + } + + public void setDoFrontChannelLogout(boolean doFrontChannelLogout) { + + this.doFrontChannelLogout = doFrontChannelLogout; + } + + public String getFrontChannelLogoutBinding() { + + return frontChannelLogoutBinding; + } + + public void setFrontChannelLogoutBinding(String frontChannelLogoutBinding) { + + this.frontChannelLogoutBinding = frontChannelLogoutBinding; + } + /** * @return */ diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/logout/LogoutRequestSender.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/logout/LogoutRequestSender.java index 3449416a3..b20e7c4c0 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/logout/LogoutRequestSender.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/logout/LogoutRequestSender.java @@ -290,40 +290,8 @@ private boolean validateResponse(HttpResponse httpResponse, String certificateAl // This should be a SAML logout response. if (xmlObject instanceof LogoutResponse) { - LogoutResponse logoutResponse = (LogoutResponse) xmlObject; - if (logoutResponse.getIssuer() == null || logoutResponse.getStatus() == null || logoutResponse - .getStatus().getStatusCode() == null) { - if (log.isDebugEnabled()) { - log.debug("Logout response validation failed due to one of given values are null. " + - "Issuer: " + logoutResponse.getIssuer() + - " Status: " + logoutResponse.getStatus() + - " Status code: " + (logoutResponse.getStatus() != null ? logoutResponse.getStatus() - .getStatusCode() : null)); - } - return false; - } - - if (log.isDebugEnabled()) { - log.debug("Logout response received for issuer: " + logoutResponse.getIssuer() - .getValue() + " for tenant domain: " + tenantDomain); - } - - boolean isSignatureValid = true; - - // Certificate alias will be null if signature validation is disabled in the service provider side. - if (certificateAlias != null && logoutResponse.isSigned()) { - isSignatureValid = SAMLSSOUtil.validateXMLSignature(logoutResponse, certificateAlias, tenantDomain); - if (log.isDebugEnabled()) { - log.debug("Signature validation result for logout response for issuer: " + - logoutResponse.getIssuer().getValue() + " in tenant domain: " + tenantDomain + " is: " + - isSignatureValid); - } - } - if (SAMLSSOConstants.StatusCodes.SUCCESS_CODE.equals(logoutResponse.getStatus().getStatusCode() - .getValue()) && isSignatureValid) { - return true; - } + return SAMLSSOUtil.validateLogoutResponse(logoutResponse, certificateAlias, tenantDomain); } return false; diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/processors/IdPInitSSOAuthnRequestProcessor.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/processors/IdPInitSSOAuthnRequestProcessor.java index f43c969b8..f366cdfb1 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/processors/IdPInitSSOAuthnRequestProcessor.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/processors/IdPInitSSOAuthnRequestProcessor.java @@ -129,6 +129,8 @@ public SAMLSSORespDTO process(SAMLSSOAuthnReqDTO authnReqDTO, String sessionId, spDO.setSloRequestURL(authnReqDTO.getSloRequestURL()); spDO.setTenantDomain(authnReqDTO.getTenantDomain()); spDO.setDoSingleLogout(authnReqDTO.isDoSingleLogout()); + spDO.setDoFrontChannelLogout(authnReqDTO.isDoFrontChannelLogout()); + spDO.setFrontChannelLogoutBinding(authnReqDTO.getFrontChannelLogoutBinding()); spDO.setIdPInitSLOEnabled(authnReqDTO.isIdPInitSLOEnabled()); spDO.setAssertionConsumerUrls(authnReqDTO.getAssertionConsumerURLs()); spDO.setIdpInitSLOReturnToURLs(authnReqDTO.getIdpInitSLOReturnToURLs()); @@ -260,6 +262,8 @@ private void populateServiceProviderConfigs(SAMLSSOServiceProviderDO ssoIdpConfi authnReqDTO.setDoSingleLogout(ssoIdpConfigs.isDoSingleLogout()); authnReqDTO.setSloResponseURL(ssoIdpConfigs.getSloResponseURL()); authnReqDTO.setSloRequestURL(ssoIdpConfigs.getSloRequestURL()); + authnReqDTO.setDoFrontChannelLogout(ssoIdpConfigs.isDoFrontChannelLogout()); + authnReqDTO.setFrontChannelLogoutBinding(ssoIdpConfigs.getFrontChannelLogoutBinding()); authnReqDTO.setDoSignResponse(ssoIdpConfigs.isDoSignResponse()); authnReqDTO.setDoSignAssertions(ssoIdpConfigs.isDoSignAssertions()); authnReqDTO.setRequestedClaims(ssoIdpConfigs.getRequestedClaims()); diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/processors/SPInitSSOAuthnRequestProcessor.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/processors/SPInitSSOAuthnRequestProcessor.java index 960fa00a7..225ca31d8 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/processors/SPInitSSOAuthnRequestProcessor.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/processors/SPInitSSOAuthnRequestProcessor.java @@ -17,7 +17,6 @@ */ package org.wso2.carbon.identity.sso.saml.processors; -import org.apache.axis2.transport.http.ServletBasedOutTransportInfo; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -158,6 +157,8 @@ public SAMLSSORespDTO process(SAMLSSOAuthnReqDTO authnReqDTO, String sessionId, spDO.setTenantDomain(authnReqDTO.getTenantDomain()); spDO.setNameIDFormat(authnReqDTO.getNameIDFormat()); spDO.setDoSingleLogout(authnReqDTO.isDoSingleLogout()); + spDO.setDoFrontChannelLogout(authnReqDTO.isDoFrontChannelLogout()); + spDO.setFrontChannelLogoutBinding(authnReqDTO.getFrontChannelLogoutBinding()); spDO.setIdPInitSLOEnabled(authnReqDTO.isIdPInitSLOEnabled()); spDO.setAssertionConsumerUrls(authnReqDTO.getAssertionConsumerURLs()); spDO.setIdpInitSLOReturnToURLs(authnReqDTO.getIdpInitSLOReturnToURLs()); @@ -287,6 +288,8 @@ private void populateServiceProviderConfigs(SAMLSSOServiceProviderDO ssoIdpConfi authnReqDTO.setNameIdClaimUri(ssoIdpConfigs.getNameIdClaimUri()); authnReqDTO.setNameIDFormat(ssoIdpConfigs.getNameIDFormat()); authnReqDTO.setDoSingleLogout(ssoIdpConfigs.isDoSingleLogout()); + authnReqDTO.setDoFrontChannelLogout(ssoIdpConfigs.isDoFrontChannelLogout()); + authnReqDTO.setFrontChannelLogoutBinding(ssoIdpConfigs.getFrontChannelLogoutBinding()); authnReqDTO.setSloResponseURL(ssoIdpConfigs.getSloResponseURL()); authnReqDTO.setSloRequestURL(ssoIdpConfigs.getSloRequestURL()); authnReqDTO.setDoSignResponse(ssoIdpConfigs.isDoSignResponse()); diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/servlet/SAMLSSOProviderServlet.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/servlet/SAMLSSOProviderServlet.java index 163a9502d..d292e5659 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/servlet/SAMLSSOProviderServlet.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/servlet/SAMLSSOProviderServlet.java @@ -20,6 +20,10 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.opensaml.saml2.core.LogoutRequest; +import org.opensaml.saml2.core.LogoutResponse; +import org.opensaml.saml2.core.impl.LogoutRequestImpl; +import org.opensaml.xml.XMLObject; import org.owasp.encoder.Encode; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.context.RegistryType; @@ -43,13 +47,22 @@ import org.wso2.carbon.identity.core.model.SAMLSSOServiceProviderDO; import org.wso2.carbon.identity.core.persistence.IdentityPersistenceManager; import org.wso2.carbon.identity.core.util.IdentityUtil; +import org.wso2.carbon.identity.sso.saml.FrontChannelSLOParticipantInfo; +import org.wso2.carbon.identity.sso.saml.FrontChannelSLOParticipantStore; import org.wso2.carbon.identity.sso.saml.SAMLECPConstants; import org.wso2.carbon.identity.sso.saml.SAMLSSOConstants; import org.wso2.carbon.identity.sso.saml.SAMLSSOService; import org.wso2.carbon.identity.sso.saml.SSOServiceProviderConfigManager; +import org.wso2.carbon.identity.sso.saml.builders.SignKeyDataHolder; +import org.wso2.carbon.identity.sso.saml.builders.SingleLogoutMessageBuilder; +import org.wso2.carbon.identity.sso.saml.builders.X509CredentialImpl; +import org.wso2.carbon.identity.sso.saml.cache.SAMLSSOParticipantCache; +import org.wso2.carbon.identity.sso.saml.cache.SAMLSSOParticipantCacheEntry; +import org.wso2.carbon.identity.sso.saml.cache.SAMLSSOParticipantCacheKey; import org.wso2.carbon.identity.sso.saml.cache.SessionDataCache; import org.wso2.carbon.identity.sso.saml.cache.SessionDataCacheEntry; import org.wso2.carbon.identity.sso.saml.cache.SessionDataCacheKey; +import org.wso2.carbon.identity.sso.saml.common.SAMLSSOProviderConstants; import org.wso2.carbon.identity.sso.saml.dto.QueryParamDTO; import org.wso2.carbon.identity.sso.saml.dto.SAMLSSOAuthnReqDTO; import org.wso2.carbon.identity.sso.saml.dto.SAMLSSOReqValidationResponseDTO; @@ -58,6 +71,7 @@ import org.wso2.carbon.identity.sso.saml.exception.IdentitySAML2SSOException; import org.wso2.carbon.identity.sso.saml.internal.IdentitySAMLSSOServiceComponent; import org.wso2.carbon.identity.sso.saml.session.SSOSessionPersistenceManager; +import org.wso2.carbon.identity.sso.saml.session.SessionInfoData; import org.wso2.carbon.identity.sso.saml.util.SAMLSOAPUtils; import org.wso2.carbon.identity.sso.saml.util.SAMLSSOUtil; import org.wso2.carbon.idp.mgt.util.IdPManagementUtil; @@ -76,6 +90,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.stream.Collectors; import javax.servlet.ServletException; import javax.servlet.http.Cookie; @@ -172,6 +187,7 @@ private void handleRequest(HttpServletRequest req, HttpServletResponse resp, boo String spEntityID = req.getParameter(SAMLSSOConstants.QueryParameter .SP_ENTITY_ID.toString()); String samlRequest = req.getParameter(SAMLSSOConstants.SAML_REQUEST); + String samlResponse = req.getParameter(SAMLSSOConstants.SAML_RESP); String sessionDataKey = getSessionDataKey(req); String slo = req.getParameter(SAMLSSOConstants.QueryParameter.SLO.toString()); Object flowStatus = req.getAttribute(FrameworkConstants.RequestParams.FLOW_STATUS); @@ -238,19 +254,10 @@ private void handleRequest(HttpServletRequest req, HttpServletResponse resp, boo handleIdPInitSSO(req, resp, relayState, queryString, authMode, sessionId, isPost, (slo != null)); } else if (samlRequest != null) {// SAMLRequest received. SP initiated SSO handleSPInitSSO(req, resp, queryString, relayState, authMode, samlRequest, sessionId, isPost); + } else if (samlResponse != null) {// SAMLResponse received. + handleSAMLResponse(req, resp, samlResponse, sessionId, isPost); } else { - log.debug("Invalid request message or single logout message "); - - if (sessionId == null) { - String errorResp = SAMLSSOUtil.buildErrorResponse( - SAMLSSOConstants.StatusCodes.REQUESTOR_ERROR, - "Invalid request message", null); - sendNotification(errorResp, SAMLSSOConstants.Notification.INVALID_MESSAGE_STATUS, - SAMLSSOConstants.Notification.INVALID_MESSAGE_MESSAGE, null, req, resp); - } else { - // Non-SAML request are assumed to be logout requests - sendToFrameworkForLogout(req, resp, null, null, sessionId, true, false); - } + handleInvalidRequestMessage(req, resp, sessionId); } } catch (UserStoreException e) { if (log.isDebugEnabled()) { @@ -281,6 +288,190 @@ private void handleRequest(HttpServletRequest req, HttpServletResponse resp, boo } } + private void handleInvalidRequestMessage(HttpServletRequest req, HttpServletResponse resp, String sessionId) + throws IOException, IdentityException, ServletException { + + log.debug("Invalid request message or single logout message "); + + if (sessionId == null) { + String errorResp = SAMLSSOUtil.buildErrorResponse( + SAMLSSOConstants.StatusCodes.REQUESTOR_ERROR, + "Invalid request message", null); + sendNotification(errorResp, SAMLSSOConstants.Notification.INVALID_MESSAGE_STATUS, + SAMLSSOConstants.Notification.INVALID_MESSAGE_MESSAGE, null, req, resp); + } else { + // Non-SAML request are assumed to be logout requests + sendToFrameworkForLogout(req, resp, null, null, sessionId, true, + false); + } + } + + private void handleSAMLResponse(HttpServletRequest req, HttpServletResponse resp, String samlResponse, + String sessionId, boolean isPost) + throws IdentityException, IOException, ServletException { + + XMLObject response; + + if (isPost) { + response = SAMLSSOUtil.unmarshall(SAMLSSOUtil.decodeForPost(samlResponse)); + } else { + response = SAMLSSOUtil.unmarshall(SAMLSSOUtil.decode(samlResponse)); + } + + if (response instanceof LogoutResponse) { + LogoutResponse logoutResponse = (LogoutResponse) response; + handleLogoutResponseFromSP(req, resp, sessionId, logoutResponse); + } else { + handleInvalidRequestMessage(req, resp, sessionId); + } + } + + private void handleLogoutResponseFromSP(HttpServletRequest req, HttpServletResponse resp, String sessionId, + LogoutResponse logoutResponse) + throws ServletException, IdentityException, IOException { + + String inResponseToId = logoutResponse.getInResponseTo(); + FrontChannelSLOParticipantInfo frontChannelSLOParticipantInfo = + getFrontChannelSLOParticipantInfo(inResponseToId); + + if (frontChannelSLOParticipantInfo == null || !frontChannelSLOParticipantInfo. + getCurrentSLOInvokedParticipant().equals(logoutResponse.getIssuer().getValue())) { + handleInvalidRequestMessage(req, resp, sessionId); + } else { + // Remove front-channel SLO Participant info from the FrontChannelSLOParticipantStore. + removeFrontChannelSLOParticipantInfo(inResponseToId); + String logoutResponseIssuer = logoutResponse.getIssuer().getValue(); + SAMLSSOServiceProviderDO responseIssuerSP = SAMLSSOUtil.getSPConfig( + SAMLSSOUtil.getTenantDomainFromThreadLocal(), logoutResponseIssuer); + + boolean isSuccessfullyLogout = SAMLSSOUtil.validateLogoutResponse(logoutResponse, + responseIssuerSP.getCertAlias(), responseIssuerSP.getTenantDomain()); + + if (!isSuccessfullyLogout) { + log.warn("Redirecting to default logout page due to an invalid logout response."); + resp.sendRedirect(FrameworkUtils.getRedirectURL(SAMLSSOUtil.getDefaultLogoutEndpoint(), req)); + if (log.isDebugEnabled()) { + log.debug("Single logout failed due to failure in logout response validation for logout " + + "response issuer: " + logoutResponseIssuer); + } + } else { + removeSPFromSession(frontChannelSLOParticipantInfo.getSessionIndex(), logoutResponseIssuer); + + List samlssoServiceProviderDOList = + SAMLSSOUtil.getRemainingSessionParticipantsForSLO( + frontChannelSLOParticipantInfo.getSessionIndex(), + frontChannelSLOParticipantInfo.getOriginalLogoutRequestIssuer(), + frontChannelSLOParticipantInfo.isIdPInitSLO()); + + if (samlssoServiceProviderDOList.isEmpty()) { + respondToOriginalLogoutRequestIssuer(req, resp, sessionId, frontChannelSLOParticipantInfo); + } else { + sendLogoutRequestToSessionParticipant(resp, samlssoServiceProviderDOList, + frontChannelSLOParticipantInfo.getOriginalIssuerLogoutRequestId(), + frontChannelSLOParticipantInfo.isIdPInitSLO(), + frontChannelSLOParticipantInfo.getRelayState(), + frontChannelSLOParticipantInfo.getReturnToURL(), + frontChannelSLOParticipantInfo.getSessionIndex(), + frontChannelSLOParticipantInfo.getOriginalLogoutRequestIssuer()); + } + } + } + } + + /** + * Respond back to the original logout request issuer after handling all the front-channel enabled + * session participants. + * + * @param req HttpServlet Request. + * @param resp HttpServlet Response. + * @param sessionId Session id. + * @param frontChannelSLOParticipantInfo Front-Channel SLO Participant Information. + * @throws IOException If sending response fails. + * @throws IdentityException If building logout response fails. + * @throws ServletException If sending response fails. + */ + private void respondToOriginalLogoutRequestIssuer(HttpServletRequest req, HttpServletResponse resp, + String sessionId, + FrontChannelSLOParticipantInfo frontChannelSLOParticipantInfo) + throws IOException, IdentityException, ServletException { + + if (SSOSessionPersistenceManager.getSessionIndexFromCache(sessionId) == null) { + // Remove tokenId Cookie when there is no session available. + removeTokenIdCookie(req, resp); + } + + if (frontChannelSLOParticipantInfo.isIdPInitSLO()) { + // Redirecting to the return URL or IS logout page. + resp.sendRedirect(frontChannelSLOParticipantInfo.getReturnToURL()); + } else { + SAMLSSOServiceProviderDO originalIssuer = + SAMLSSOUtil.getSPConfig(SAMLSSOUtil.getTenantDomainFromThreadLocal(), + frontChannelSLOParticipantInfo.getOriginalLogoutRequestIssuer()); + LogoutResponse logoutResponse = buildLogoutResponseForOriginalIssuer( + frontChannelSLOParticipantInfo.getOriginalIssuerLogoutRequestId(), originalIssuer); + + // Sending LogoutResponse back to the original issuer. + sendResponse(req, resp, frontChannelSLOParticipantInfo.getRelayState(), + SAMLSSOUtil.encode(SAMLSSOUtil.marshall(logoutResponse)), + logoutResponse.getDestination(), null, null, + SAMLSSOUtil.getTenantDomainFromThreadLocal()); + } + } + + /** + * Build logout response for the original logout request issuer. + * + * @param originalIssuerLogoutRequestId Logout request id of original issuer. + * @param originalIssuer Original issuer. + * @return Logout response. + * @throws IdentityException If building logout response fails. + */ + private LogoutResponse buildLogoutResponseForOriginalIssuer(String originalIssuerLogoutRequestId, + SAMLSSOServiceProviderDO originalIssuer) + throws IdentityException { + + String destination; + if (StringUtils.isNotBlank(originalIssuer.getSloResponseURL())) { + destination = originalIssuer.getSloResponseURL(); + if (log.isDebugEnabled()) { + log.debug("Destination of the logout response is set to the SLO response URL of the SP: " + + originalIssuer.getSloResponseURL()); + } + } else { + destination = originalIssuer.getDefaultAssertionConsumerUrl(); + if (log.isDebugEnabled()) { + log.debug("Destination of the logout response is set to the ACS URL of the SP: " + + originalIssuer.getAssertionConsumerUrl()); + } + } + + SingleLogoutMessageBuilder logoutMsgBuilder = new SingleLogoutMessageBuilder(); + LogoutResponse logoutResponse = logoutMsgBuilder.buildLogoutResponse( + originalIssuerLogoutRequestId, + SAMLSSOConstants.StatusCodes.SUCCESS_CODE, + null, + destination, + originalIssuer.isDoSignResponse(), + SAMLSSOUtil.getTenantDomainFromThreadLocal(), + originalIssuer.getSigningAlgorithmUri(), + originalIssuer.getDigestAlgorithmUri()); + + return logoutResponse; + } + + private void removeSPFromSession(String sessionIndex, String serviceProvider) { + + if (sessionIndex != null && serviceProvider != null) { + SAMLSSOParticipantCacheKey cacheKey = new SAMLSSOParticipantCacheKey(sessionIndex); + SAMLSSOParticipantCacheEntry cacheEntry = SAMLSSOParticipantCache.getInstance().getValueFromCache(cacheKey); + + if (cacheEntry.getSessionInfoData() != null && + cacheEntry.getSessionInfoData().getServiceProviderList() != null) { + cacheEntry.getSessionInfoData().removeServiceProvider(serviceProvider); + } + } + } + /** * In federated and multi steps scenario there is a redirection from commonauth to samlsso so have to get * session data key from query parameter @@ -637,6 +828,10 @@ private void sendToFrameworkForLogout(HttpServletRequest request, HttpServletRes sessionDTO.setLogoutReq(true); sessionDTO.setInvalidLogout(invalid); + Properties properties = new Properties(); + properties.put(SAMLSSOConstants.IS_POST, isPost); + sessionDTO.setProperties(properties); + if (signInRespDTO != null) { sessionDTO.setDestination(signInRespDTO.getDestination()); sessionDTO.setRequestMessageString(signInRespDTO.getRequestMessageString()); @@ -764,62 +959,80 @@ private void sendResponse(HttpServletRequest req, HttpServletResponse resp, Stri } out.print(soapResponse); } else { + generateSamlPostPageFromFile(resp, acUrl, response, relayState, authenticatedIdPs, + SAMLSSOConstants.SAML_RESP); + } - String finalPage = null; - String htmlPage = IdentitySAMLSSOServiceComponent.getSsoRedirectHtml(); - String pageWithAcs = htmlPage.replace("$acUrl", acUrl); - String pageWithAcsResponse = pageWithAcs.replace("", "\n" + ""); - String pageWithAcsResponseRelay = pageWithAcsResponse; + } else { + generateSamlPostPageFromTemplate(resp, acUrl, response, relayState, authenticatedIdPs, + SAMLSSOConstants.SAML_RESP); + } + } - if(relayState != null) { - pageWithAcsResponseRelay = pageWithAcsResponse.replace("", "\n" + ""); - } + private void generateSamlPostPageFromTemplate(HttpServletResponse resp, String acUrl, String samlMessage, + String relayState, String authenticatedIdPs, String samlMessageType) + throws IOException { - if (authenticatedIdPs == null || authenticatedIdPs.isEmpty()) { - finalPage = pageWithAcsResponseRelay; - } else { - finalPage = pageWithAcsResponseRelay.replace( - "", - ""); - } + PrintWriter out = resp.getWriter(); + out.println(""); + out.println(""); + out.println("

You are now redirected back to " + Encode.forHtmlContent(acUrl)); + out.println(" If the redirection fails, please click the post button.

"); + out.println("
"); + out.println("

"); + out.println(""); - PrintWriter out = resp.getWriter(); - out.print(finalPage); + if (relayState != null) { + out.println(""); + } - if (log.isDebugEnabled()) { - log.debug("samlsso_response.html " + finalPage); - } + if (authenticatedIdPs != null && !authenticatedIdPs.isEmpty()) { + out.println(""); + } - } + out.println(""); + out.println("

"); + out.println("
"); + out.println(""); + out.println(""); + out.println(""); + } + + private void generateSamlPostPageFromFile(HttpServletResponse resp, String acUrl, String samlMessage, + String relayState, String authenticatedIdPs, String samlMessageType) + throws IOException { + + String finalPage; + String htmlPage = IdentitySAMLSSOServiceComponent.getSsoRedirectHtml(); + String pageWithAcs = htmlPage.replace("$acUrl", acUrl); + String pageWithAcsResponse = pageWithAcs.replace("", + buildPostPageInputs(samlMessageType, samlMessage)); + String pageWithAcsResponseRelay = pageWithAcsResponse; + + if (relayState != null) { + pageWithAcsResponseRelay = pageWithAcsResponse.replace("", + buildPostPageInputs(SAMLSSOConstants.RELAY_STATE, relayState)); + } + if (authenticatedIdPs == null || authenticatedIdPs.isEmpty()) { + finalPage = pageWithAcsResponseRelay; } else { - PrintWriter out = resp.getWriter(); - out.println(""); - out.println(""); - out.println("

You are now redirected back to " + Encode.forHtmlContent(acUrl)); - out.println(" If the redirection fails, please click the post button.

"); - out.println("
"); - out.println("

"); - out.println(""); - - if(relayState != null) { - out.println(""); - } + finalPage = pageWithAcsResponseRelay.replace( + "", + ""); + } - if (authenticatedIdPs != null && !authenticatedIdPs.isEmpty()) { - out.println(""); - } + PrintWriter out = resp.getWriter(); + out.print(finalPage); - out.println(""); - out.println("

"); - out.println("
"); - out.println(""); - out.println(""); - out.println(""); + if (log.isDebugEnabled()) { + log.debug("samlsso_response.html " + finalPage); } } @@ -1012,49 +1225,108 @@ private void handleAuthenticationReponseFromFramework(HttpServletRequest req, Ht } } - private void handleLogoutResponseFromFramework(HttpServletRequest request, - HttpServletResponse response, SAMLSSOSessionDTO sessionDTO) + private void handleLogoutResponseFromFramework(HttpServletRequest request, HttpServletResponse response, + SAMLSSOSessionDTO sessionDTO) throws ServletException, IOException, IdentityException { SAMLSSOReqValidationResponseDTO validationResponseDTO = sessionDTO.getValidationRespDTO(); if (validationResponseDTO != null) { removeSessionDataFromCache(request.getParameter(SAMLSSOConstants.SESSION_DATA_KEY)); - - if ( SSOSessionPersistenceManager.getSessionIndexFromCache(sessionDTO.getSessionId()) == null) { - // remove tokenId Cookie when there is no session available. - removeTokenIdCookie(request, response); + boolean isPost = (boolean) sessionDTO.getProperties().get(SAMLSSOConstants.IS_POST); + String sessionIndex = extractSessionIndex(request, validationResponseDTO.isIdPInitSLO(), + sessionDTO.getSessionId(), isPost); + List samlssoServiceProviderDOList = + SAMLSSOUtil.getRemainingSessionParticipantsForSLO(sessionIndex, sessionDTO.getIssuer(), + validationResponseDTO.isIdPInitSLO()); + + // Get the SP list and check for other session participants that have enabled single logout. + if (samlssoServiceProviderDOList.isEmpty()) { + respondToOriginalIssuer(request, response, sessionDTO); + } else { + String originalIssuerLogoutRequestId = null; + if (!validationResponseDTO.isIdPInitSLO()) { + originalIssuerLogoutRequestId = extractLogoutRequestId(request, isPost); + } + sendLogoutRequestToSessionParticipant(response, samlssoServiceProviderDOList, + originalIssuerLogoutRequestId, validationResponseDTO.isIdPInitSLO(), sessionDTO.getRelayState(), + validationResponseDTO.getReturnToURL(), sessionIndex, sessionDTO.getIssuer()); } + } else { + sendErrorResponseToOriginalIssuer(request, response, sessionDTO); + } + } - if (validationResponseDTO.isIdPInitSLO()) { - // redirecting to the return URL or IS logout page - response.sendRedirect(validationResponseDTO.getReturnToURL()); - } else { - // sending LogoutResponse back to the initiator - sendResponse(request, response, sessionDTO.getRelayState(), validationResponseDTO.getLogoutResponse(), - validationResponseDTO.getAssertionConsumerURL(), validationResponseDTO.getSubject(), null, - sessionDTO.getTenantDomain()); + private void sendLogoutRequestToSessionParticipant(HttpServletResponse response, + List samlssoServiceProviderDOList, + String originalIssuerLogoutRequestId, boolean isIdPInitSLO, + String relayState, String returnToURL, String sessionIndex, + String originalLogoutRequestIssuer) + throws IOException, IdentityException { + + for (SAMLSSOServiceProviderDO entry : samlssoServiceProviderDOList) { + if (entry.isDoFrontChannelLogout()) { + doFrontChannelSLO(response, entry, sessionIndex, originalLogoutRequestIssuer, + originalIssuerLogoutRequestId, isIdPInitSLO, relayState, returnToURL); + break; } + } + } + + private void respondToOriginalIssuer(HttpServletRequest request, HttpServletResponse response, + SAMLSSOSessionDTO sessionDTO) throws ServletException, IOException, + IdentityException { + + SAMLSSOReqValidationResponseDTO validationResponseDTO = sessionDTO.getValidationRespDTO(); + + if (SSOSessionPersistenceManager.getSessionIndexFromCache(sessionDTO.getSessionId()) == null) { + // Remove tokenId Cookie when there is no session available. + removeTokenIdCookie(request, response); + } + + if (validationResponseDTO.isIdPInitSLO()) { + // Redirecting to the return URL or IS logout page. + response.sendRedirect(validationResponseDTO.getReturnToURL()); } else { - String acsUrl = sessionDTO.getAssertionConsumerURL(); - if (StringUtils.isBlank(acsUrl) && sessionDTO.getIssuer() != null) { - SAMLSSOServiceProviderDO serviceProviderDO = - SAMLSSOUtil.getSPConfig(SAMLSSOUtil.getTenantDomainFromThreadLocal(), sessionDTO.getIssuer()); - if (serviceProviderDO != null) { - acsUrl = serviceProviderDO.getSloResponseURL(); - if (StringUtils.isBlank(acsUrl)) { - acsUrl = serviceProviderDO.getDefaultAssertionConsumerUrl(); - } + // Sending LogoutResponse back to the initiator. + sendResponse(request, response, sessionDTO.getRelayState(), validationResponseDTO.getLogoutResponse(), + validationResponseDTO.getAssertionConsumerURL(), validationResponseDTO.getSubject(), + null, sessionDTO.getTenantDomain()); + } + } + + /** + * Send an error response to original issuer when the SAML request validation is invalid. + * + * @param request HttpServlet Request. + * @param response HttpServlet Response. + * @param sessionDTO SAMLSSOSessionDTO. + * @throws IOException If error response building fails. + * @throws IdentityException If error response building fails. + * @throws ServletException If sending error response fails. + */ + private void sendErrorResponseToOriginalIssuer(HttpServletRequest request, HttpServletResponse response, + SAMLSSOSessionDTO sessionDTO) + throws IOException, IdentityException, ServletException { + + String acsUrl = sessionDTO.getAssertionConsumerURL(); + if (StringUtils.isBlank(acsUrl) && sessionDTO.getIssuer() != null) { + SAMLSSOServiceProviderDO serviceProviderDO = + SAMLSSOUtil.getSPConfig(SAMLSSOUtil.getTenantDomainFromThreadLocal(), sessionDTO.getIssuer()); + if (serviceProviderDO != null) { + acsUrl = serviceProviderDO.getSloResponseURL(); + if (StringUtils.isBlank(acsUrl)) { + acsUrl = serviceProviderDO.getDefaultAssertionConsumerUrl(); } } - String errorResp = SAMLSSOUtil.buildErrorResponse( - SAMLSSOConstants.StatusCodes.REQUESTOR_ERROR, - "Invalid request", - acsUrl); - sendNotification(errorResp, SAMLSSOConstants.Notification.INVALID_MESSAGE_STATUS, - SAMLSSOConstants.Notification.INVALID_MESSAGE_MESSAGE, - acsUrl, request, response); } + String errorResp = SAMLSSOUtil.buildErrorResponse( + SAMLSSOConstants.StatusCodes.REQUESTOR_ERROR, + "Invalid request", + acsUrl); + sendNotification(errorResp, SAMLSSOConstants.Notification.INVALID_MESSAGE_STATUS, + SAMLSSOConstants.Notification.INVALID_MESSAGE_MESSAGE, + acsUrl, request, response); } private Cookie getTokenIdCookie(HttpServletRequest req) { @@ -1507,4 +1779,210 @@ private void setSPAttributeToRequest(HttpServletRequest req, String issuer, Stri tenantDomain, e); } } + + private void doFrontChannelSLO(HttpServletResponse response, + SAMLSSOServiceProviderDO samlssoServiceProviderDO, String sessionIndex, + String originalLogoutRequestIssuer, String originalIssuerLogoutRequestId, + boolean isIdPInitSLO, String relayState, String returnToURL) + throws IdentityException, IOException { + + SessionInfoData sessionInfoData = SAMLSSOUtil.getSessionInfoData(sessionIndex); + String subject = sessionInfoData.getSubject(samlssoServiceProviderDO.getIssuer()); + + LogoutRequest logoutRequest = SAMLSSOUtil.buildLogoutRequest(samlssoServiceProviderDO, subject, sessionIndex); + storeFrontChannelSLOParticipantInfo(samlssoServiceProviderDO, originalLogoutRequestIssuer, logoutRequest, + originalIssuerLogoutRequestId, sessionIndex, isIdPInitSLO, relayState, returnToURL); + + if (SAMLSSOProviderConstants.HTTP_POST_BINDING. + equals(samlssoServiceProviderDO.getFrontChannelLogoutBinding())) { + sendPostRequest(response, samlssoServiceProviderDO, logoutRequest); + } else { + String redirectUrl = createHttpQueryStringForRedirect(logoutRequest, samlssoServiceProviderDO); + response.sendRedirect(redirectUrl); + } + } + + /** + * This method is used to prepare and send a SAML request message with HTTP POST binding. + * + * @param response HttpServlet Response. + * @param samlssoServiceProviderDO SAMLSSOServiceProviderDO. + * @param logoutRequest Logout Request. + * @throws IdentityException Error in marshalling or getting SignKeyDataHolder. + * @throws IOException Error in post page printing. + */ + private void sendPostRequest(HttpServletResponse response, SAMLSSOServiceProviderDO samlssoServiceProviderDO, + LogoutRequest logoutRequest) + throws IdentityException, IOException { + + logoutRequest = SAMLSSOUtil.setSignature(logoutRequest, samlssoServiceProviderDO.getSigningAlgorithmUri(), + samlssoServiceProviderDO.getDigestAlgorithmUri(), new SignKeyDataHolder(null)); + String encodedRequestMessage = SAMLSSOUtil.encode(SAMLSSOUtil.marshall(logoutRequest)); + String acUrl = logoutRequest.getDestination(); + printPostPage(response, acUrl, encodedRequestMessage); + } + + private void printPostPage(HttpServletResponse response, String acUrl, String encodedRequestMessage) + throws IOException { + + response.setContentType("text/html; charset=UTF-8"); + if (IdentitySAMLSSOServiceComponent.getSsoRedirectHtml() != null) { + generateSamlPostPageFromFile(response, acUrl, encodedRequestMessage, null, null, + SAMLSSOConstants.SAML_REQUEST); + } else { + generateSamlPostPageFromTemplate(response, acUrl, encodedRequestMessage, null, + null, SAMLSSOConstants.SAML_REQUEST); + } + } + + private String buildPostPageInputs(String formControlName, String formControlValue) { + + StringBuilder hiddenInputBuilder = new StringBuilder(); + hiddenInputBuilder.append("\n").append(""); + + return hiddenInputBuilder.toString(); + } + + /** + * Stores information of front-channel session participants in single logout. + * + * @param logoutRequestIssuingSP Logout request issuing service provider + * @param originalLogoutRequestIssuer Original logout request issuer + * @param logoutRequest Logout request + * @param initialLogoutRequestId Logout request id of the original issuer + * @param sessionIndex Session index + * @param isIdPInitSLO is IdP Initiated Single logout + * @param relayState Relay State + * @param returnToURL Return to URL + */ + private void storeFrontChannelSLOParticipantInfo(SAMLSSOServiceProviderDO logoutRequestIssuingSP, + String originalLogoutRequestIssuer, + LogoutRequest logoutRequest, String initialLogoutRequestId, + String sessionIndex, boolean isIdPInitSLO, String relayState, + String returnToURL) { + + FrontChannelSLOParticipantInfo frontChannelSLOParticipantInfo = + new FrontChannelSLOParticipantInfo(initialLogoutRequestId, originalLogoutRequestIssuer, + logoutRequestIssuingSP.getIssuer(), sessionIndex, isIdPInitSLO, relayState, returnToURL); + + FrontChannelSLOParticipantStore.getInstance().addToCache(logoutRequest.getID(), frontChannelSLOParticipantInfo); + } + + /** + * Extract logout request id of the original issuer logout request. + * + * @param request HttpServletRequest. + * @param isPost Whether the request is post. + * @return Logout request id of the original logout request issuer. + * @throws IdentityException Decoding error. + */ + private String extractLogoutRequestId(HttpServletRequest request, boolean isPost) throws IdentityException { + + String initialSamlLogoutRequest = request.getParameter(SAMLSSOConstants.SAML_REQUEST); + XMLObject samlRequest = SAMLSSOUtil.decodeSamlLogoutRequest(initialSamlLogoutRequest, isPost); + + String initialLogoutRequestId = null; + if (samlRequest instanceof LogoutRequestImpl) { + initialLogoutRequestId = ((LogoutRequestImpl) samlRequest).getID(); + } + + return initialLogoutRequestId; + } + + /** + * Extract session index. + * + * @param request HttpServlet Request. + * @param isIdPInitSLO Whether the SLO is IdP initiated or not. + * @param sessionId Session id. + * @param isPost Whether the request is post. + * @return Session Index. + * @throws IdentityException Decoding error. + */ + private String extractSessionIndex(HttpServletRequest request, boolean isIdPInitSLO, String sessionId, + boolean isPost) throws IdentityException { + + String sessionIndex = null; + if (isIdPInitSLO) { + SSOSessionPersistenceManager ssoSessionPersistenceManager = SSOSessionPersistenceManager + .getPersistenceManager(); + sessionIndex = ssoSessionPersistenceManager.getSessionIndexFromTokenId(sessionId); + } else { + String initialSamlLogoutRequest = request.getParameter(SAMLSSOConstants.SAML_REQUEST); + XMLObject samlRequest = SAMLSSOUtil.decodeSamlLogoutRequest(initialSamlLogoutRequest, isPost); + + if (samlRequest instanceof LogoutRequestImpl) { + sessionIndex = ((LogoutRequestImpl) samlRequest).getSessionIndexes().size() > 0 ? + ((LogoutRequestImpl) samlRequest).getSessionIndexes().get(0).getSessionIndex() : null; + } + } + + return sessionIndex; + } + + /** + * Retrieves information of front-channel session participants in single logout. + * + * @param logoutRequestId Logout request id. + * @return Front-Channel SLO Participant Information. + */ + private FrontChannelSLOParticipantInfo getFrontChannelSLOParticipantInfo(String logoutRequestId) { + + FrontChannelSLOParticipantInfo frontChannelSLOParticipantInfo = + FrontChannelSLOParticipantStore.getInstance().getValueFromCache(logoutRequestId); + + return frontChannelSLOParticipantInfo; + } + + /** + * Removes information of front-channel slo session participant from the store. + * + * @param logoutRequestId Logout request id. + */ + private void removeFrontChannelSLOParticipantInfo(String logoutRequestId) { + + FrontChannelSLOParticipantStore.getInstance().clearCacheEntry(logoutRequestId); + } + + /** + * This method is used to prepare a SAML request message as a HTTP query string for HTTP Redirect binding. + * + * @param logoutRequest Logout Request. + * @param serviceProviderDO SAMLSSOServiceProviderDO. + * @return Redirect URL. + * @throws IdentityException Error in marshalling or setting signature to http query string. + */ + private String createHttpQueryStringForRedirect(LogoutRequest logoutRequest, + SAMLSSOServiceProviderDO serviceProviderDO) + throws IdentityException { + + // Convert the SAML logout request object to a string. + String logoutRequestString = (SAMLSSOUtil.marshall(logoutRequest)). + replaceAll(SAMLSSOConstants.XML_TAG_REGEX, "").trim(); + + StringBuilder httpQueryString = null; + String signatureAlgorithmUri = serviceProviderDO.getSigningAlgorithmUri(); + + String tenantDomain = serviceProviderDO.getTenantDomain(); + if (StringUtils.isEmpty(tenantDomain)) { + tenantDomain = MultitenantConstants.SUPER_TENANT_DOMAIN_NAME; + } + + try { + httpQueryString = new StringBuilder(SAMLSSOConstants.SAML_REQUEST + "=" + + URLEncoder.encode(SAMLSSOUtil.compressResponse(logoutRequestString), "UTF-8")); + httpQueryString.append("&" + SAMLSSOConstants.SIG_ALG + "=" + + URLEncoder.encode(signatureAlgorithmUri, "UTF-8")); + SAMLSSOUtil.addSignatureToHTTPQueryString(httpQueryString, signatureAlgorithmUri, + new X509CredentialImpl(tenantDomain)); + } catch (IOException e) { + throw new IdentityException("Error in compressing the SAML request message.", e); + } + + String redirectUrl = logoutRequest.getDestination() + "?" + httpQueryString.toString(); + + return redirectUrl; + } + } diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/session/SSOSessionPersistenceManager.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/session/SSOSessionPersistenceManager.java index d45460c88..2c3773397 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/session/SSOSessionPersistenceManager.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/session/SSOSessionPersistenceManager.java @@ -204,21 +204,7 @@ public static void removeSession(String sessionId, String issuer) { if (cacheEntry.getSessionInfoData() != null && cacheEntry.getSessionInfoData().getServiceProviderList() != null) { SAMLSSOServiceProviderDO providerDO = cacheEntry.getSessionInfoData().getServiceProviderList().get(issuer); if (providerDO != null && providerDO.isDoSingleLogout()) { - Set sloSupportedIssuers = new HashSet(); - //Filter out service providers which enabled the single logout - for (Map.Entry entry : cacheEntry.getSessionInfoData(). - getServiceProviderList().entrySet()) { - if (entry.getValue().isDoSingleLogout()) { - sloSupportedIssuers.add(entry.getKey()); - } - } - //Remove service providers which enabled the single logout - for (String sloSupportedIssuer : sloSupportedIssuers) { - cacheEntry.getSessionInfoData().removeServiceProvider(sloSupportedIssuer); - if(log.isDebugEnabled()) { - log.debug("Removed SLO supported service provider from session info data with name " + sloSupportedIssuer); - } - } + removeBackChannelSLOEnabledSPs(cacheEntry); } else { if(cacheEntry.getSessionInfoData() != null) { cacheEntry.getSessionInfoData().removeServiceProvider(issuer); @@ -228,6 +214,9 @@ public static void removeSession(String sessionId, String issuer) { } } } + } else { + // Remove session participants in IdP initiated back-channel SLO. + removeBackChannelSLOEnabledSPs(cacheEntry); } if (cacheEntry.getSessionInfoData() == null || cacheEntry.getSessionInfoData().getServiceProviderList() == null || @@ -242,6 +231,26 @@ public static void removeSession(String sessionId, String issuer) { } } + public static void removeBackChannelSLOEnabledSPs(SAMLSSOParticipantCacheEntry cacheEntry) { + + Set sloSupportedIssuers = new HashSet(); + // Filter out service providers which enabled the single logout and back-channel logout. + for (Map.Entry entry : cacheEntry.getSessionInfoData(). + getServiceProviderList().entrySet()) { + if (entry.getValue().isDoSingleLogout() && !entry.getValue().isDoFrontChannelLogout()) { + sloSupportedIssuers.add(entry.getKey()); + } + } + // Remove service providers which enabled the single logout and back-channel logout. + for (String sloSupportedIssuer : sloSupportedIssuers) { + cacheEntry.getSessionInfoData().removeServiceProvider(sloSupportedIssuer); + if (log.isDebugEnabled()) { + log.debug("Removed back-channel SLO supported service provider from session info data with name " + + sloSupportedIssuer); + } + } + } + public String getSessionIndexFromTokenId(String tokenId) { return getSessionIndexFromCache(tokenId); } diff --git a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/util/SAMLSSOUtil.java b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/util/SAMLSSOUtil.java index a3f5d277b..5755ab9bd 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/util/SAMLSSOUtil.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/main/java/org/wso2/carbon/identity/sso/saml/util/SAMLSSOUtil.java @@ -49,6 +49,7 @@ import org.opensaml.xml.io.Unmarshaller; import org.opensaml.xml.io.UnmarshallerFactory; import org.opensaml.xml.security.SecurityException; +import org.opensaml.xml.security.SigningUtil; import org.opensaml.xml.security.x509.X509Credential; import org.opensaml.xml.signature.SignableXMLObject; import org.opensaml.xml.util.Base64; @@ -97,6 +98,7 @@ import org.wso2.carbon.identity.sso.saml.processors.SPInitLogoutRequestProcessor; import org.wso2.carbon.identity.sso.saml.processors.SPInitSSOAuthnRequestProcessor; import org.wso2.carbon.identity.sso.saml.session.SSOSessionPersistenceManager; +import org.wso2.carbon.identity.sso.saml.session.SessionInfoData; import org.wso2.carbon.identity.sso.saml.validators.IdPInitSSOAuthnRequestValidator; import org.wso2.carbon.identity.sso.saml.validators.SAML2HTTPRedirectSignatureValidator; import org.wso2.carbon.identity.sso.saml.validators.SPInitSSOAuthnRequestValidator; @@ -123,6 +125,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.net.URLDecoder; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; @@ -2000,4 +2003,214 @@ public static SAMLSSOServiceProviderDO getSPConfig(String tenantDomain, String i PrivilegedCarbonContext.endTenantFlow(); } } + + /** + * Build SAML logout request. + * + * @param serviceProviderDO SP for which the logout request is built. + * @param subject Subject identifier. + * @param sessionId Session index. + * @return Logout Request. + * @throws IdentityException If tenant domain is invalid. + */ + public static LogoutRequest buildLogoutRequest(SAMLSSOServiceProviderDO serviceProviderDO, String subject, + String sessionId) throws IdentityException { + + String destination; + if (StringUtils.isNotBlank(serviceProviderDO.getSloRequestURL())) { + destination = serviceProviderDO.getSloRequestURL(); + if (log.isDebugEnabled()) { + log.debug("Destination of the logout request is set to the " + + "SLO request URL of the SP: " + serviceProviderDO.getSloRequestURL()); + } + } else { + destination = serviceProviderDO.getAssertionConsumerUrl(); + if (log.isDebugEnabled()) { + log.debug("Destination of the logout request is set to the " + + "ACS URL of the SP: " + serviceProviderDO.getAssertionConsumerUrl()); + } + } + + SingleLogoutMessageBuilder logoutMsgBuilder = new SingleLogoutMessageBuilder(); + LogoutRequest logoutReq = logoutMsgBuilder.buildLogoutRequest(destination, serviceProviderDO.getTenantDomain(), + sessionId, subject, serviceProviderDO.getNameIDFormat(), + SAMLSSOConstants.SingleLogoutCodes.LOGOUT_USER); + + return logoutReq; + } + + /** + * Get remaining session participants for SLO except for the original issuer. + * + * @param sessionIndex Session index. + * @param issuer Original issuer. + * @param isIdPInitSLO Whether IdP initiated SLO or not. + * @return SP List with remaining session participants for SLO except for the original issuer. + */ + public static List getRemainingSessionParticipantsForSLO( + String sessionIndex, String issuer, boolean isIdPInitSLO) { + + if (isIdPInitSLO) { + issuer = null; + } + + SSOSessionPersistenceManager ssoSessionPersistenceManager = SSOSessionPersistenceManager + .getPersistenceManager(); + SessionInfoData sessionInfoData = ssoSessionPersistenceManager.getSessionInfo(sessionIndex); + + List samlssoServiceProviderDOList; + + if (sessionInfoData == null) { + return new ArrayList<>(); + } + + Map sessionsList = sessionInfoData.getServiceProviderList(); + samlssoServiceProviderDOList = new ArrayList<>(); + + for (Map.Entry entry : sessionsList.entrySet()) { + SAMLSSOServiceProviderDO serviceProviderDO = entry.getValue(); + + // Logout request should not be created for the issuer. + if (entry.getKey().equals(issuer)) { + continue; + } + + if (serviceProviderDO.isDoSingleLogout()) { + samlssoServiceProviderDOList.add(serviceProviderDO); + } + } + + return samlssoServiceProviderDOList; + } + + /** + * Get SessionInfoData. + * + * @param sessionIndex Session index. + * @return Session Info Data. + */ + public static SessionInfoData getSessionInfoData(String sessionIndex) { + + SSOSessionPersistenceManager ssoSessionPersistenceManager = SSOSessionPersistenceManager + .getPersistenceManager(); + SessionInfoData sessionInfoData = ssoSessionPersistenceManager.getSessionInfo(sessionIndex); + + return sessionInfoData; + } + + /** + * Get Session Index. + * + * @param sessionId Session id. + * @return Session Index. + */ + public static String getSessionIndex(String sessionId) { + + SSOSessionPersistenceManager ssoSessionPersistenceManager = SSOSessionPersistenceManager + .getPersistenceManager(); + String sessionIndex = ssoSessionPersistenceManager.getSessionIndexFromTokenId(sessionId); + + return sessionIndex; + } + + /** + * Construct signature for http redirect. + * + * @param httpQueryString http query string + * @param signatureAlgorithmURI signature algorithm URI + * @param credential X509Credential + */ + public static void addSignatureToHTTPQueryString(StringBuilder httpQueryString, + String signatureAlgorithmURI, X509Credential credential) { + + try { + byte[] rawSignature = SigningUtil.signWithURI(credential, signatureAlgorithmURI, + httpQueryString.toString().getBytes("UTF-8")); + + String base64Signature = Base64.encodeBytes(rawSignature, Base64.DONT_BREAK_LINES); + + if (log.isDebugEnabled()) { + log.debug("Generated digital signature value (base64-encoded) {} " + base64Signature); + } + + httpQueryString.append("&" + SAMLSSOConstants.SIGNATURE + "=" + + URLEncoder.encode(base64Signature, "UTF-8").trim()); + + } catch (org.opensaml.xml.security.SecurityException e) { + log.error("Unable to sign query string", e); + } catch (UnsupportedEncodingException e) { + // UTF-8 encoding is required to be supported by all JVMs. + log.error("Error while adding signature to HTTP query string", e); + } + } + + /** + * Validate whether the LogoutResponse is a success. + * + * @param logoutResponse Logout Response object. + * @param certificateAlias Certificate Alias. + * @param tenantDomain Tenant domain. + * @return True if Logout response state success. + * @throws IdentityException If validating XML signature fails. + */ + public static boolean validateLogoutResponse(LogoutResponse logoutResponse, String certificateAlias, + String tenantDomain) + throws IdentityException { + + if (logoutResponse.getIssuer() == null || logoutResponse.getStatus() == null || logoutResponse + .getStatus().getStatusCode() == null) { + if (log.isDebugEnabled()) { + log.debug("Logout response validation failed due to one of given values are null. " + + "Issuer: " + logoutResponse.getIssuer() + + " Status: " + logoutResponse.getStatus() + + " Status code: " + (logoutResponse.getStatus() != null ? logoutResponse.getStatus() + .getStatusCode() : null)); + } + return false; + } + + if (log.isDebugEnabled()) { + log.debug("Logout response received for issuer: " + logoutResponse.getIssuer() + .getValue() + " for tenant domain: " + tenantDomain); + } + + boolean isSignatureValid = true; + + // Certificate alias will be null if signature validation is disabled in the service provider side. + if (certificateAlias != null && logoutResponse.isSigned()) { + isSignatureValid = SAMLSSOUtil.validateXMLSignature(logoutResponse, certificateAlias, tenantDomain); + if (log.isDebugEnabled()) { + log.debug("Signature validation result for logout response for issuer: " + + logoutResponse.getIssuer().getValue() + " in tenant domain: " + tenantDomain + " is: " + + isSignatureValid); + } + } + if (SAMLSSOConstants.StatusCodes.SUCCESS_CODE.equals(logoutResponse.getStatus().getStatusCode() + .getValue()) && isSignatureValid) { + return true; + } + + return false; + } + + /** + * Decoding the logout request extracted from the query string. + * + * @param logoutRequest Logout request string. + * @param isPost Whether the request is post. + * @return Logout request XML object. + * @throws IdentityException Error in decoding. + */ + public static XMLObject decodeSamlLogoutRequest(String logoutRequest, boolean isPost) throws IdentityException { + + XMLObject samlRequest; + if (isPost) { + samlRequest = SAMLSSOUtil.unmarshall(SAMLSSOUtil.decodeForPost(logoutRequest)); + } else { + samlRequest = SAMLSSOUtil.unmarshall(SAMLSSOUtil.decode(logoutRequest)); + } + + return samlRequest; + } + } diff --git a/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/session/SSOSessionPersistenceManagerTest.java b/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/session/SSOSessionPersistenceManagerTest.java index b803276e1..cd00b1486 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/session/SSOSessionPersistenceManagerTest.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/session/SSOSessionPersistenceManagerTest.java @@ -34,6 +34,7 @@ import org.wso2.carbon.identity.sso.saml.cache.SAMLSSOSessionIndexCache; import org.wso2.carbon.identity.sso.saml.cache.SAMLSSOSessionIndexCacheEntry; import org.wso2.carbon.identity.sso.saml.cache.SAMLSSOSessionIndexCacheKey; +import org.wso2.carbon.identity.sso.saml.common.SAMLSSOProviderConstants; import org.wso2.carbon.utils.multitenancy.MultitenantConstants; import static org.mockito.MockitoAnnotations.initMocks; @@ -251,6 +252,14 @@ public static void initializeData() throws IdentityException { SSOSessionPersistenceManager.getPersistenceManager().persistSession(sessionIndex, subject, samlssoServiceProviderDO2, null, "issuer2", null); + SAMLSSOServiceProviderDO samlssoServiceProviderDO3 = new SAMLSSOServiceProviderDO(); + samlssoServiceProviderDO3.setIssuer("issuer3"); + samlssoServiceProviderDO3.setDoSingleLogout(true); + samlssoServiceProviderDO3.setDoFrontChannelLogout(true); + samlssoServiceProviderDO3.setFrontChannelLogoutBinding(SAMLSSOProviderConstants.HTTP_REDIRECT_BINDING); + SSOSessionPersistenceManager.addSessionIndexToCache("sessionId3", "sessionIndex3"); + SSOSessionPersistenceManager.getPersistenceManager().persistSession("sessionIndex3", subject, + samlssoServiceProviderDO3, null, "issuer3", null); } @DataProvider(name = "testRemoveSession1") @@ -261,7 +270,9 @@ public Object[][] data() throws IdentityException { {"sessionId", null, "sessionIndex",}, {"sessionId", "issuer", "sessionIndex"}, {"sessionId2", "issuer2", "sessionIndex"}, - {"sessionId1", "issuer1", null} + {"sessionId1", "issuer1", null}, + {"sessionId1", null, null}, + {"sessionId3", "issuer3", "sessionIndex3"} }; } diff --git a/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/util/SAMLSSOUtilTest.java b/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/util/SAMLSSOUtilTest.java index 966f76ae0..85a1847c8 100644 --- a/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/util/SAMLSSOUtilTest.java +++ b/components/org.wso2.carbon.identity.sso.saml/src/test/java/org/wso2/carbon/identity/sso/saml/util/SAMLSSOUtilTest.java @@ -35,6 +35,7 @@ import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants; import org.wso2.carbon.identity.application.common.util.IdentityApplicationManagementUtil; import org.wso2.carbon.identity.base.IdentityException; +import org.wso2.carbon.identity.core.model.SAMLSSOServiceProviderDO; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.sso.saml.SAMLSSOConstants; import org.wso2.carbon.identity.sso.saml.TestConstants; @@ -42,6 +43,8 @@ import org.wso2.carbon.identity.sso.saml.builders.X509CredentialImpl; import org.wso2.carbon.identity.sso.saml.exception.IdentitySAML2SSOException; import org.wso2.carbon.identity.sso.saml.extension.eidas.EidasExtensionProcessor; +import org.wso2.carbon.identity.sso.saml.session.SSOSessionPersistenceManager; +import org.wso2.carbon.identity.sso.saml.session.SessionInfoData; import org.wso2.carbon.idp.mgt.IdentityProviderManagementException; import org.wso2.carbon.idp.mgt.IdentityProviderManager; import org.wso2.carbon.user.api.UserStoreException; @@ -52,10 +55,18 @@ import java.util.ArrayList; import java.util.List; -import static org.mockito.Matchers.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; -import static org.testng.Assert.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; /** * Unit test cases for SAMLSSOUtil. @@ -82,6 +93,8 @@ public class SAMLSSOUtilTest extends PowerMockTestCase { @Mock private FederatedAuthenticatorConfig federatedAuthenticatorConfig; + private SessionInfoData sessionInfoData; + @ObjectFactory public IObjectFactory getObjectFactory() { return new PowerMockObjectFactory(); @@ -329,4 +342,68 @@ public void testBuildResponseStatus() { assertEquals(status.getStatusMessage().getMessage(), statusMsg, "Status Message is not properly set in " + "the Status object."); } + + @Test(dataProvider = "remainingSessionParticipantsforSloData") + public void testGetRemainingSessionParticipantsForSLO(String sessionIndex, String issuer, boolean isIdPInitSLO, + int expected) { + + initializeData(); + List samlssoServiceProviderDOList = + SAMLSSOUtil.getRemainingSessionParticipantsForSLO(sessionIndex, issuer, isIdPInitSLO); + assertEquals(samlssoServiceProviderDOList.size(), expected); + + } + + @DataProvider(name = "remainingSessionParticipantsforSloData") + public Object[][] remainingSessionParticipantsforSloData() { + + return new Object[][]{ + {null, null, true, 0}, + {null, "issuer", false, 0}, + {"sessionIndex", null, false, 2}, + {"sessionIndex", "issuer1", false, 1}, + {"sessionIndex", "issuer1", true, 2} + }; + } + + public void initializeData() { + + TestUtils.startTenantFlow(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); + SAMLSSOServiceProviderDO samlssoServiceProviderDO1 = new SAMLSSOServiceProviderDO(); + samlssoServiceProviderDO1.setIssuer("issuer1"); + samlssoServiceProviderDO1.setDoSingleLogout(true); + + SAMLSSOServiceProviderDO samlssoServiceProviderDO2 = new SAMLSSOServiceProviderDO(); + samlssoServiceProviderDO2.setIssuer("issuer2"); + samlssoServiceProviderDO2.setDoSingleLogout(true); + + SAMLSSOServiceProviderDO samlssoServiceProviderDO3 = new SAMLSSOServiceProviderDO(); + samlssoServiceProviderDO3.setIssuer("issuer3"); + samlssoServiceProviderDO3.setDoSingleLogout(false); + + sessionInfoData = new SessionInfoData(); + sessionInfoData.addServiceProvider("issuer1", samlssoServiceProviderDO1, null); + sessionInfoData.addServiceProvider("issuer2", samlssoServiceProviderDO2, null); + sessionInfoData.addServiceProvider("issuer3", samlssoServiceProviderDO3, null); + + SSOSessionPersistenceManager.addSessionIndexToCache("samlssoTokenId", "sessionIndex"); + SSOSessionPersistenceManager.addSessionInfoDataToCache("sessionIndex", sessionInfoData); + } + + @Test + public void testGetSessionInfoData() { + + initializeData(); + assertEquals(SAMLSSOUtil.getSessionInfoData("sessionIndex"), sessionInfoData); + assertNotEquals(SAMLSSOUtil.getSessionInfoData("sessionIndex1"), sessionInfoData); + } + + @Test + public void testGetSessionIndex() { + + initializeData(); + assertEquals(SAMLSSOUtil.getSessionIndex("samlssoTokenId"), "sessionIndex"); + assertNull(SAMLSSOUtil.getSessionIndex("sessionId"), "Session Index is null."); + } + } diff --git a/features/org.wso2.carbon.identity.sso.saml.server.feature/pom.xml b/features/org.wso2.carbon.identity.sso.saml.server.feature/pom.xml index 951287862..aa6515b64 100644 --- a/features/org.wso2.carbon.identity.sso.saml.server.feature/pom.xml +++ b/features/org.wso2.carbon.identity.sso.saml.server.feature/pom.xml @@ -39,6 +39,10 @@ org.wso2.carbon.identity.inbound.auth.saml2 org.wso2.carbon.identity.sso.saml
+ + org.wso2.carbon.identity.inbound.auth.saml2 + org.wso2.carbon.identity.sso.saml.common + org.wso2.orbit.org.opensaml opensaml @@ -105,6 +109,7 @@ org.wso2.carbon.identity.inbound.auth.saml2:org.wso2.carbon.identity.sso.saml + org.wso2.carbon.identity.inbound.auth.saml2:org.wso2.carbon.identity.sso.saml.common org.wso2.orbit.org.opensaml:opensaml org.wso2.orbit.joda-time:joda-time diff --git a/features/org.wso2.carbon.identity.sso.saml.ui.feature/pom.xml b/features/org.wso2.carbon.identity.sso.saml.ui.feature/pom.xml index 014bdec73..d245ba210 100644 --- a/features/org.wso2.carbon.identity.sso.saml.ui.feature/pom.xml +++ b/features/org.wso2.carbon.identity.sso.saml.ui.feature/pom.xml @@ -43,6 +43,10 @@ org.wso2.carbon.identity.inbound.auth.saml2 org.wso2.carbon.identity.sso.saml.stub + + org.wso2.carbon.identity.inbound.auth.saml2 + org.wso2.carbon.identity.sso.saml.common + org.wso2.orbit.org.opensaml opensaml @@ -78,6 +82,7 @@ org.wso2.carbon.identity.inbound.auth.saml2:org.wso2.carbon.identity.sso.saml.ui org.wso2.carbon.identity.inbound.auth.saml2:org.wso2.carbon.identity.sso.saml.stub + org.wso2.carbon.identity.inbound.auth.saml2:org.wso2.carbon.identity.sso.saml.common org.wso2.orbit.org.apache.httpcomponents:httpclient org.wso2.orbit.org.opensaml:opensaml org.wso2.orbit.joda-time:joda-time diff --git a/pom.xml b/pom.xml index e3078990c..c65bd191f 100644 --- a/pom.xml +++ b/pom.xml @@ -424,7 +424,7 @@ 4.4.22 - 5.12.240 + 5.12.258 [5.0.0, 6.0.0)