Merge pull request #2430 from mstruk/assert-events

KEYCLOAK-2589 KEYCLOAK-2607 KEYCLOAK-2597 Port AssertEvents to integration-arquillian
This commit is contained in:
Bill Burke 2016-03-30 15:16:25 -04:00
commit 020d090aee
23 changed files with 1533 additions and 133 deletions

View file

@ -105,4 +105,37 @@ public class EventRepresentation {
public void setDetails(Map<String, String> details) {
this.details = details;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EventRepresentation that = (EventRepresentation) o;
if (time != that.time) return false;
if (type != null ? !type.equals(that.type) : that.type != null) return false;
if (realmId != null ? !realmId.equals(that.realmId) : that.realmId != null) return false;
if (clientId != null ? !clientId.equals(that.clientId) : that.clientId != null) return false;
if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false;
if (sessionId != null ? !sessionId.equals(that.sessionId) : that.sessionId != null) return false;
if (ipAddress != null ? !ipAddress.equals(that.ipAddress) : that.ipAddress != null) return false;
if (error != null ? !error.equals(that.error) : that.error != null) return false;
return !(details != null ? !details.equals(that.details) : that.details != null);
}
@Override
public int hashCode() {
int result = (int) (time ^ (time >>> 32));
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (realmId != null ? realmId.hashCode() : 0);
result = 31 * result + (clientId != null ? clientId.hashCode() : 0);
result = 31 * result + (userId != null ? userId.hashCode() : 0);
result = 31 * result + (sessionId != null ? sessionId.hashCode() : 0);
result = 31 * result + (ipAddress != null ? ipAddress.hashCode() : 0);
result = 31 * result + (error != null ? error.hashCode() : 0);
result = 31 * result + (details != null ? details.hashCode() : 0);
return result;
}
}

View file

