Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2017-01-13 11:45:55 -05:00
commit ffb688b393
35 changed files with 817 additions and 41 deletions

View file

@ -17,6 +17,19 @@
<name>KeyCloak AuthZ: Drools Policy Provider</name>
<description>KeyCloak AuthZ: Drools Policy Provider</description>
<dependencyManagement>
<dependencies>
<!-- Authorization Drools Policy Provider -->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-bom</artifactId>
<type>pom</type>
<version>${version.org.drools}</version>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>

View file

@ -40,6 +40,15 @@
<scope>import</scope>
<version>${version.jboss-integration-platform}</version>
</dependency>
<!-- Authorization Drools Policy Provider -->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-bom</artifactId>
<type>pom</type>
<version>${version.org.drools}</version>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

View file

@ -78,8 +78,8 @@ public class Keycloak {
return new Keycloak(serverUrl, realm, username, password, clientId, null, PASSWORD, null, null);
}
public static Keycloak getInstance(String serverUrl, String realm, String clientId, String authtoken) {
return new Keycloak(serverUrl, realm, null, null, clientId, null, PASSWORD, null, null);
public static Keycloak getInstance(String serverUrl, String realm, String clientId, String authToken) {
return new Keycloak(serverUrl, realm, null, null, clientId, null, PASSWORD, null, authToken);
}
public RealmsResource realms() {

View file

@ -39,7 +39,7 @@ public interface RoleMappingResource {
@Path("realm")
public RoleScopeResource realmLevel();
@Path("clients/{clientId}")
public RoleScopeResource clientLevel(@PathParam("clientId") String clientId);
@Path("clients/{clientUUID}")
public RoleScopeResource clientLevel(@PathParam("clientUUID") String clientUUID);
}

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.session;
import org.keycloak.storage.jpa.KeyUtils;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
@ -84,6 +86,7 @@ public class PersistentUserSessionEntity {
}
public void setUserId(String userId) {
KeyUtils.assertValidKey(userId);
this.userId = userId;
}

View file

@ -0,0 +1,56 @@
/*
* 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.storage.jpa;
import java.util.regex.Pattern;
import org.jboss.logging.Logger;
/**
*
* @author hmlnarik
*/
public class KeyUtils {
private static final Logger LOG = Logger.getLogger(KeyUtils.class);
public static final Pattern UUID_PATTERN = Pattern.compile("[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}");
public static final Pattern EXPECTED_KEY_PATTERN = Pattern.compile(
UUID_PATTERN.pattern()
+ "|"
+ "f:" + UUID_PATTERN.pattern() + ":.*"
);
/**
* Returns {@code} true when the key is {@code null} or either a plain UUID or a key formatted as "f:[UUID]:any_string"
* @param key String representation of the key
* @return
*/
public static boolean isValidKey(String key) {
return key == null || EXPECTED_KEY_PATTERN.matcher(key).matches();
}
/**
* Logs an warning when the key is not a valid key
* @param key String representation of the key
*/
public static void assertValidKey(String key) throws IllegalArgumentException {
if (! isValidKey(key)) {
LOG.warnf("The given key is not a valid key per specification, future migration might fail: %s", key);
}
}
}

View file

@ -17,6 +17,8 @@
package org.keycloak.storage.jpa.entity;
import org.keycloak.storage.jpa.KeyUtils;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
@ -71,6 +73,7 @@ public class BrokerLinkEntity {
}
public void setUserId(String userId) {
KeyUtils.assertValidKey(userId);
this.userId = userId;
}

View file

@ -16,6 +16,8 @@
*/
package org.keycloak.storage.jpa.entity;
import org.keycloak.storage.jpa.KeyUtils;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
@ -58,6 +60,7 @@ public class FederatedUser {
}
public void setId(String id) {
KeyUtils.assertValidKey(id);
this.id = id;
}

View file

@ -17,6 +17,8 @@
package org.keycloak.storage.jpa.entity;
import org.keycloak.storage.jpa.KeyUtils;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
@ -74,6 +76,7 @@ public class FederatedUserGroupMembershipEntity {
}
public void setUserId(String userId) {
KeyUtils.assertValidKey(userId);
this.userId = userId;
}

View file

@ -17,6 +17,8 @@
package org.keycloak.storage.jpa.entity;
import org.keycloak.storage.jpa.KeyUtils;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
@ -69,6 +71,7 @@ public class FederatedUserRequiredActionEntity {
}
public void setUserId(String userId) {
KeyUtils.assertValidKey(userId);
this.userId = userId;
}

View file

@ -17,6 +17,8 @@
package org.keycloak.storage.jpa.entity;
import org.keycloak.storage.jpa.KeyUtils;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
@ -64,6 +66,7 @@ public class FederatedUserRoleMappingEntity {
}
public void setUserId(String userId) {
KeyUtils.assertValidKey(userId);
this.userId = userId;
}

View file

@ -0,0 +1,53 @@
/*
* 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.storage.jpa;
import java.util.UUID;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author hmlnarik
*/
public class KeyUtilsTest {
@Test
public void testValidKeys() {
assertTrue(KeyUtils.isValidKey(UUID.randomUUID().toString()));
assertTrue(KeyUtils.isValidKey("01234567-1234-1234-aAAa-123456789012"));
assertTrue(KeyUtils.isValidKey("01234567-1234-1234-aAAf-123456789012"));
assertTrue(KeyUtils.isValidKey("f:" + UUID.randomUUID() + ":dsadsada"));
assertTrue(KeyUtils.isValidKey("f:01234567-1234-1234-aAAa-123456789012:dsadsada"));
assertTrue(KeyUtils.isValidKey("f:a1234567-1234-1234-aAAa-123456789012:dsadsada"));
}
@Test
public void testInvalidKeys() {
assertFalse(KeyUtils.isValidKey("any string"));
assertFalse(KeyUtils.isValidKey("0"));
assertFalse(KeyUtils.isValidKey("01234567-1234-1234-aAAg-123456789012a"));
assertFalse(KeyUtils.isValidKey("z1234567-1234-1234-aAAa-123456789012"));
assertFalse(KeyUtils.isValidKey("f:g1234567-1234-1234-aAAa-123456789012:dsadsada"));
assertFalse(KeyUtils.isValidKey("g:a1234567-1234-1234-aAAa-123456789012:dsadsada"));
assertFalse(KeyUtils.isValidKey("f:a1234567-1234-1234-aAAa-123456789012"));
}
}

View file

@ -428,15 +428,6 @@
<version>${google.zxing.version}</version>
</dependency>
<!-- Authorization Drools Policy Provider -->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-bom</artifactId>
<type>pom</type>
<version>${version.org.drools}</version>
<scope>import</scope>
</dependency>
<!-- Email Test Servers -->
<dependency>
<groupId>com.icegreen</groupId>

View file

@ -61,7 +61,7 @@ public class AppAuthManager extends AuthenticationManager {
public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
String tokenString = extractAuthorizationHeaderToken(headers);
if (tokenString == null) return null;
AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, true, tokenString, headers);
AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, true, false, tokenString, headers);
return authResult;
}

