Skip to content

Commit

Permalink
[KEYCLOAK-883] - Initial tests for SAML brokering.
Browse files Browse the repository at this point in the history
  • Loading branch information
pedroigor committed Jan 29, 2015
1 parent 2065815 commit 1960c60
Show file tree
Hide file tree
Showing 21 changed files with 573 additions and 213 deletions.
Expand Up @@ -199,7 +199,7 @@ private AssertionType getAssertion(AuthenticationRequest request) throws Excepti

private void validateSignature(SAML2Request saml2Request) throws ProcessingException {
if (getConfig().isValidateSignature()) {
X509Certificate certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(getConfig().getSigningPublicKey().replaceAll("\\s", ""));
X509Certificate certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(getConfig().getSigningCertificate().replaceAll("\\s", ""));
SAMLDocumentHolder samlDocumentHolder = saml2Request.getSamlDocumentHolder();
Document samlDocument = samlDocumentHolder.getSamlDocument();

Expand Down
Expand Up @@ -55,20 +55,20 @@ public void setForceAuthn(boolean forceAuthn) {
getConfig().put("forceAuthn", String.valueOf(forceAuthn));
}

public String getSigningPublicKey() {
return getConfig().get("signingPublicKey");
public String getSigningCertificate() {
return getConfig().get("signingCertificate");
}

public void setSigningPublicKey(String signingPublicKey) {
getConfig().put("signingPublicKey", signingPublicKey);
public void setSigningCertificate(String signingCertificate) {
getConfig().put("signingCertificate", signingCertificate);
}

public String getNameIDPolicyFormat() {
return getConfig().get("nameIDPolicyFormat");
}

public void setNameIDPolicyFormat(String signingPublicKey) {
getConfig().put("nameIDPolicyFormat", signingPublicKey);
public void setNameIDPolicyFormat(String nameIDPolicyFormat) {
getConfig().put("nameIDPolicyFormat", nameIDPolicyFormat);
}

public boolean isWantAuthnRequestsSigned() {
Expand Down
Expand Up @@ -83,30 +83,30 @@ public Map<String, String> parseConfig(InputStream inputStream) {
samlIdentityProviderConfig.setPostBindingResponse(true);

List<KeyDescriptorType> keyDescriptor = idpDescriptor.getKeyDescriptor();
String defaultPublicKey = null;
String defaultCertificate = null;

if (keyDescriptor != null) {
for (KeyDescriptorType keyDescriptorType : keyDescriptor) {
Element keyInfo = keyDescriptorType.getKeyInfo();
Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));

if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
samlIdentityProviderConfig.setSigningPublicKey(x509KeyInfo.getTextContent());
samlIdentityProviderConfig.setSigningCertificate(x509KeyInfo.getTextContent());
} else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
} else if (keyDescriptorType.getUse() == null) {
defaultPublicKey = x509KeyInfo.getTextContent();
defaultCertificate = x509KeyInfo.getTextContent();
}
}
}

if (defaultPublicKey != null) {
if (samlIdentityProviderConfig.getSigningPublicKey() == null) {
samlIdentityProviderConfig.setSigningPublicKey(defaultPublicKey);
if (defaultCertificate != null) {
if (samlIdentityProviderConfig.getSigningCertificate() == null) {
samlIdentityProviderConfig.setSigningCertificate(defaultCertificate);
}

if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
samlIdentityProviderConfig.setEncryptionPublicKey(defaultPublicKey);
samlIdentityProviderConfig.setEncryptionPublicKey(defaultCertificate);
}
}

Expand Down
Expand Up @@ -52,11 +52,11 @@ <h2 class="pull-left">{{identityProvider.name}} Provider Settings</h2>
<span tooltip-placement="right" tooltip="Specifies the URI reference corresponding to a name identifier format. Defaults to urn:oasis:names:tc:SAML:2.0:nameid-format:persistent." class="fa fa-info-circle"></span>
</div>
<div class="form-group clearfix" data-ng-show="!importFile">
<label class="col-sm-2 control-label" for="signingPublicKey">Signing Public Key</label>
<label class="col-sm-2 control-label" for="signingCertificate">Validating X509 Certificate</label>
<div class="col-sm-4">
<textarea class="form-control" id="signingPublicKey" ng-model="identityProvider.config.signingPublicKey"/>
<textarea class="form-control" id="signingCertificate" ng-model="identityProvider.config.signingCertificate"/>
</div>
<span tooltip-placement="right" tooltip="The public key that must be used to check for signatures." class="fa fa-info-circle"></span>
<span tooltip-placement="right" tooltip="The certificate in PEM format that must be used to check for signatures." class="fa fa-info-circle"></span>
</div>
<div class="form-group" data-ng-show="!importFile">
<label class="col-sm-2 control-label" for="wantAuthnRequestsSigned">Want AuthnRequests Signed</label>
Expand Down
Expand Up @@ -124,7 +124,12 @@ public CachedRealm(RealmCache cache, RealmProvider delegate, RealmModel model) {

requiredCredentials = model.getRequiredCredentials();
userFederationProviders = model.getUserFederationProviders();
identityProviders = model.getIdentityProviders();

this.identityProviders = new ArrayList<IdentityProviderModel>();

for (IdentityProviderModel identityProviderModel : model.getIdentityProviders()) {
this.identityProviders.add(new IdentityProviderModel(identityProviderModel));
}

smtpConfig.putAll(model.getSmtpConfig());
browserSecurityHeaders.putAll(model.getBrowserSecurityHeaders());
Expand Down
Expand Up @@ -72,7 +72,7 @@ public static URI identityProviderAuthnRequest(URI baseURI, IdentityProviderMode
UriBuilder uriBuilder = UriBuilder.fromUri(baseURI)
.path(AuthenticationBrokerResource.class)
.path(AuthenticationBrokerResource.class, "performLogin")
.replaceQueryParam("provider_id", identityProvider.getProviderId());
.replaceQueryParam("provider_id", identityProvider.getId());

if (accessCode != null) {
uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
Expand Down
@@ -0,0 +1,137 @@
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* 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.keycloak.testsuite.broker;

import org.codehaus.jackson.map.ObjectMapper;
import org.junit.ClassRule;
import org.junit.Rule;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionStatus;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;

import java.io.IOException;
import java.net.URL;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
* @author pedroigor
*/
public abstract class AbstractIdentityProviderTest {

@ClassRule
public static AbstractKeycloakRule brokerServerRule = new AbstractKeycloakRule() {

@Override
protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
server.importRealm(getClass().getResourceAsStream("/broker-test/test-realm-with-broker.json"));
URL url = getClass().getResource("/broker-test/test-app-keycloak.json");
deployApplication("test-app", "/test-app", UserSessionStatusServlet.class, url.getPath(), "manager");
}
};

@Rule
public WebRule webRule = new WebRule(this);

@WebResource
private WebDriver driver;

@WebResource
private LoginPage loginPage;

@WebResource
private LoginUpdateProfilePage updateProfilePage;

protected void assertSuccessfulAuthentication(String providerId) {
this.driver.navigate().to("http://localhost:8081/test-app/");

assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));

// choose the identity provider
this.loginPage.clickSocial(providerId);

assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/realms/realm-with-saml-identity-provider/protocol/saml"));

// log in to identity provider
this.loginPage.login("saml.user", "password");

assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/broker/realm-with-broker/" + providerId));

// update profile
this.updateProfilePage.assertCurrent();

String userEmail = "new@email.com";
String userFirstName = "New first";
String userLastName = "New last";

this.updateProfilePage.update(userFirstName, userLastName, userEmail);

// authenticated and redirected to app
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app/"));

KeycloakSession samlServerSession = brokerServerRule.startSession();
RealmModel brokerRealm = samlServerSession.realms().getRealm("realm-with-broker");

UserModel federatedUser = samlServerSession.users().getUserByEmail(userEmail, brokerRealm);

// user created
assertNotNull(federatedUser);
assertEquals(userFirstName, federatedUser.getFirstName());
assertEquals(userLastName, federatedUser.getLastName());

driver.navigate().to("http://localhost:8081/test-app/logout");
driver.navigate().to("http://localhost:8081/test-app/");

assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));

// choose the identity provider
this.loginPage.clickSocial(providerId);

// already authenticated in saml idp and redirected to app
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app/"));
}

private UserSessionStatus retrieveSessionStatus() {
UserSessionStatus sessionStatus = null;

try {
ObjectMapper objectMapper = new ObjectMapper();
String pageSource = this.driver.getPageSource();

sessionStatus = objectMapper.readValue(pageSource.getBytes(), UserSessionStatus.class);

assertNotNull(retrieveSessionStatus());
} catch (IOException e) {
throw new RuntimeException("Could not retrieve session status.", e);
}

return sessionStatus;
}

}
Expand Up @@ -24,7 +24,7 @@
import org.keycloak.social.SocialIdentityProvider;
import org.keycloak.social.SocialIdentityProviderFactory;
import org.keycloak.testsuite.broker.provider.CustomIdentityProvider;
import org.keycloak.testsuite.broker.social.CustomSocialProvider;
import org.keycloak.testsuite.broker.provider.social.CustomSocialProvider;

import java.util.Set;

Expand Down
Expand Up @@ -144,7 +144,11 @@ private void assertIdentityProviderConfig(List<IdentityProviderModel> identityPr
String providerId = identityProvider.getProviderId();

if (SAMLIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
assertSamlIdentityProviderConfig(identityProvider);
if (identityProvider.getId().equals("saml-signed-idp")) {
assertSamlIdentityProviderConfig(identityProvider);
} else {
continue;
}
} else if (GoogleIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
assertGoogleIdentityProviderConfig(identityProvider);
} else if (OIDCIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
Expand Down Expand Up @@ -184,14 +188,14 @@ private void assertSamlIdentityProviderConfig(IdentityProviderModel identityProv
SAMLIdentityProvider samlIdentityProvider = new SAMLIdentityProviderFactory().create(identityProvider);
SAMLIdentityProviderConfig config = samlIdentityProvider.getConfig();

assertEquals("saml-idp", config.getId());
assertEquals("saml-signed-idp", config.getId());
assertEquals(SAMLIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
assertEquals("SAML IdP", config.getName());
assertEquals("SAML Signed IdP", config.getName());
assertEquals(true, config.isEnabled());
assertEquals(true, config.isUpdateProfileFirstLogin());
assertEquals("http://localhost:8080/idp/", config.getSingleSignOnServiceUrl());
assertEquals("http://localhost:8082/auth/realms/realm-with-saml-identity-provider/protocol/saml", config.getSingleSignOnServiceUrl());
assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", config.getNameIDPolicyFormat());
assertEquals("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", config.getSigningPublicKey());
assertEquals("MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin", config.getSigningCertificate());
assertEquals(true, config.isWantAuthnRequestsSigned());
assertEquals(true, config.isForceAuthn());
assertEquals(true, config.isPostBindingAuthnRequest());
Expand Down Expand Up @@ -258,10 +262,10 @@ private void assertTwitterIdentityProviderConfig(IdentityProviderModel identityP
}

private RealmModel installTestRealm() throws IOException {
RealmRepresentation realmRepresentation = loadJson("model/test-realm-with-identity-provider.json");
RealmRepresentation realmRepresentation = loadJson("broker-test/test-realm-with-broker.json");

assertNotNull(realmRepresentation);
assertEquals("test-realm-with-identity-provider", realmRepresentation.getRealm());
assertEquals("realm-with-broker", realmRepresentation.getRealm());

RealmModel realmModel = this.realmManager.importRealm(realmRepresentation);

Expand Down
@@ -0,0 +1,34 @@
package org.keycloak.testsuite.broker;

import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testutils.KeycloakServer;

/**
* @author pedroigor
*/
public class SAMLKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderTest {

@ClassRule
public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() {

@Override
protected void configureServer(KeycloakServer server) {
server.getConfig().setPort(8082);
}

@Override
protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
server.importRealm(getClass().getResourceAsStream("/broker-test/test-broker-realm-with-saml.json"));
}
};

@Test
public void testSuccessfulAuthentication() {
assertSuccessfulAuthentication("saml-idp-basic");
}
}
@@ -0,0 +1,34 @@
package org.keycloak.testsuite.broker;

import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testutils.KeycloakServer;

/**
* @author pedroigor
*/
public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractIdentityProviderTest {

@ClassRule
public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() {

@Override
protected void configureServer(KeycloakServer server) {
server.getConfig().setPort(8082);
}

@Override
protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
server.importRealm(getClass().getResourceAsStream("/broker-test/test-broker-realm-with-saml-with-signature.json"));
}
};

@Test
public void testSuccessfulAuthentication() {
assertSuccessfulAuthentication("saml-signed-idp");
}
}
Expand Up @@ -15,7 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.broker.social;
package org.keycloak.testsuite.broker.provider.social;

import org.keycloak.broker.provider.AbstractIdentityProvider;
import org.keycloak.broker.provider.AuthenticationRequest;
Expand Down
Expand Up @@ -15,7 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.broker.social;
package org.keycloak.testsuite.broker.provider.social;

import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
import org.keycloak.models.IdentityProviderModel;
Expand Down

0 comments on commit 1960c60

Please sign in to comment.