@ -129,7 +129,7 @@ public class GroupResource {
child = realm.createGroup(rep.getName());
updateGroup(rep, child);
URI uri = uriInfo.getBaseUriBuilder()
.path(uriInfo.getMatchedURIs().get(1))
.path(uriInfo.getMatchedURIs().get(2))
.path(child.getId()).build();
builder.status(201).location(uri);
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();

View file

@ -0,0 +1,42 @@
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ 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.
-->
<project name="inject-provider" basedir="." default="inject-provider">
<scriptdef name="inject-provider" language="javascript" manager="bsf">
<attribute name="path"/>
<![CDATA[
importClass(Packages.java.io.File);
importClass(Packages.org.keycloak.util.JsonSerialization);
path = attributes.get("path");
file = new File(path);
root = JsonSerialization.mapper.readTree(file);
// inject provider
providers = root.withArray("providers");
providers.add("module:org.keycloak.testsuite.integration-arquillian-event-queue");
// save file
JsonSerialization.prettyMapper.writeValue(file, root);
]]>
</scriptdef>
<target name="inject-provider">
<inject-provider path="${auth.server.home}/standalone/configuration/keycloak-server.json"/>
</target>
</project>

View file

@ -100,13 +100,102 @@
</artifactItems>
</configuration>
</execution>
<execution>
<id>copy-event-queue-provider</id>
<phase>generate-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-event-queue</artifactId>
<version>${project.version}</version>
<type>jar</type>
<overWrite>false</overWrite>
<outputDirectory>${auth.server.home}/modules/org/keycloak/testsuite/integration-arquillian-event-queue/main</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
<execution>
<id>install-event-queue-module</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-event-queue</artifactId>
<version>${project.version}</version>
<type>jar</type>
<outputDirectory>${auth.server.home}/modules</outputDirectory>
<includes>**/module.xml</includes>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>inject-into-keycloak-server-json</id>
<phase>process-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<ant antfile="../build.xml" inheritRefs="true">
<target name="inject-provider"/>
</ant>
</target>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>ant-contrib</groupId>
<artifactId>ant-contrib</artifactId>
<version>1.0b3</version>
<exclusions>
<exclusion>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant-apache-bsf</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>org.apache.bsf</groupId>
<artifactId>bsf-api</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>rhino</groupId>
<artifactId>js</artifactId>
<version>1.7R2</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<artifactId>maven-enforcer-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>

View file

@ -30,6 +30,7 @@
<name>Auth Server</name>
<modules>
<module>services</module>
<module>jboss</module>
<module>undertow</module>
</modules>

View file

@ -0,0 +1,81 @@
<?xml version="1.0"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ 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.
-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-servers-auth-server-services</artifactId>
<version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>integration-arquillian-event-queue</artifactId>
<name>Auth Server Services - Event Queue</name>
<dependencies>
<!-- Keycloak deps for tests -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-dependencies-server-all</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>

View file

@ -0,0 +1,66 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.events;
import org.keycloak.events.Event;
import org.keycloak.util.JsonSerialization;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class AssertEventsServletFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if ("/event-queue".equals(req.getRequestURI().substring(req.getContextPath().length()))) {
BlockingQueue<Event> events = EventsListenerProvider.getInstance();
HttpServletResponse resp = (HttpServletResponse) servletResponse;
resp.setContentType("application/json");
Event event = events.poll();
if (event != null) {
JsonSerialization.writeValueToStream(servletResponse.getOutputStream(), event);
} else {
resp.setStatus(204);
}
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
@Override
public void destroy() {
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.events;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.admin.AdminEvent;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class EventsListenerProvider implements EventListenerProvider {
private static final BlockingQueue<Event> events = new LinkedBlockingQueue<Event>();
@Override
public void onEvent(Event event) {
events.add(event);
}
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
// TODO: implement if needed
}
@Override
public void close() {
}
public static BlockingQueue<Event> getInstance() {
return events;
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.events;
import org.keycloak.Config;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class EventsListenerProviderFactory implements EventListenerProviderFactory {
private static final EventsListenerProvider INSTANCE = new EventsListenerProvider();
private EventsServer server = new EventsServer();
@Override
public EventListenerProvider create(KeycloakSession session) {
return INSTANCE;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
server.start();
}
@Override
public void close() {
server.stop();
}
@Override
public String getId() {
return "event-queue";
}
}

View file

@ -0,0 +1,95 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.events;
import io.undertow.Undertow;
import io.undertow.server.handlers.PathHandler;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.DeploymentManager;
import io.undertow.servlet.api.FilterInfo;
import io.undertow.servlet.api.ServletContainer;
import io.undertow.servlet.api.ServletContainer.Factory;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
import java.util.logging.Logger;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class EventsServer {
private static final Logger log = Logger.getLogger(EventsServer.class.getName());
private String rootPath = "/";
private int port;
private Undertow server;
public EventsServer() {
int eventsPort = Integer.parseInt(System.getProperty("auth.server.events.http.port", "8089"));
int portOffset = Integer.parseInt(System.getProperty("auth.server.port.offset", "0"));
int jbossPortOffset = Integer.parseInt(System.getProperty("jboss.socket.binding.port-offset", "-1"));
log.fine("Configuration:");
log.fine(" auth.server.events.http.port: " + eventsPort);
log.fine(" auth.server.port.offset: " + portOffset);
log.fine(" jboss.socket.binding.port-offset: " + jbossPortOffset);
port = eventsPort + (jbossPortOffset != -1 ? jbossPortOffset : portOffset);
}
public void start() {
PathHandler root = new PathHandler();
this.server = Undertow.builder().addHttpListener(port, "localhost").setHandler(root).build();
this.server.start();
ServletContainer container = Factory.newInstance();
DeploymentInfo di = new DeploymentInfo();
di.setClassLoader(getClass().getClassLoader());
di.setContextPath(rootPath);
di.setDeploymentName("testing-event-queue");
FilterInfo filter = Servlets.filter("EventsFilter", AssertEventsServletFilter.class);
di.addFilter(filter);
di.addFilterUrlMapping("EventsFilter", "/event-queue", DispatcherType.REQUEST);
DeploymentManager manager = container.addDeployment(di);
manager.deploy();
try {
root.addPrefixPath(rootPath, manager.start());
} catch (ServletException e) {
throw new RuntimeException(e);
}
log.info("Started EventsServer on port: " + port);
}
public void stop() {
this.server.stop();
}
public void setRootPath(String rootPath) {
this.rootPath = rootPath;
}
public void setPort(int port) {
this.port = port;
}
}

View file

@ -0,0 +1,35 @@
#
# Copyright 2016 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# 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.
#
#
# Copyright 2016 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# 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.
#
org.keycloak.testsuite.events.EventsListenerProviderFactory

View file

@ -0,0 +1,30 @@
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ 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.
-->
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.testsuite.integration-arquillian-event-queue">
<resources>
<resource-root path="integration-arquillian-event-queue-${project.version}.jar"/>
</resources>
<dependencies>
<module name="javax.api"/>
<module name="javax.servlet.api"/>
<module name="io.undertow.core"/>
<module name="io.undertow.servlet"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-server-spi"/>
</dependencies>
</module>

View file

@ -0,0 +1,36 @@
<?xml version="1.0"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ 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.
-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-servers-auth-server</artifactId>
<version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>integration-arquillian-servers-auth-server-services</artifactId>
<packaging>pom</packaging>
<name>Auth Server Services</name>
<modules>
<module>event-queue</module>
</modules>
</project>

View file

@ -57,6 +57,11 @@
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-event-queue</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>

View file

@ -0,0 +1,365 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Assert;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.common.util.PemUtils;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.TokenUtil;
import java.io.IOException;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AssertEvents {
static final String DEFAULT_CLIENT_ID = "test-app";
static final String DEFAULT_IP_ADDRESS = "127.0.0.1";
static final String DEFAULT_REALM = "test";
static final String DEFAULT_USERNAME = "test-user@localhost";
String defaultRedirectUri = "http://localhost:8081/app/auth";
String defaultEventsQueueUri = "http://localhost:8092";
private RealmResource realmResource;
private RealmRepresentation realmRep;
private AbstractKeycloakTest context;
private PublicKey realmPublicKey;
private UserRepresentation defaultUser;
public AssertEvents(AbstractKeycloakTest ctx) throws Exception {
context = ctx;
realmResource = context.adminClient.realms().realm(DEFAULT_REALM);
realmRep = realmResource.toRepresentation();
String pubKeyString = realmRep.getPublicKey();
realmPublicKey = PemUtils.decodePublicKey(pubKeyString);
defaultUser = getUser(DEFAULT_USERNAME);
if (defaultUser == null) {
throw new RuntimeException("Default user does not exist: " + DEFAULT_USERNAME + ". Make sure to add it to your test realm.");
}
defaultEventsQueueUri = getAuthServerEventsQueueUri();
}
String getAuthServerEventsQueueUri() {
int httpPort = Integer.parseInt(System.getProperty("auth.server.event.http.port", "8089"));
int portOffset = Integer.parseInt(System.getProperty("auth.server.port.offset", "0"));
return "http://localhost:" + (httpPort + portOffset);
}
public EventRepresentation poll() {
EventRepresentation event = fetchNextEvent();
Assert.assertNotNull("Event expected", event);
return event;
}
public void clear() {
realmResource.clearEvents();
}
public ExpectedEvent expectRequiredAction(EventType event) {
return expectLogin().event(event).removeDetail(Details.CONSENT).session(isUUID());
}
public ExpectedEvent expectLogin() {
return expect(EventType.LOGIN)
.detail(Details.CODE_ID, isCodeId())
//.detail(Details.USERNAME, DEFAULT_USERNAME)
//.detail(Details.AUTH_METHOD, OIDCLoginProtocol.LOGIN_PROTOCOL)
//.detail(Details.AUTH_TYPE, AuthorizationEndpoint.CODE_AUTH_TYPE)
.detail(Details.REDIRECT_URI, defaultRedirectUri)
.detail(Details.CONSENT, Details.CONSENT_VALUE_NO_CONSENT_REQUIRED)
.session(isUUID());
}
public ExpectedEvent expectClientLogin() {
return expect(EventType.CLIENT_LOGIN)
.detail(Details.CODE_ID, isCodeId())
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
.detail(Details.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)
.removeDetail(Details.CODE_ID)
.session(isUUID());
}
public ExpectedEvent expectSocialLogin() {
return expect(EventType.LOGIN)
.detail(Details.CODE_ID, isCodeId())
.detail(Details.USERNAME, DEFAULT_USERNAME)
.detail(Details.AUTH_METHOD, "form")
.detail(Details.REDIRECT_URI, defaultRedirectUri)
.session(isUUID());
}
public ExpectedEvent expectCodeToToken(String codeId, String sessionId) {
return expect(EventType.CODE_TO_TOKEN)
.detail(Details.CODE_ID, codeId)
.detail(Details.TOKEN_ID, isUUID())
.detail(Details.REFRESH_TOKEN_ID, isUUID())
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH)
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
.session(sessionId);
}
public ExpectedEvent expectRefresh(String refreshTokenId, String sessionId) {
return expect(EventType.REFRESH_TOKEN)
.detail(Details.TOKEN_ID, isUUID())
.detail(Details.REFRESH_TOKEN_ID, refreshTokenId)
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH)
.detail(Details.UPDATED_REFRESH_TOKEN_ID, isUUID())
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
.session(sessionId);
}
public ExpectedEvent expectLogout(String sessionId) {
return expect(EventType.LOGOUT).client((String) null)
.detail(Details.REDIRECT_URI, defaultRedirectUri)
.session(sessionId);
}
public ExpectedEvent expectRegister(String username, String email) {
UserRepresentation user = username != null ? getUser(username) : null;
return expect(EventType.REGISTER)
.user(user != null ? user.getId() : null)
.detail(Details.USERNAME, username)
.detail(Details.EMAIL, email)
.detail(Details.REGISTER_METHOD, "form")
.detail(Details.REDIRECT_URI, defaultRedirectUri);
}
public ExpectedEvent expectAccount(EventType event) {
return expect(event).client("account");
}
public ExpectedEvent expect(EventType event) {
return new ExpectedEvent()
.realm(realmRep.getId())
.client(DEFAULT_CLIENT_ID)
.user(defaultUser.getId())
.ipAddress(DEFAULT_IP_ADDRESS)
.session((String) null)
.event(event);
}
UserRepresentation getUser(String username) {
List<UserRepresentation> result = realmResource.users().search(username, null, null, null, 0, 1);
return result.size() > 0 ? result.get(0) : null;
}
public PublicKey getRealmPublicKey() {
return realmPublicKey;
}
public class ExpectedEvent {
private EventRepresentation expected = new EventRepresentation();
private Matcher<String> userId;
private Matcher<String> sessionId;
private HashMap<String, Matcher<String>> details;
public ExpectedEvent realm(RealmRepresentation realm) {
expected.setRealmId(realm.getId());
return this;
}
public ExpectedEvent realm(String realmId) {
expected.setRealmId(realmId);
return this;
}
public ExpectedEvent client(ClientRepresentation client) {
expected.setClientId(client.getClientId());
return this;
}
public ExpectedEvent client(String clientId) {
expected.setClientId(clientId);
return this;
}
public ExpectedEvent user(UserRepresentation user) {
return user(user.getId());
}
public ExpectedEvent user(String userId) {
return user(CoreMatchers.equalTo(userId));
}
public ExpectedEvent user(Matcher<String> userId) {
this.userId = userId;
return this;
}
public ExpectedEvent session(UserSessionRepresentation session) {
return session(session.getId());
}
public ExpectedEvent session(String sessionId) {
return session(CoreMatchers.equalTo(sessionId));
}
public ExpectedEvent session(Matcher<String> sessionId) {
this.sessionId = sessionId;
return this;
}
public ExpectedEvent ipAddress(String ipAddress) {
expected.setIpAddress(ipAddress);
return this;
}
public ExpectedEvent event(EventType e) {
expected.setType(e.name());
return this;
}
public ExpectedEvent detail(String key, String value) {
return detail(key, CoreMatchers.equalTo(value));
}
public ExpectedEvent detail(String key, Matcher<String> matcher) {
if (details == null) {
details = new HashMap<String, Matcher<String>>();
}
details.put(key, matcher);
return this;
}
public ExpectedEvent removeDetail(String key) {
if (details != null) {
details.remove(key);
}
return this;
}
public ExpectedEvent clearDetails() {
if (details != null) details.clear();
return this;
}
public ExpectedEvent error(String error) {
expected.setError(error);
return this;
}
public EventRepresentation assertEvent() {
return assertEvent(poll());
}
public EventRepresentation assertEvent(EventRepresentation actual) {
if (expected.getError() != null && !expected.getType().toString().endsWith("_ERROR")) {
expected.setType(expected.getType() + "_ERROR");
}
Assert.assertEquals(expected.getType(), actual.getType());
Assert.assertEquals(expected.getRealmId(), actual.getRealmId());
Assert.assertEquals(expected.getClientId(), actual.getClientId());
Assert.assertEquals(expected.getError(), actual.getError());
Assert.assertEquals(expected.getIpAddress(), actual.getIpAddress());
Assert.assertThat(actual.getUserId(), userId);
Assert.assertThat(actual.getSessionId(), sessionId);
if (details == null || details.isEmpty()) {
// Assert.assertNull(actual.getDetails());
} else {
Assert.assertNotNull(actual.getDetails());
for (Map.Entry<String, Matcher<String>> d : details.entrySet()) {
String actualValue = actual.getDetails().get(d.getKey());
if (!actual.getDetails().containsKey(d.getKey())) {
Assert.fail(d.getKey() + " missing");
}
Assert.assertThat("Unexpected value for " + d.getKey(), actualValue, d.getValue());
}
/*
for (String k : actual.getDetails().keySet()) {
if (!details.containsKey(k)) {
Assert.fail(k + " was not expected");
}
}
*/
}
return actual;
}
}
public static Matcher<String> isCodeId() {
return isUUID();
}
public static Matcher<String> isUUID() {
return new TypeSafeMatcher<String>() {
@Override
protected boolean matchesSafely(String item) {
return 36 == item.length() && item.charAt(8) == '-' && item.charAt(13) == '-' && item.charAt(18) == '-' && item.charAt(23) == '-';
}
@Override
public void describeTo(Description description) {
description.appendText("Not an UUID");
}
};
}
private EventRepresentation fetchNextEvent() {
CloseableHttpClient httpclient = HttpClients.createDefault();
try {
HttpPost post = new HttpPost(defaultEventsQueueUri + "/event-queue");
CloseableHttpResponse response = httpclient.execute(post);
if (response.getStatusLine().getStatusCode() != 200) {
throw new RuntimeException("Failed to retrieve event from " + post.getURI() + ": " + response.getStatusLine().toString() + " / " + IOUtils.toString(response.getEntity().getContent()));
}
return JsonSerialization.readValue(response.getEntity().getContent(), EventRepresentation.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
finally {
try {
httpclient.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

View file

@ -38,7 +38,7 @@ public abstract class AbstractEventTest extends AbstractAuthTest {
configRep.setAdminEventsDetailsEnabled(false);
configRep.setAdminEventsEnabled(false);
configRep.setEventsEnabled(false);
configRep.setEnabledEventTypes(Collections.EMPTY_LIST); // resets to all types
configRep.setEnabledEventTypes(Collections.<String>emptyList()); // resets to all types
saveConfig();
}

View file

@ -0,0 +1,86 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.admin.group;
import org.junit.Before;
import org.keycloak.OAuth2Constants;
import org.keycloak.RSATokenVerifier;
import org.keycloak.events.Details;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import java.util.List;
import static org.keycloak.testsuite.util.IOUtil.loadRealm;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public abstract class AbstractGroupTest extends AbstractKeycloakTest {
AssertEvents events;
@Before
public void initAssertEvents() throws Exception {
events = new AssertEvents(this);
}
AccessToken login(String login, String clientId, String clientSecret, String userId) throws Exception {
AccessTokenResponse tokenResponse = oauthClient.getToken("test", clientId, clientSecret, login, "password");
String accessToken = tokenResponse.getToken();
String refreshToken = tokenResponse.getRefreshToken();
AccessToken accessTokenRepresentation = RSATokenVerifier.verifyToken(accessToken, events.getRealmPublicKey(), AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/realms/test");
JWSInput jws = new JWSInput(refreshToken);
if (!RSAProvider.verify(jws, events.getRealmPublicKey())) {
throw new RuntimeException("Invalid refresh token");
}
RefreshToken refreshTokenRepresentation = jws.readJsonContent(RefreshToken.class);
events.expectLogin()
.client(clientId)
.user(userId)
.session(tokenResponse.getSessionState())
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessTokenRepresentation.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshTokenRepresentation.getId())
.detail(Details.USERNAME, login)
.removeDetail(Details.CODE_ID)
.removeDetail(Details.REDIRECT_URI)
.removeDetail(Details.CONSENT)
.assertEvent();
return accessTokenRepresentation;
}
RealmRepresentation loadTestRealm(List<RealmRepresentation> testRealms) {
RealmRepresentation result = loadRealm("/testrealm.json");
testRealms.add(result);
return result;
}
}

View file

@ -0,0 +1,139 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.admin.group;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class GroupMappersTest extends AbstractGroupTest {
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation testRealmRep = loadTestRealm(testRealms);
testRealmRep.setEventsEnabled(true);
ClientRepresentation client = getClientByAlias(testRealmRep, "test-app");
Assert.assertNotNull("test-app client exists", client);
client.setDirectAccessGrantsEnabled(true);
List<ProtocolMapperRepresentation> mappers = new LinkedList<>();
ProtocolMapperRepresentation mapper = new ProtocolMapperRepresentation();
mapper.setName("groups");
mapper.setProtocolMapper(GroupMembershipMapper.PROVIDER_ID);
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
mapper.setConsentRequired(false);
Map<String, String> config = new HashMap<>();
config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "groups");
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
mapper.setConfig(config);
mappers.add(mapper);
mapper = new ProtocolMapperRepresentation();
mapper.setName("topAttribute");
mapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID);
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
mapper.setConsentRequired(false);
config = new HashMap<>();
config.put(ProtocolMapperUtils.USER_ATTRIBUTE, "topAttribute");
config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "topAttribute");
config.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE);
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
mapper.setConfig(config);
mappers.add(mapper);
mapper = new ProtocolMapperRepresentation();
mapper.setName("level2Attribute");
mapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID);
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
mapper.setConsentRequired(false);
config = new HashMap<>();
config.put(ProtocolMapperUtils.USER_ATTRIBUTE, "level2Attribute");
config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "level2Attribute");
config.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE);
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
mapper.setConfig(config);
mappers.add(mapper);
client.setProtocolMappers(mappers);
}
private ClientRepresentation getClientByAlias(RealmRepresentation testRealmRep, String alias) {
for (ClientRepresentation client: testRealmRep.getClients()) {
if (alias.equals(client.getClientId())) {
return client;
}
}
return null;
}
@Test
@SuppressWarnings("unchecked")
public void testGroupMappers() throws Exception {
RealmResource realm = adminClient.realms().realm("test");
{
UserRepresentation user = realm.users().search("topGroupUser", -1, -1).get(0);
AccessToken token = login(user.getUsername(), "test-app", "password", user.getId());
Assert.assertTrue(token.getRealmAccess().getRoles().contains("user"));
List<String> groups = (List<String>) token.getOtherClaims().get("groups");
Assert.assertNotNull(groups);
Assert.assertTrue(groups.size() == 1);
Assert.assertEquals("topGroup", groups.get(0));
Assert.assertEquals("true", token.getOtherClaims().get("topAttribute"));
}
{
UserRepresentation user = realm.users().search("level2GroupUser", -1, -1).get(0);
AccessToken token = login(user.getUsername(), "test-app", "password", user.getId());
Assert.assertTrue(token.getRealmAccess().getRoles().contains("user"));
Assert.assertTrue(token.getRealmAccess().getRoles().contains("admin"));
Assert.assertTrue(token.getResourceAccess("test-app").getRoles().contains("customer-user"));
List<String> groups = (List<String>) token.getOtherClaims().get("groups");
Assert.assertNotNull(groups);
Assert.assertTrue(groups.size() == 1);
Assert.assertEquals("level2group", groups.get(0));
Assert.assertEquals("true", token.getOtherClaims().get("topAttribute"));
Assert.assertEquals("true", token.getOtherClaims().get("level2Attribute"));
}
}
}