View file

@ -108,6 +108,15 @@ public class AuthenticationManager {
return userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() > currentTime && max > currentTime;
}
public static boolean isOfflineSessionValid(RealmModel realm, UserSessionModel userSession) {
if (userSession == null) {
logger.debug("No offline user session");
return false;
}
int currentTime = Time.currentTime();
return userSession.getLastSessionRefresh() + realm.getOfflineSessionIdleTimeout() > currentTime;
}
public static void expireUserSessionCookie(KeycloakSession session, UserSessionModel userSession, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, ClientConnection connection) {
try {
// check to see if any identity cookie is set with the same session and expire it if necessary
@ -390,7 +399,7 @@ public class AuthenticationManager {
}
String tokenString = cookie.getValue();
AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, false, tokenString, session.getContext().getRequestHeaders());
AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, false, true, tokenString, session.getContext().getRequestHeaders());
if (authResult == null) {
expireIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection());
return null;
@ -691,7 +700,7 @@ public class AuthenticationManager {
protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType,
String tokenString, HttpHeaders headers) {
boolean isCookie, String tokenString, HttpHeaders headers) {
try {
TokenVerifier verifier = TokenVerifier.create(tokenString).realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())).checkActive(checkActive).checkTokenType(checkTokenType);
String kid = verifier.getHeader().getKeyId();
@ -729,6 +738,14 @@ public class AuthenticationManager {
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
if (!isSessionValid(realm, userSession)) {
// Check if accessToken was for the offline session.
if (!isCookie) {
UserSessionModel offlineUserSession = session.sessions().getUserSession(realm, token.getSessionState());
if (isOfflineSessionValid(realm, offlineUserSession)) {
return new AuthResult(user, offlineUserSession, token);
}
}
if (userSession != null) backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
logger.debug("User session not active");
return null;

View file

@ -23,7 +23,7 @@ do
fi
if "$UPDATE_CONFIG" == "true"; then
echo "Updating Config - org.ops4j.pax.url.mvn"
echo "Updating Config"
./client $CLIENT_AUTH -f update-config.cli
if [ $? -ne 0 ]; then
RESULT=1;
@ -36,6 +36,14 @@ do
./client $CLIENT_AUTH -f install-features.cli
if [ $? -ne 0 ]; then RESULT=1; fi
if "$UPDATE_CONFIG" == "true"; then
echo "Updating Config - Keycloak authentication"
./client $CLIENT_AUTH -f update-config-auth.cli
if [ $? -ne 0 ]; then
RESULT=1;
fi
fi
./stop
rm -rf ../data/log
rm -rf ../data/tmp

View file

@ -32,7 +32,7 @@
<properties>
<app.server.karaf>fuse63</app.server.karaf>
<app.server.karaf.groupId>org.jboss.fuse</app.server.karaf.groupId>
<app.server.karaf.artifactId>jboss-fuse-full</app.server.karaf.artifactId>
<app.server.karaf.artifactId>jboss-fuse-karaf</app.server.karaf.artifactId>
<app.server.karaf.version>${fuse63.version}</app.server.karaf.version>
<app.server.karaf.unpacked.folder.name>jboss-fuse-${fuse63.version}</app.server.karaf.unpacked.folder.name>
<app.server.karaf.client.auth>-u admin -p admin</app.server.karaf.client.auth>

View file

@ -0,0 +1,9 @@
{
"realm": "demo",
"resource": "ssh-jmx-admin-client",
"ssl-required" : "external",
"auth-server-url" : "http://localhost:8080/auth",
"credentials": {
"secret": "password"
}
}

View file

@ -0,0 +1,7 @@
{
"realm" : "demo",
"resource" : "hawtio-client",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"public-client" : true
}

View file

@ -0,0 +1,9 @@
{
"realm" : "demo",
"resource" : "jaas",
"bearer-only" : true,
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"use-resource-role-mappings": false,
"principal-attribute": "preferred_username"
}

View file

@ -0,0 +1,8 @@
config:edit org.apache.karaf.shell
config:propset sshRealm keycloak
config:update
system-property -p hawtio.roles admin,user
system-property -p hawtio.keycloakEnabled true
system-property -p hawtio.realm keycloak
system-property -p hawtio.keycloakClientConfig \$\{karaf.base\}/etc/keycloak-hawtio-client.json
system-property -p hawtio.rolePrincipalClasses org.keycloak.adapters.jaas.RolePrincipal,org.apache.karaf.jaas.boot.principal.RolePrincipal

View file

@ -3,3 +3,8 @@ config:propset org.ops4j.pax.url.mvn.localRepository ${maven.repo.local}
config:propset org.ops4j.pax.url.mvn.settings ${maven.local.settings}
config:propappend org.ops4j.pax.url.mvn.repositories ${repositories}
config:update
config:edit jmx.acl.org.apache.karaf.security.jmx
config:propappend list* viewer
config:propappend set* jmxAdmin
config:propappend * jmxAdmin,admin
config:update

View file

@ -117,6 +117,7 @@
<includes>
<include>install-features.cli</include>
<include>update-config.cli</include>
<include>update-config-auth.cli</include>
</includes>
<filtering>true</filtering>
</resource>
@ -124,7 +125,7 @@
</configuration>
</execution>
<execution>
<id>copy-users-properties</id>
<id>copy-configs</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
@ -137,6 +138,9 @@
<directory>src/main/resources</directory>
<includes>
<include>users.properties</include>
<include>keycloak-direct-access.json</include>
<include>keycloak-hawtio-client.json</include>
<include>keycloak-hawtio.json</include>
</includes>
</resource>
</resources>

View file

@ -81,6 +81,26 @@
<artifactId>greenmail</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-container-karaf-managed</artifactId>
<version>2.1.0.CR18</version>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-container-osgi</artifactId>
<version>2.1.0.CR18</version>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.enterprise</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View file

@ -17,6 +17,7 @@
package org.keycloak.testsuite.arquillian;
import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
import org.jboss.arquillian.container.test.impl.enricher.resource.URLResourceProvider;
import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor;
import org.jboss.arquillian.container.test.spi.client.deployment.DeploymentScenarioGenerator;
@ -26,6 +27,7 @@ import org.jboss.arquillian.graphene.location.CustomizableURLResourceProvider;
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
import org.jboss.arquillian.test.spi.execution.TestExecutionDecider;
import org.keycloak.testsuite.arquillian.h2.H2TestEnricher;
import org.keycloak.testsuite.arquillian.karaf.CustomKarafContainer;
import org.keycloak.testsuite.arquillian.migration.MigrationTestExecutionDecider;
import org.keycloak.testsuite.arquillian.provider.AdminClientProvider;
import org.keycloak.testsuite.arquillian.provider.OAuthClientProvider;
@ -51,10 +53,10 @@ public class KeycloakArquillianExtension implements LoadableExtension {
builder
.service(DeploymentScenarioGenerator.class, DeploymentTargetModifier.class)
.service(ApplicationArchiveProcessor.class, DeploymentArchiveProcessor.class)
.service(DeployableContainer.class, CustomKarafContainer.class)
.observer(AuthServerTestEnricher.class)
.observer(AppServerTestEnricher.class)
.observer(H2TestEnricher.class);
builder
.service(TestExecutionDecider.class, MigrationTestExecutionDecider.class);

View file

@ -0,0 +1,231 @@
package org.keycloak.testsuite.arquillian.karaf;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import org.jboss.arquillian.container.osgi.jmx.JMXDeployableContainer;
import org.jboss.arquillian.container.osgi.jmx.ObjectNameFactory;
import org.jboss.arquillian.container.osgi.karaf.managed.KarafManagedContainerConfiguration;
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
import org.osgi.jmx.framework.BundleStateMBean;
import org.osgi.jmx.framework.FrameworkMBean;
import org.osgi.jmx.framework.ServiceStateMBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* KarafManagedDeployableContainer
*
* @author thomas.diesler@jboss.com
*/
public class CustomKarafContainer<T extends KarafManagedContainerConfiguration> extends JMXDeployableContainer<T> {
static final Logger _logger = LoggerFactory.getLogger(CustomKarafContainer.class.getPackage().getName());
private KarafManagedContainerConfiguration config;
private Process process;
@Override
public Class<T> getConfigurationClass() {
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) KarafManagedContainerConfiguration.class;
return clazz;
}
@Override
public void setup(T config) {
super.setup(config);
this.config = config;
}
@Override
public void start() throws LifecycleException {
// Try to connect to an already running server
MBeanServerConnection mbeanServer = null;
try {
mbeanServer = getMBeanServerConnection(500, TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
// ignore
}
if (mbeanServer != null && !config.isAllowConnectingToRunningServer()) {
throw new LifecycleException(
"The server is already running! Managed containers does not support connecting to running server instances due to the " +
"possible harmful effect of connecting to the wrong server. Please stop server before running or change to another type of container.\n" +
"To disable this check and allow Arquillian to connect to a running server, set allowConnectingToRunningServer to true in the container configuration");
}
// Start the Karaf process
if (mbeanServer == null) {
String karafHome = config.getKarafHome();
if (karafHome == null)
throw new IllegalStateException("karafHome cannot be null");
File karafHomeDir = new File(karafHome).getAbsoluteFile();
if (!karafHomeDir.isDirectory())
throw new IllegalStateException("Not a valid Karaf home dir: " + karafHomeDir);
String java = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
_logger.info(String.format("Using java: %s", java));
List<String> cmd = new ArrayList<String>();
cmd.add(java);
// JavaVM args
String javaArgs = config.getJavaVmArguments();
if (!javaArgs.contains("-Xmx")) {
javaArgs = KarafManagedContainerConfiguration.DEFAULT_JAVAVM_ARGUMENTS + " " + javaArgs;
}
cmd.addAll(Arrays.asList(javaArgs.split("\\s")));
// Karaf properties
cmd.add("-Dkaraf.home=" + karafHomeDir);
cmd.add("-Dkaraf.base=" + karafHomeDir);
cmd.add("-Dkaraf.etc=" + karafHomeDir + "/etc");
cmd.add("-Dkaraf.data=" + karafHomeDir + "/data");
cmd.add("-Dkaraf.instances=" + karafHomeDir + "/instances");
cmd.add("-Dkaraf.startLocalConsole=false");
cmd.add("-Dkaraf.startRemoteShell=true");
// Java properties
cmd.add("-Djava.io.tmpdir=" + new File(karafHomeDir, "data/tmp"));
cmd.add("-Djava.util.logging.config.file=" + new File(karafHomeDir, "etc/java.util.logging.properties"));
cmd.add("-Djava.endorsed.dirs=" + new File(karafHomeDir, "lib/endorsed"));
// Classpath
StringBuilder classPath = new StringBuilder();
File karafLibDir = new File(karafHomeDir, "lib");
String[] libs = karafLibDir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith("karaf");
}
});
for (String lib : libs) {
String separator = classPath.length() > 0 ? File.pathSeparator : "";
classPath.append(separator).append(new File(karafHomeDir, "lib/" + lib));
}
cmd.add("-classpath");
cmd.add(classPath.toString());
// Main class
cmd.add("org.apache.karaf.main.Main");
// Output the startup command
StringBuffer cmdstr = new StringBuffer();
for (String tok : cmd) {
cmdstr.append(tok).append(" ");
}
_logger.debug("Starting Karaf with: {}", cmdstr);
try {
ProcessBuilder processBuilder = new ProcessBuilder(cmd);
processBuilder.directory(karafHomeDir);
processBuilder.redirectErrorStream(true);
process = processBuilder.start();
new Thread(new ConsoleConsumer()).start();
} catch (Exception ex) {
throw new LifecycleException("Cannot start managed Karaf container", ex);
}
// Get the MBeanServerConnection
try {
mbeanServer = getMBeanServerConnection(30, TimeUnit.SECONDS);
} catch (Exception ex) {
destroyKarafProcess();
throw new LifecycleException("Cannot obtain MBean server connection", ex);
}
}
mbeanServerInstance.set(mbeanServer);
try {
// Get the FrameworkMBean
ObjectName oname = ObjectNameFactory.create("osgi.core:type=framework,*");
frameworkMBean = getMBeanProxy(mbeanServer, oname, FrameworkMBean.class, 30, TimeUnit.SECONDS);
// Get the BundleStateMBean
oname = ObjectNameFactory.create("osgi.core:type=bundleState,*");
bundleStateMBean = getMBeanProxy(mbeanServer, oname, BundleStateMBean.class, 30, TimeUnit.SECONDS);
// Get the BundleStateMBean
oname = ObjectNameFactory.create("osgi.core:type=serviceState,*");
serviceStateMBean = getMBeanProxy(mbeanServer, oname, ServiceStateMBean.class, 30, TimeUnit.SECONDS);
// Install the arquillian bundle to become active
installArquillianBundle();
// Await the arquillian bundle to become active
awaitArquillianBundleActive(30, TimeUnit.SECONDS);
// Await the beginning start level
Integer beginningStartLevel = config.getKarafBeginningStartLevel();
if (beginningStartLevel != null)
awaitBeginningStartLevel(beginningStartLevel, 30, TimeUnit.SECONDS);
// Await bootsrap complete services
awaitBootstrapCompleteServices();
} catch (RuntimeException rte) {
destroyKarafProcess();
throw rte;
} catch (Exception ex) {
destroyKarafProcess();
throw new LifecycleException("Cannot start Karaf container", ex);
}
}
@Override
public void stop() throws LifecycleException {
super.stop();
destroyKarafProcess();
}
private void destroyKarafProcess() throws LifecycleException {
if (process != null) {
process.destroy();
try {
process.waitFor();
} catch (InterruptedException e) {
throw new LifecycleException("Cannot start Karaf container", e);
}
}
}
/**
* Runnable that consumes the output of the process. If nothing consumes the output the AS will hang on some platforms
*
* @author Stuart Douglas
*/
private class ConsoleConsumer implements Runnable {
@Override
public void run() {
final InputStream stream = process.getInputStream();
final boolean writeOutput = config.isOutputToConsole();
try {
byte[] buf = new byte[32];
int num;
// Do not try reading a line cos it considers '\r' end of line
while ((num = stream.read(buf)) != -1) {
if (writeOutput)
System.out.write(buf, 0, num);
}
} catch (IOException ignored) {
}
}
}
}

