267 lines
14 KiB
Java
267 lines
14 KiB
Java
package org.keycloak.testsuite.adapter.servlet;
|
|
|
|
import org.jboss.arquillian.container.test.api.Deployment;
|
|
import org.jboss.arquillian.graphene.page.Page;
|
|
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
|
import org.junit.Assert;
|
|
import org.junit.Test;
|
|
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
|
import org.keycloak.admin.client.resource.ClientResource;
|
|
import org.keycloak.admin.client.resource.ProtocolMappersResource;
|
|
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
|
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
|
import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
|
|
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
|
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
|
|
import org.keycloak.protocol.saml.mappers.RoleListMapper;
|
|
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
|
import org.keycloak.saml.SAML2ErrorResponseBuilder;
|
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
|
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
|
import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
|
|
import org.keycloak.testsuite.adapter.page.Employee2Servlet;
|
|
import org.keycloak.testsuite.adapter.page.EmployeeSigServlet;
|
|
import org.keycloak.testsuite.admin.ApiUtil;
|
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
|
import org.keycloak.testsuite.saml.AbstractSamlTest;
|
|
import org.keycloak.testsuite.util.Matchers;
|
|
import org.keycloak.testsuite.util.SamlClient;
|
|
import org.keycloak.testsuite.util.SamlClientBuilder;
|
|
import org.keycloak.testsuite.util.WaitUtils;
|
|
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
|
|
import org.openqa.selenium.By;
|
|
import org.w3c.dom.Document;
|
|
|
|
import javax.ws.rs.core.Response;
|
|
import java.net.URI;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import static org.hamcrest.Matchers.containsString;
|
|
import static org.junit.Assert.assertThat;
|
|
import static org.keycloak.testsuite.admin.ApiUtil.getCreatedId;
|
|
import static org.keycloak.testsuite.saml.AbstractSamlTest.REALM_PRIVATE_KEY;
|
|
import static org.keycloak.testsuite.saml.AbstractSamlTest.REALM_PUBLIC_KEY;
|
|
import static org.keycloak.testsuite.util.Matchers.bodyHC;
|
|
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
|
|
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
|
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
|
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
|
|
|
/**
|
|
* @author mhajas
|
|
*/
|
|
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
|
|
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
|
|
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY_DEPRECATED)
|
|
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
|
|
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
|
|
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
|
|
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT7)
|
|
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT8)
|
|
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT9)
|
|
@AppServerContainer(ContainerConstants.APP_SERVER_JETTY92)
|
|
@AppServerContainer(ContainerConstants.APP_SERVER_JETTY93)
|
|
@AppServerContainer(ContainerConstants.APP_SERVER_JETTY94)
|
|
public class SAMLLoginResponseHandlingTest extends AbstractSAMLServletAdapterTest {
|
|
|
|
@Page
|
|
protected Employee2Servlet employee2ServletPage;
|
|
|
|
@Page
|
|
protected EmployeeSigServlet employeeSigServletPage;
|
|
|
|
@Deployment(name = Employee2Servlet.DEPLOYMENT_NAME)
|
|
protected static WebArchive employee2() {
|
|
return samlServletDeployment(Employee2Servlet.DEPLOYMENT_NAME, WEB_XML_WITH_ACTION_FILTER, SendUsernameServlet.class, AdapterActionsFilter.class, PublicKeyLocator.class);
|
|
}
|
|
|
|
@Deployment(name = EmployeeSigServlet.DEPLOYMENT_NAME)
|
|
protected static WebArchive employeeSig() {
|
|
return samlServletDeployment(EmployeeSigServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
|
|
}
|
|
|
|
@Test
|
|
public void testNilAttributeValueAttribute() {
|
|
beginAuthenticationAndLogin(employee2ServletPage, SamlClient.Binding.POST)
|
|
.processSamlResponse(SamlClient.Binding.POST) // Update response with Nil attribute
|
|
.transformObject(ob -> {
|
|
assertThat(ob, Matchers.isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
|
|
ResponseType resp = (ResponseType) ob;
|
|
|
|
Set<StatementAbstractType> statements = resp.getAssertions().get(0).getAssertion().getStatements();
|
|
|
|
AttributeStatementType attributeType = (AttributeStatementType) statements.stream()
|
|
.filter(statement -> statement instanceof AttributeStatementType)
|
|
.findFirst().orElse(new AttributeStatementType());
|
|
|
|
AttributeType attr = new AttributeType("attribute-with-null-attribute-value");
|
|
attr.addAttributeValue(null);
|
|
|
|
attributeType.addAttribute(new AttributeStatementType.ASTChoiceType(attr));
|
|
resp.getAssertions().get(0).getAssertion().addStatement(attributeType);
|
|
|
|
return ob;
|
|
})
|
|
.build()
|
|
.navigateTo(employee2ServletPage.getUriBuilder().clone().path("getAttributes").build())
|
|
.execute(response -> {
|
|
Assert.assertThat(response, statusCodeIsHC(Response.Status.OK));
|
|
Assert.assertThat(response, bodyHC(containsString("attribute-with-null-attribute-value: <br />")));
|
|
}
|
|
);
|
|
}
|
|
|
|
@Test
|
|
public void testErrorHandlingUnsigned() throws Exception {
|
|
SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder()
|
|
.destination(employeeSigServletPage.toString() + "saml")
|
|
.issuer("http://localhost:" + System.getProperty("auth.server.http.port", "8180") + "/realms/demo")
|
|
.status(JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
|
|
Document document = builder.buildDocument();
|
|
|
|
new SamlClientBuilder()
|
|
.addStep((client, currentURI, currentResponse, context) ->
|
|
SamlClient.Binding.REDIRECT.createSamlUnsignedResponse(URI.create(employeeSigServletPage.toString() + "/saml"), null, document))
|
|
.execute(closeableHttpResponse -> Assert.assertThat(closeableHttpResponse, bodyHC(containsString("INVALID_SIGNATURE"))));
|
|
}
|
|
|
|
@Test
|
|
public void testErrorHandlingSigned() throws Exception {
|
|
SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder()
|
|
.destination(employeeSigServletPage.toString() + "saml")
|
|
.issuer("http://localhost:" + System.getProperty("auth.server.http.port", "8180") + "/realms/demo")
|
|
.status(JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
|
|
Document document = builder.buildDocument();
|
|
|
|
new SamlClientBuilder()
|
|
.addStep((client, currentURI, currentResponse, context) ->
|
|
SamlClient.Binding.REDIRECT.createSamlSignedResponse(URI.create(employeeSigServletPage.toString() + "/saml"), null, document, REALM_PRIVATE_KEY, REALM_PUBLIC_KEY))
|
|
.execute(closeableHttpResponse -> Assert.assertThat(closeableHttpResponse, bodyHC(containsString("ERROR_STATUS"))));
|
|
}
|
|
|
|
@Test
|
|
public void testAttributes() throws Exception {
|
|
ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), AbstractSamlTest.SAML_CLIENT_ID_EMPLOYEE_2);
|
|
ProtocolMappersResource protocolMappersResource = clientResource.getProtocolMappers();
|
|
|
|
Map<String, String> config = new LinkedHashMap<>();
|
|
config.put("attribute.nameformat", "Basic");
|
|
config.put("user.attribute", "topAttribute");
|
|
config.put("attribute.name", "topAttribute");
|
|
getCleanup().addCleanup(createProtocolMapper(protocolMappersResource, "topAttribute", "saml", "saml-user-attribute-mapper", config));
|
|
|
|
config = new LinkedHashMap<>();
|
|
config.put("attribute.nameformat", "Basic");
|
|
config.put("user.attribute", "level2Attribute");
|
|
config.put("attribute.name", "level2Attribute");
|
|
getCleanup().addCleanup(createProtocolMapper(protocolMappersResource, "level2Attribute", "saml", "saml-user-attribute-mapper", config));
|
|
|
|
config = new LinkedHashMap<>();
|
|
config.put("attribute.nameformat", "Basic");
|
|
config.put("single", "true");
|
|
config.put("attribute.name", "group");
|
|
getCleanup().addCleanup(createProtocolMapper(protocolMappersResource, "groups", "saml", "saml-group-membership-mapper", config));
|
|
|
|
setRolesToCheck("manager,user");
|
|
|
|
employee2ServletPage.navigateTo();
|
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
|
testRealmSAMLPostLoginPage.form().login("level2GroupUser", "password");
|
|
|
|
driver.navigate().to(employee2ServletPage.getUriBuilder().clone().path("getAttributes").build().toURL());
|
|
waitUntilElement(By.xpath("//body")).text().contains("topAttribute: true");
|
|
waitUntilElement(By.xpath("//body")).text().contains("level2Attribute: true");
|
|
waitUntilElement(By.xpath("//body")).text().contains(X500SAMLProfileConstants.EMAIL.get() + ": level2@redhat.com");
|
|
waitUntilElement(By.xpath("//body")).text().not().contains("group: []");
|
|
waitUntilElement(By.xpath("//body")).text().not().contains("group: null");
|
|
waitUntilElement(By.xpath("//body")).text().not().contains("group: <br />");
|
|
waitUntilElement(By.xpath("//body")).text().contains("group: level2");
|
|
|
|
employee2ServletPage.logout();
|
|
checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage);
|
|
|
|
setRolesToCheck("manager,employee,user");
|
|
|
|
employee2ServletPage.navigateTo();
|
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
|
|
|
driver.navigate().to(employee2ServletPage.getUriBuilder().clone().path("getAttributes").build().toURL());
|
|
waitUntilElement(By.xpath("//body")).text().contains(X500SAMLProfileConstants.EMAIL.get() + ": bburke@redhat.com");
|
|
waitUntilElement(By.xpath("//body")).text().contains("friendly email: bburke@redhat.com");
|
|
waitUntilElement(By.xpath("//body")).text().contains("phone: 617");
|
|
waitUntilElement(By.xpath("//body")).text().not().contains("friendly phone:");
|
|
|
|
driver.navigate().to(employee2ServletPage.getUriBuilder().clone().path("getAssertionFromDocument").build().toURL());
|
|
waitForPageToLoad();
|
|
Assert.assertEquals("", driver.getPageSource());
|
|
|
|
employee2ServletPage.logout();
|
|
checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage);
|
|
|
|
config = new LinkedHashMap<>();
|
|
config.put("attribute.value", "hard");
|
|
config.put("attribute.nameformat", "Basic");
|
|
config.put("attribute.name", "hardcoded-attribute");
|
|
getCleanup().addCleanup(createProtocolMapper(protocolMappersResource, "hardcoded-attribute", "saml", "saml-hardcode-attribute-mapper", config));
|
|
|
|
config = new LinkedHashMap<>();
|
|
config.put("role", "hardcoded-role");
|
|
getCleanup().addCleanup(createProtocolMapper(protocolMappersResource, "hardcoded-role", "saml", "saml-hardcode-role-mapper", config));
|
|
|
|
config = new LinkedHashMap<>();
|
|
config.put("new.role.name", "pee-on");
|
|
config.put("role", "http://localhost:8280/employee/.employee");
|
|
getCleanup().addCleanup(createProtocolMapper(protocolMappersResource, "renamed-employee-role", "saml", "saml-role-name-mapper", config));
|
|
|
|
for (ProtocolMapperRepresentation mapper : clientResource.toRepresentation().getProtocolMappers()) {
|
|
if (mapper.getName().equals("role-list")) {
|
|
protocolMappersResource.delete(mapper.getId());
|
|
Map<String, String> origConfig = new HashMap<>(mapper.getConfig());
|
|
|
|
mapper.setId(null);
|
|
mapper.getConfig().put(RoleListMapper.SINGLE_ROLE_ATTRIBUTE, "true");
|
|
mapper.getConfig().put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, "memberOf");
|
|
|
|
try (Response response = protocolMappersResource.createMapper(mapper)) {
|
|
String createdId = getCreatedId(response);
|
|
getCleanup().addCleanup((Runnable) () -> {
|
|
protocolMappersResource.delete(createdId);
|
|
mapper.setConfig(origConfig);
|
|
protocolMappersResource.createMapper(mapper).close();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
setRolesToCheck("pee-on,el-jefe,manager,hardcoded-role");
|
|
|
|
config = new LinkedHashMap<>();
|
|
config.put("new.role.name", "el-jefe");
|
|
config.put("role", "user");
|
|
getCleanup().addCleanup(createProtocolMapper(protocolMappersResource, "renamed-role", "saml", "saml-role-name-mapper", config));
|
|
|
|
employee2ServletPage.navigateTo();
|
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
|
|
|
driver.navigate().to(employee2ServletPage.getUriBuilder().clone().path("getAttributes").build().toURL());
|
|
waitUntilElement(By.xpath("//body")).text().contains("hardcoded-attribute: hard");
|
|
employee2ServletPage.checkRolesEndPoint(false);
|
|
employee2ServletPage.logout();
|
|
checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage);
|
|
}
|
|
|
|
private void setRolesToCheck(String roles) throws Exception {
|
|
employee2ServletPage.navigateTo();
|
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
|
driver.navigate().to(employee2ServletPage.getUriBuilder().clone().path("setCheckRoles").queryParam("roles", roles).build().toURL());
|
|
WaitUtils.waitUntilElement(By.tagName("body")).text().contains("These roles will be checked:");
|
|
employee2ServletPage.logout();
|
|
}
|
|
}
|