View file

@ -15,89 +15,68 @@
* limitations under the License.
*/
package org.keycloak.testsuite.model;
package org.keycloak.testsuite.admin.group;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.events.Details;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper;
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.URLAssert;
import org.keycloak.util.JsonSerialization;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import static org.junit.Assert.assertEquals;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class GroupTest {
public class GroupTest extends AbstractGroupTest {
@ClassRule
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ClientModel app = KeycloakModelUtils.createClient(appRealm, "resource-owner");
app.setDirectAccessGrantsEnabled(true);
app.setSecret("secret");
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation testRealmRep = loadTestRealm(testRealms);
app = appRealm.getClientByClientId("test-app");
app.setDirectAccessGrantsEnabled(true);
testRealmRep.setEventsEnabled(true);
UserModel user = session.users().addUser(appRealm, "direct-login");
user.setEmail("direct-login@localhost");
user.setEnabled(true);
List<UserRepresentation> users = testRealmRep.getUsers();
UserRepresentation user = new UserRepresentation();
user.setUsername("direct-login");
user.setEmail("direct-login@localhost");
user.setEnabled(true);
List<CredentialRepresentation> credentials = new LinkedList<>();
CredentialRepresentation credential = new CredentialRepresentation();
credential.setType(CredentialRepresentation.PASSWORD);
credential.setValue("password");
credentials.add(credential);
user.setCredentials(credentials);
users.add(user);
session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID);
}
});
List<ClientRepresentation> clients = testRealmRep.getClients();
protected static Keycloak keycloak;
@Rule
public AssertEvents events = new AssertEvents(keycloakRule);
@Rule
public WebRule webRule = new WebRule(this);
@WebResource
protected WebDriver driver;
@WebResource
protected OAuthClient oauth;
ClientRepresentation client = new ClientRepresentation();
client.setClientId("resource-owner");
client.setDirectAccessGrantsEnabled(true);
client.setSecret("secret");
clients.add(client);
}
@Test
public void createAndTestGroups() throws Exception {
RealmResource realm = keycloak.realms().realm("test");
RealmResource realm = adminClient.realms().realm("test");
{
RoleRepresentation groupRole = new RoleRepresentation();
groupRole.setName("topRole");
@ -132,6 +111,21 @@ public class GroupTest {
level2Group.setName("level2");
response = realm.groups().group(topGroup.getId()).subGroup(level2Group);
response.close();
URI location = response.getLocation();
final String level2Id = ApiUtil.getCreatedId(response);
final GroupRepresentation level2GroupById = realm.groups().group(level2Id).toRepresentation();
Assert.assertEquals(level2Id, level2GroupById.getId());
Assert.assertEquals(level2Group.getName(), level2GroupById.getName());
URLAssert.assertGetURL(location, adminClient.tokenManager().getAccessTokenString(), new URLAssert.AssertJSONResponseHandler() {
@Override
protected void assertResponseBody(String body) throws IOException {
GroupRepresentation level2 = JsonSerialization.readValue(body, GroupRepresentation.class);
Assert.assertEquals(level2Id, level2.getId());
}
});
level2Group = realm.getGroupByPath("/top/level2");
Assert.assertNotNull(level2Group);
roles.clear();
@ -207,73 +201,5 @@ public class GroupTest {
realm.removeDefaultGroup(level3Group.getId());
defaultGroups = realm.getDefaultGroups();
Assert.assertEquals(0, defaultGroups.size());
}
@Test
public void testGroupMappers() throws Exception {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ClientModel app = appRealm.getClientByClientId("test-app");
app.addProtocolMapper(GroupMembershipMapper.create("groups", "groups", false, null, true, true));
app.addProtocolMapper(UserAttributeMapper.createClaimMapper("topAttribute", "topAttribute", "topAttribute", ProviderConfigProperty.STRING_TYPE, false, null, true, true, false));
app.addProtocolMapper(UserAttributeMapper.createClaimMapper("level2Attribute", "level2Attribute", "level2Attribute", ProviderConfigProperty.STRING_TYPE, false, null, true, true, false));
}
}, "test");
RealmResource realm = keycloak.realms().realm("test");
{
UserRepresentation user = realm.users().search("topGroupUser", -1, -1).get(0);
AccessToken token = login(user.getUsername(), "test-app", "password", user.getId());
Assert.assertTrue(token.getRealmAccess().getRoles().contains("user"));
List<String> groups = (List<String>) token.getOtherClaims().get("groups");
Assert.assertNotNull(groups);
Assert.assertTrue(groups.size() == 1);
Assert.assertEquals("topGroup", groups.get(0));
Assert.assertEquals("true", token.getOtherClaims().get("topAttribute"));
}
{
UserRepresentation user = realm.users().search("level2GroupUser", -1, -1).get(0);
AccessToken token = login(user.getUsername(), "test-app", "password", user.getId());
Assert.assertTrue(token.getRealmAccess().getRoles().contains("user"));
Assert.assertTrue(token.getRealmAccess().getRoles().contains("admin"));
Assert.assertTrue(token.getResourceAccess("test-app").getRoles().contains("customer-user"));
List<String> groups = (List<String>) token.getOtherClaims().get("groups");
Assert.assertNotNull(groups);
Assert.assertTrue(groups.size() == 1);
Assert.assertEquals("level2group", groups.get(0));
Assert.assertEquals("true", token.getOtherClaims().get("topAttribute"));
Assert.assertEquals("true", token.getOtherClaims().get("level2Attribute"));
}
}
protected AccessToken login(String login, String clientId, String clientSecret, String userId) throws Exception {
oauth.clientId(clientId);
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(clientSecret, login, "password");
assertEquals(200, response.getStatusCode());
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
RefreshToken refreshToken = oauth.verifyRefreshToken(response.getRefreshToken());
events.expectLogin()
.client(clientId)
.user(userId)
.session(accessToken.getSessionState())
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, login)
.removeDetail(Details.CODE_ID)
.removeDetail(Details.REDIRECT_URI)
.removeDetail(Details.CONSENT)
.assertEvent();
return accessToken;
}
}