View file

@ -0,0 +1,258 @@
package org.keycloak.testsuite.adapter.example;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
import static org.keycloak.testsuite.util.IOUtil.loadRealm;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.channel.ClientChannelEvent;
import org.apache.sshd.client.future.ConnectFuture;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.client.session.ClientSession.ClientSessionEvent;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.adapter.page.HawtioPage;
public abstract class AbstractFuseAdminAdapterTest extends AbstractExampleAdapterTest {
@Page
private HawtioPage hawtioPage;
private SshClient client;
private ClientChannel channel;
private ClientSession session;
enum Result { OK, NOT_FOUND, NO_CREDENTIALS, NO_ROLES };
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation fuseRealm = loadRealm(new File(EXAMPLES_HOME_DIR + "/fuse/demorealm.json"));
testRealms.add(fuseRealm);
}
@Override
public void setDefaultPageUriParameters() {
super.setDefaultPageUriParameters();
testRealmPage.setAuthRealm(DEMO);
testRealmLoginPage.setAuthRealm(DEMO);
}
@Test
public void hawtioLoginTest() throws Exception {
hawtioPage.navigateTo();
testRealmLoginPage.form().login("user", "invalid-password");
assertCurrentUrlDoesntStartWith(hawtioPage);
testRealmLoginPage.form().login("invalid-user", "password");
assertCurrentUrlDoesntStartWith(hawtioPage);
testRealmLoginPage.form().login("root", "password");
assertCurrentUrlStartsWith(hawtioPage.getDriver(), hawtioPage.toString() + "/welcome");
hawtioPage.logout();
assertCurrentUrlStartsWith(testRealmLoginPage);
hawtioPage.navigateTo();
testRealmLoginPage.form().login("mary", "password");
assertTrue(!driver.getPageSource().contains("welcome"));
}
@Test
public void sshLoginTest() throws Exception {
assertCommand("mary", "password", "shell:date", Result.NO_ROLES);
assertCommand("john", "password", "shell:info", Result.NO_CREDENTIALS);
assertCommand("john", "password", "shell:date", Result.OK);
assertCommand("root", "password", "shell:info", Result.OK);
}
@Test
public void jmxLoginTest() throws Exception {
setJMXAuthentication("keycloak", "password");
ObjectName mbean = new ObjectName("org.apache.karaf:type=config,name=root");
//invalid credentials
try {
getJMXConnector("mary", "password1").getMBeanServerConnection();
Assert.fail();
} catch (SecurityException se) {}
//no role
MBeanServerConnection connection = getJMXConnector("mary", "password").getMBeanServerConnection();
assertJmxInvoke(false, connection, mbean, "listProperties", new Object [] {""}, new String [] {String.class.getName()});
assertJmxInvoke(false, connection, mbean, "setProperty", new Object [] {"", "x", "y"}, new String [] {String.class.getName(), String.class.getName(), String.class.getName()});
//read only role
connection = getJMXConnector("john", "password").getMBeanServerConnection();
assertJmxInvoke(true, connection, mbean, "listProperties", new Object [] {""}, new String [] {String.class.getName()});
assertJmxInvoke(false, connection, mbean, "setProperty", new Object [] {"", "x", "y"}, new String [] {String.class.getName(), String.class.getName(), String.class.getName()});
//read write role
connection = getJMXConnector("root", "password").getMBeanServerConnection();
assertJmxInvoke(true, connection, mbean, "listProperties", new Object [] {""}, new String [] {String.class.getName()});
assertJmxInvoke(true, connection, mbean, "setProperty", new Object [] {"", "x", "y"}, new String [] {String.class.getName(), String.class.getName(), String.class.getName()});
setJMXAuthentication("karaf", "admin");
}
private String assertCommand(String user, String password, String command, Result result) throws Exception, IOException {
if (!command.endsWith("\n"))
command += "\n";
ByteArrayOutputStream out = new ByteArrayOutputStream();
OutputStream pipe = openSshChannel(user, password, out, out);
pipe.write(command.getBytes());
pipe.flush();
closeSshChannel(pipe);
String output = new String(out.toByteArray());
switch(result) {
case OK:
Assert.assertFalse("Should not contain 'Insufficient credentials' or 'Command not found': " + output,
output.contains("Insufficient credentials") || output.contains("Command not found"));
break;
case NOT_FOUND:
Assert.assertTrue("Should contain 'Command not found': " + output,
output.contains("Command not found"));
break;
case NO_CREDENTIALS:
Assert.assertTrue("Should contain 'Insufficient credentials': " + output,
output.contains("Insufficient credentials"));
break;
case NO_ROLES:
Assert.assertTrue("Should contain 'Current user has no associated roles': " + output,
output.contains("Current user has no associated roles"));
break;
default:
Assert.fail("Unexpected enum value: " + result);
}
return output;
}
private OutputStream openSshChannel(String username, String password, OutputStream ... outputs) throws Exception {
client = SshClient.setUpDefaultClient();
client.start();
ConnectFuture future = client.connect(username, "localhost", 8101);
future.await();
session = future.getSession();
Set<ClientSessionEvent> ret = EnumSet.of(ClientSessionEvent.WAIT_AUTH);
while (ret.contains(ClientSessionEvent.WAIT_AUTH)) {
session.addPasswordIdentity(password);
session.auth().verify();
ret = session.waitFor(EnumSet.of(ClientSessionEvent.WAIT_AUTH, ClientSessionEvent.CLOSED, ClientSessionEvent.AUTHED), 0);
}
if (ret.contains(ClientSessionEvent.CLOSED)) {
throw new Exception("Could not open SSH channel");
}
channel = session.createChannel("shell");
PipedOutputStream pipe = new PipedOutputStream();
channel.setIn(new PipedInputStream(pipe));
OutputStream out;
if (outputs.length >= 1) {
out = outputs[0];
} else {
out = new ByteArrayOutputStream();
}
channel.setOut(out);
OutputStream err;
if (outputs.length >= 2) {
err = outputs[1];
} else {
err = new ByteArrayOutputStream();
}
channel.setErr(err);
channel.open();
return pipe;
}
private void setJMXAuthentication(String realm, String password) throws Exception {
assertCommand("admin", "password", "config:edit org.apache.karaf.management; config:propset jmxRealm " + realm + "; config:update", Result.OK);
getMBeanServerConnection(10000, TimeUnit.MILLISECONDS, "admin", password);
}
private void closeSshChannel(OutputStream pipe) throws IOException {
pipe.write("logout\n".getBytes());
pipe.flush();
channel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0);
session.close(true);
client.stop();
client = null;
channel = null;
session = null;
}
private Object assertJmxInvoke(boolean expectSuccess, MBeanServerConnection connection, ObjectName mbean, String method,
Object[] params, String[] signature) throws InstanceNotFoundException, MBeanException, ReflectionException, IOException {
try {
Object result = connection.invoke(mbean, method, params, signature);
assertTrue(expectSuccess);
return result;
} catch (SecurityException se) {
assertTrue(!expectSuccess);
return null;
}
}
private MBeanServerConnection getMBeanServerConnection(long timeout, final TimeUnit unit, String username, String password) throws Exception {
Exception lastException = null;
long timeoutMillis = System.currentTimeMillis() + unit.toMillis(timeout);
while (System.currentTimeMillis() < timeoutMillis) {
try {
return getJMXConnector(username, password).getMBeanServerConnection();
} catch (Exception ex) {
lastException = ex;
Thread.sleep(500);
ex.printStackTrace();
}
}
TimeoutException timeoutException = new TimeoutException();
timeoutException.initCause(lastException);
throw timeoutException;
}
private JMXConnector getJMXConnector(String userName, String password) throws Exception {
JMXServiceURL url = new JMXServiceURL(getJmxServiceUrl());
String[] credentials = new String[] { userName, password };
Map<String, ?> env = Collections.singletonMap(JMXConnector.CREDENTIALS, credentials);
JMXConnector connector = JMXConnectorFactory.connect(url, env);
return connector;
}
private String getJmxServiceUrl() throws Exception {
return "service:jmx:rmi://localhost:44444/jndi/rmi://localhost:1099/karaf-root";
}
}

View file

@ -59,7 +59,7 @@ public abstract class AbstractFuseExampleAdapterTest extends AbstractExampleAdap
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation fuseRealm = loadRealm(new File(EXAMPLES_HOME_DIR + "/fuse/testrealm.json"));
RealmRepresentation fuseRealm = loadRealm(new File(EXAMPLES_HOME_DIR + "/fuse/demorealm.json"));
testRealms.add(fuseRealm);
}

View file

@ -23,12 +23,15 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.Constants;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.AccessToken;
@ -40,6 +43,9 @@ import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.auth.page.AuthRealm;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.ClientManager;
@ -432,4 +438,40 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
testUser.roles().realmLevel().add(Collections.singletonList(offlineAccess));
}
/**
* KEYCLOAK-4201
*
* @throws Exception
*/
@Test
public void offlineTokenAdminRESTAccess() throws Exception {
// Grant "view-realm" role to user
RealmResource appRealm = adminClient.realm("test");
ClientResource realmMgmt = ApiUtil.findClientByClientId(appRealm, Constants.REALM_MANAGEMENT_CLIENT_ID);
String realmMgmtUuid = realmMgmt.toRepresentation().getId();
RoleRepresentation roleRep = realmMgmt.roles().get(AdminRoles.VIEW_REALM).toRepresentation();
UserResource testUser = findUserByUsernameId(appRealm, "test-user@localhost");
testUser.roles().clientLevel(realmMgmtUuid).add(Collections.singletonList(roleRep));
// Login with offline token now
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
oauth.clientId("offline-client");
OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("secret1", "test-user@localhost", "password");
events.clear();
// Set the time offset, so that "normal" userSession expires
setTimeOffset(86400);
// Refresh with the offline token
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "secret1");
// Use accessToken to admin REST request
Keycloak offlineTokenAdmin = Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth",
AuthRealm.MASTER, Constants.ADMIN_CLI_CLIENT_ID, tokenResponse.getAccessToken());
RealmRepresentation testRealm = offlineTokenAdmin.realm("test").toRepresentation();
Assert.assertNotNull(testRealm);
}
}