View file

@ -18,14 +18,27 @@
package org.keycloak.testsuite.util;
import javax.ws.rs.core.UriBuilder;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.junit.Assert;
import org.keycloak.testsuite.page.AbstractPage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.keycloak.testsuite.auth.page.login.PageWithLoginUrl;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.net.URI;
import java.nio.charset.Charset;
/**
*
@ -104,9 +117,68 @@ public class URLAssert {
public static void assertCurrentUrlStartsWithLoginUrlOf(PageWithLoginUrl page) {
assertCurrentUrlStartsWithLoginUrlOf(page.getDriver(), page);
}
public static void assertCurrentUrlStartsWithLoginUrlOf(WebDriver driver, PageWithLoginUrl page) {
assertCurrentUrlStartsWith(driver, page.getOIDCLoginUrl().toString());
}
public static void assertGetURL(URI url, String accessToken, AssertResponseHandler handler) {
CloseableHttpClient httpclient = HttpClients.createDefault();
try {
HttpGet get = new HttpGet(url);
get.setHeader("Authorization", "Bearer " + accessToken);
CloseableHttpResponse response = httpclient.execute(get);
if (response.getStatusLine().getStatusCode() != 200) {
throw new RuntimeException("Response status error: " + response.getStatusLine().getStatusCode() + ": " + url);
}
handler.assertResponse(response);
} catch (Exception e) {
throw new RuntimeException(e);
}
finally {
try {
httpclient.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public interface AssertResponseHandler {
void assertResponse(CloseableHttpResponse response) throws IOException;
}
public static abstract class AssertJSONResponseHandler implements AssertResponseHandler {
@Override
public void assertResponse(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
Header contentType = entity.getContentType();
Assert.assertEquals("application/json", contentType.getValue());
char [] buf = new char[8192];
StringWriter out = new StringWriter();
Reader in = new InputStreamReader(entity.getContent(), Charset.forName("utf-8"));
int rc = 0;
try {
while ((rc = in.read(buf)) != -1) {
out.write(buf, 0, rc);
}
} finally {
try {
in.close();
} catch (Exception ignored) {}
out.close();
}
assertResponseBody(out.toString());
}
protected abstract void assertResponseBody(String body) throws IOException;
}
}

View file

@ -11,7 +11,8 @@
"jboss-logging" : {
"success-level": "debug",
"error-level": "warn"
}
},
"event-queue": {}
},
"realm": {

View file

@ -0,0 +1,186 @@
{
"id": "test",
"realm": "test",
"enabled": true,
"sslRequired": "external",
"registrationAllowed": true,
"resetPasswordAllowed": true,
"editUsernameAllowed" : true,
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"requiredCredentials": [ "password" ],
"defaultRoles": [ "user" ],
"smtpServer": {
"from": "auto@keycloak.org",
"host": "localhost",
"port":"3025"
},
"users" : [
{
"username" : "test-user@localhost",
"enabled": true,
"email" : "test-user@localhost",
"firstName": "Tom",
"lastName": "Brady",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["user", "offline_access"],
"clientRoles": {
"test-app": [ "customer-user" ],
"account": [ "view-profile", "manage-account" ]
}
},
{
"username" : "john-doh@localhost",
"enabled": true,
"email" : "john-doh@localhost",
"firstName": "John",
"lastName": "Doh",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["user"],
"clientRoles": {
"test-app": [ "customer-user" ],
"account": [ "view-profile", "manage-account" ]
}
},
{
"username" : "keycloak-user@localhost",
"enabled": true,
"email" : "keycloak-user@localhost",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["user"],
"clientRoles": {
"test-app": [ "customer-user" ],
"account": [ "view-profile", "manage-account" ]
}
},
{
"username" : "topGroupUser",
"enabled": true,
"email" : "top@redhat.com",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"groups": [
"/topGroup"
]
},
{
"username" : "level2GroupUser",
"enabled": true,
"email" : "level2@redhat.com",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"groups": [
"/topGroup/level2group"
]
}
],
"scopeMappings": [
{
"client": "third-party",
"roles": ["user"]
},
{
"client": "test-app",
"roles": ["user"]
}
],
"clients": [
{
"clientId": "test-app",
"enabled": true,
"baseUrl": "http://localhost:8081/app",
"redirectUris": [
"http://localhost:8081/app/*"
],
"adminUrl": "http://localhost:8081/app/logout",
"secret": "password"
},
{
"clientId" : "third-party",
"enabled": true,
"consentRequired": true,
"redirectUris": [
"http://localhost:8081/app/*"
],
"secret": "password"
}
],
"roles" : {
"realm" : [
{
"name": "user",
"description": "Have User privileges"
},
{
"name": "admin",
"description": "Have Administrator privileges"
}
],
"client" : {
"test-app" : [
{
"name": "customer-user",
"description": "Have Customer User privileges"
},
{
"name": "customer-admin",
"description": "Have Customer Admin privileges"
}
]
}
},
"groups" : [
{
"name": "topGroup",
"attributes": {
"topAttribute": ["true"]
},
"realmRoles": ["user"],
"subGroups": [
{
"name": "level2group",
"realmRoles": ["admin"],
"clientRoles": {
"test-app": ["customer-user"]
},
"attributes": {
"level2Attribute": ["true"]
}
}
]
}
],
"clientScopeMappings": {
"test-app": [
{
"client": "third-party",
"roles": ["customer-user"]
}
]
},
"internationalizationEnabled": true,
"supportedLocales": ["en", "de"],
"defaultLocale": "en",
"eventsListeners": ["jboss-logging", "event-queue"]
}

View file

@ -48,6 +48,7 @@
<auth.server.port.offset>100</auth.server.port.offset>
<auth.server.http.port>8180</auth.server.http.port>
<auth.server.events.http.port>8089</auth.server.events.http.port>
<auth.server.https.port>8543</auth.server.https.port>
<auth.server.management.port>10090</auth.server.management.port>
<auth.server.management.port.jmx>10099</auth.server.management.port.jmx>
@ -140,6 +141,7 @@
<auth.server.port.offset>${auth.server.port.offset}</auth.server.port.offset>
<auth.server.http.port>${auth.server.http.port}</auth.server.http.port>
<auth.server.events.http.port>${auth.server.events.http.port}</auth.server.events.http.port>
<auth.server.https.port>${auth.server.https.port}</auth.server.https.port>
<auth.server.management.port>${auth.server.management.port}</auth.server.management.port>
<auth.server.management.port.jmx>${auth.server.management.port.jmx}</auth.server.management.port.jmx>