View file

@ -31,21 +31,19 @@
<container qualifier="app-server-${{app.server}}" mode="manual" >
<configuration>
<property name="enabled">true</property>
<property name="adapterImplClass">org.jboss.arquillian.container.osgi.karaf.managed.KarafManagedDeployableContainer</property>
<!--<property name="adapterImplClass">org.keycloak.testsuite.arquillian.karaf.CustomKarafContainer</property>-->
<!-- <property name="adapterImplClass">org.jboss.arquillian.container.osgi.karaf.managed.KarafManagedDeployableContainer</property> -->
<property name="adapterImplClass">org.keycloak.testsuite.arquillian.karaf.CustomKarafContainer</property>
<property name="autostartBundle">false</property>
<property name="karafHome">${app.server.home}</property>
<property name="javaHome">${app.server.java.home}</property>
<property name="javaVmArguments">
-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n
${adapter.test.props}
</property>
<property name="jmxServiceURL">service:jmx:rmi://127.0.0.1:44444/jndi/rmi://127.0.0.1:1099/karaf-root</property>
<property name="jmxUsername">${app.server.management.user}</property>
<property name="jmxPassword">${app.server.management.password}</property>
<property name="jmxPassword">${app.server.management.password}</property>
</configuration>
</container>
</xsl:copy>
</xsl:template>

View file

@ -0,0 +1,8 @@
package org.keycloak.testsuite.adapter.example;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
@AppServerContainer("app-server-fuse63")
public class Fuse63AdminAdapterTest extends AbstractFuseAdminAdapterTest {
}

View file

@ -86,7 +86,12 @@
<groupId>org.apache.karaf</groupId>
<artifactId>org.apache.karaf.client</artifactId>
<version>3.0.3</version>
</dependency>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<version>1.2.0</version>
</dependency>
</dependencies>
<build>
<plugins>

View file

@ -719,6 +719,12 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<version>1.2.0</version>
</dependency>
<!-- Email Test Server -->
<dependency>
<groupId>com.icegreen</groupId>

View file

@ -11,7 +11,7 @@
</li>
<li>
<p>${msg("loginTotpStep2")}</p>
<img src="data:image/png;base64, ${totp.totpSecretQrCode}" alt="Figure: Barcode"><br/>
<img id="kc-totp-secret-qr-code" src="data:image/png;base64, ${totp.totpSecretQrCode}" alt="Figure: Barcode"><br/>
<span class="code">${totp.totpSecretEncoded}</span>
</li>
<li>

View file

@ -152,6 +152,11 @@ ol#kc-totp-settings li:first-of-type {
margin-top: 0;
}
#kc-totp-secret-qr-code {
max-width:150px;
max-height:150px;
}
/* OAuth */
#kc-oauth h3 {
@ -244,18 +249,6 @@ ol#kc-totp-settings li:first-of-type {
.zocial.microsoft {background-color: #0052a4; color: #fff;}
.zocial.microsoft:before { content: "\f15d"; }
@media (min-width: 1281px) {
#kc-container-wrapper {
bottom: 13%;
}
#kc-logo-wrapper {
position: absolute;
top: 50px;
right: 50px;
}
}
@media (min-width: 768px) {
#kc-container-wrapper {
position: absolute;
@ -326,7 +319,13 @@ ol#kc-totp-settings li:first-of-type {
}
}
@media (max-height: 500px) {
@media (min-height: 621px) {
#kc-container-wrapper {
bottom: 12%;
}
}
@media (max-height: 620px) {
#kc-container-wrapper {
}
}