diff --git a/adapters/oidc/adapter-core/pom.xml b/adapters/oidc/adapter-core/pom.xml index e97ca71dbf6..3509f9fef52 100755 --- a/adapters/oidc/adapter-core/pom.xml +++ b/adapters/oidc/adapter-core/pom.xml @@ -46,8 +46,6 @@ org.apache.http.impl.cookie.*;version=${apache.httpcomponents.fuse.version}, org.apache.http.impl.execchain.*;version=${apache.httpcomponents.fuse.version}, org.apache.http.*;version=${apache.httpcomponents.httpcore.fuse.version}, - org.apache.karaf.jaas.boot.principal;resolution:=optional, - org.apache.karaf.jaas.modules;resolution:=optional, *;resolution:=optional diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java deleted file mode 100755 index 2a8dc151637..00000000000 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java +++ /dev/null @@ -1,261 +0,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. - */ - -package org.keycloak.adapters.jaas; - -import org.jboss.logging.Logger; -import org.keycloak.KeycloakPrincipal; -import org.keycloak.adapters.AdapterUtils; -import org.keycloak.adapters.KeycloakDeployment; -import org.keycloak.adapters.KeycloakDeploymentBuilder; -import org.keycloak.adapters.RefreshableKeycloakSecurityContext; -import org.keycloak.adapters.rotation.AdapterTokenVerifier; -import org.keycloak.common.VerificationException; -import org.keycloak.common.util.FindFile; -import org.keycloak.common.util.reflections.Reflections; -import org.keycloak.representations.AccessToken; - -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.login.LoginException; -import javax.security.auth.spi.LoginModule; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.Principal; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * @author Marek Posolda - */ -public abstract class AbstractKeycloakLoginModule implements LoginModule { - - public static final String KEYCLOAK_CONFIG_FILE_OPTION = "keycloak-config-file"; - public static final String ROLE_PRINCIPAL_CLASS_OPTION = "role-principal-class"; - public static final String PROFILE_RESOURCE = "profile:"; - protected Subject subject; - protected CallbackHandler callbackHandler; - protected Auth auth; - protected KeycloakDeployment deployment; - protected String rolePrincipalClass; - - // This is to avoid parsing keycloak.json file in each request. Key is file location, Value is parsed keycloak deployment - private static ConcurrentMap deployments = new ConcurrentHashMap<>(); - - @Override - public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { - this.subject = subject; - this.callbackHandler = callbackHandler; - - String configFile = (String)options.get(KEYCLOAK_CONFIG_FILE_OPTION); - rolePrincipalClass = (String)options.get(ROLE_PRINCIPAL_CLASS_OPTION); - getLogger().debug("Declared options: " + KEYCLOAK_CONFIG_FILE_OPTION + "=" + configFile + ", " + ROLE_PRINCIPAL_CLASS_OPTION + "=" + rolePrincipalClass); - - if (configFile != null) { - deployment = deployments.get(configFile); - if (deployment == null) { - // lazy init of our deployment - deployment = resolveDeployment(configFile); - deployments.putIfAbsent(configFile, deployment); - } - } - } - - protected KeycloakDeployment resolveDeployment(String keycloakConfigFile) { - try { - InputStream is = null; - if (keycloakConfigFile.startsWith(PROFILE_RESOURCE)) { - try { - is = new URL(keycloakConfigFile).openStream(); - } catch (MalformedURLException mfue) { - throw new RuntimeException(mfue); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - } else { - is = FindFile.findFile(keycloakConfigFile); - } - KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is); - return kd; - - } catch (RuntimeException e) { - getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e); - throw e; - } - } - - - @Override - public boolean login() throws LoginException { - // get username and password - Callback[] callbacks = new Callback[2]; - callbacks[0] = new NameCallback("username"); - callbacks[1] = new PasswordCallback("password", false); - - try { - callbackHandler.handle(callbacks); - String username = ((NameCallback) callbacks[0]).getName(); - char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword(); - String password = new String(tmpPassword); - ((PasswordCallback) callbacks[1]).clearPassword(); - - Auth auth = doAuth(username, password); - if (auth != null) { - this.auth = auth; - return true; - } else { - return false; - } - } catch (UnsupportedCallbackException uce) { - getLogger().warn("Error: " + uce.getCallback().toString() - + " not available to gather authentication information from the user"); - return false; - } catch (Exception e) { - LoginException le = new LoginException(e.toString()); - le.initCause(e); - throw le; - } - } - - - @Override - public boolean commit() throws LoginException { - if (auth == null) { - return false; - } - - this.subject.getPrincipals().add(auth.getPrincipal()); - this.subject.getPrivateCredentials().add(auth.getTokenString()); - if (auth.getRoles() != null) { - for (String roleName : auth.getRoles()) { - Principal rolePrinc = createRolePrincipal(roleName); - this.subject.getPrincipals().add(rolePrinc); - } - } - - return true; - } - - - protected Principal createRolePrincipal(String roleName) { - if (rolePrincipalClass != null && rolePrincipalClass.length() > 0) { - try { - Class clazz = Reflections.classForName(rolePrincipalClass, getClass().getClassLoader()); - Constructor constructor = clazz.getDeclaredConstructor(String.class); - return constructor.newInstance(roleName); - } catch (Exception e) { - getLogger().warn("Unable to create declared roleClass " + rolePrincipalClass + " due to " + e.getMessage()); - } - } - - // Fallback to default rolePrincipal class - return new RolePrincipal(roleName); - } - - - @Override - public boolean abort() throws LoginException { - return true; - } - - @Override - public boolean logout() throws LoginException { - Set principals = new HashSet(subject.getPrincipals()); - for (Principal principal : principals) { - if (principal.getClass().equals(KeycloakPrincipal.class) || principal.getClass().equals(RolePrincipal.class)) { - subject.getPrincipals().remove(principal); - } - } - Set creds = subject.getPrivateCredentials(); - for (Object cred : creds) { - subject.getPrivateCredentials().remove(cred); - } - subject = null; - callbackHandler = null; - return true; - } - - - protected Auth bearerAuth(String tokenString) throws VerificationException { - AccessToken token = AdapterTokenVerifier.verifyToken(tokenString, deployment); - return postTokenVerification(tokenString, token); - } - - - /** - * Called after accessToken was verified (including signature, expiration etc) - * - */ - protected Auth postTokenVerification(String tokenString, AccessToken token) { - boolean verifyCaller; - if (deployment.isUseResourceRoleMappings()) { - verifyCaller = token.isVerifyCaller(deployment.getResourceName()); - } else { - verifyCaller = token.isVerifyCaller(); - } - if (verifyCaller) { - throw new IllegalStateException("VerifyCaller not supported yet in login module"); - } - - RefreshableKeycloakSecurityContext skSession = new RefreshableKeycloakSecurityContext(deployment, null, tokenString, token, null, null, null); - String principalName = AdapterUtils.getPrincipalName(deployment, token); - final KeycloakPrincipal principal = new KeycloakPrincipal(principalName, skSession); - final Set roles = AdapterUtils.getRolesFromSecurityContext(skSession); - return new Auth(principal, roles, tokenString); - } - - - protected abstract Auth doAuth(String username, String password) throws Exception; - - protected abstract Logger getLogger(); - - - public static class Auth { - private final KeycloakPrincipal principal; - private final Set roles; - private final String tokenString; - - public Auth(KeycloakPrincipal principal, Set roles, String accessToken) { - this.principal = principal; - this.roles = roles; - this.tokenString = accessToken; - } - - public KeycloakPrincipal getPrincipal() { - return principal; - } - - public Set getRoles() { - return roles; - } - - public String getTokenString() { - return tokenString; - } - } -} diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/BearerTokenLoginModule.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/BearerTokenLoginModule.java deleted file mode 100755 index f9a72f29935..00000000000 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/BearerTokenLoginModule.java +++ /dev/null @@ -1,45 +0,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. - */ - -package org.keycloak.adapters.jaas; - -import org.jboss.logging.Logger; -import org.keycloak.common.VerificationException; - -/** - * Login module, which allows to authenticate Keycloak access token in environments, which rely on JAAS - *

- * It expects login based on username and password where username doesn't matter and password is keycloak access token. - * - * @author Marek Posolda - */ -public class BearerTokenLoginModule extends AbstractKeycloakLoginModule { - - private static final Logger log = Logger.getLogger(BearerTokenLoginModule.class); - - @Override - protected Auth doAuth(String username, String password) throws VerificationException { - // Should do some checking of authenticated username if it's equivalent to passed value? - return bearerAuth(password); - } - - @Override - protected Logger getLogger() { - return log; - } - -} diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java deleted file mode 100755 index 945a2606d43..00000000000 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java +++ /dev/null @@ -1,193 +0,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. - */ - -package org.keycloak.adapters.jaas; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.message.BasicNameValuePair; -import org.jboss.logging.Logger; -import org.keycloak.OAuth2Constants; -import org.keycloak.adapters.AdapterUtils; -import org.keycloak.adapters.rotation.AdapterTokenVerifier; -import org.keycloak.common.VerificationException; -import org.keycloak.representations.AccessTokenResponse; -import org.keycloak.representations.idm.OAuth2ErrorRepresentation; -import org.keycloak.util.JsonSerialization; - -import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.login.LoginException; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.net.URI; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * Login module based on Resource Owner password credentials grant from OAuth2 specs. It's supposed to be used in environments. which - * can't rely on HTTP (like SSH authentication for instance). It needs that Direct Grant is enabled on particular realm in Keycloak. - * - * @author Marek Posolda - */ -public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule { - - private static final Logger log = Logger.getLogger(DirectAccessGrantsLoginModule.class); - - public static final String SCOPE_OPTION = "scope"; - - private String refreshToken; - private String scope; - - @Override - public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { - super.initialize(subject, callbackHandler, sharedState, options); - this.scope = (String)options.get(SCOPE_OPTION); - - // This is used just for logout - Iterator iterator = subject.getPrivateCredentials(RefreshTokenHolder.class).iterator(); - if (iterator.hasNext()) { - refreshToken = iterator.next().refreshToken; - } - } - - @Override - protected Auth doAuth(String username, String password) throws IOException, VerificationException { - return directGrantAuth(username, password); - } - - @Override - protected Logger getLogger() { - return log; - } - - protected Auth directGrantAuth(String username, String password) throws IOException, VerificationException { - String authServerBaseUrl = deployment.getAuthServerBaseUrl(); - HttpPost post = new HttpPost(deployment.getTokenUrl()); - List formparams = new ArrayList(); - formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)); - formparams.add(new BasicNameValuePair("username", username)); - formparams.add(new BasicNameValuePair("password", password)); - - if (scope != null) { - formparams.add(new BasicNameValuePair(OAuth2Constants.SCOPE, scope)); - } - - AdapterUtils.setClientCredentials(deployment, post, formparams); - - UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8"); - post.setEntity(form); - - HttpClient client = deployment.getClient(); - HttpResponse response = client.execute(post); - int status = response.getStatusLine().getStatusCode(); - HttpEntity entity = response.getEntity(); - if (status != 200) { - StringBuilder errorBuilder = new StringBuilder("Login failed. Invalid status: " + status); - if (entity != null) { - InputStream is = entity.getContent(); - OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class); - errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError()) - .append(", Error description: " + errorRep.getErrorDescription()); - } - String error = errorBuilder.toString(); - log.warn(error); - throw new IOException(error); - } - - if (entity == null) { - throw new IOException("No Entity"); - } - - InputStream is = entity.getContent(); - AccessTokenResponse tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class); - - // refreshToken will be saved to privateCreds of Subject for now - refreshToken = tokenResponse.getRefreshToken(); - - AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(tokenResponse.getToken(), tokenResponse.getIdToken(), deployment); - return postTokenVerification(tokenResponse.getToken(), tokens.getAccessToken()); - } - - @Override - public boolean commit() throws LoginException { - boolean superCommit = super.commit(); - - // refreshToken will be saved to privateCreds of Subject for now - if (refreshToken != null) { - RefreshTokenHolder refreshTokenHolder = new RefreshTokenHolder(); - refreshTokenHolder.refreshToken = refreshToken; - subject.getPrivateCredentials().add(refreshTokenHolder); - } - - return superCommit; - } - - @Override - public boolean logout() throws LoginException { - if (refreshToken != null) { - try { - URI logoutUri = deployment.getLogoutUrl().clone().build(); - HttpPost post = new HttpPost(logoutUri); - - List formparams = new ArrayList<>(); - AdapterUtils.setClientCredentials(deployment, post, formparams); - formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken)); - - UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8"); - post.setEntity(form); - - HttpClient client = deployment.getClient(); - HttpResponse response = client.execute(post); - int status = response.getStatusLine().getStatusCode(); - HttpEntity entity = response.getEntity(); - if (status != 204) { - StringBuilder errorBuilder = new StringBuilder("Logout of refreshToken failed. Invalid status: " + status); - if (entity != null) { - InputStream is = entity.getContent(); - if (status == 400) { - OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class); - errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError()) - .append(", Error description: " + errorRep.getErrorDescription()); - - } else { - if (is != null) is.close(); - } - } - - // Should do something better than warn if logout failed? Perhaps update of refresh tokens on existing subject might be supported too... - log.warn(errorBuilder.toString()); - } - } catch (IOException ioe) { - log.warn(ioe); - } - } - - return super.logout(); - } - - private static class RefreshTokenHolder implements Serializable { - private String refreshToken; - } -} diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/RolePrincipal.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/RolePrincipal.java deleted file mode 100755 index 60890ffb9a4..00000000000 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/RolePrincipal.java +++ /dev/null @@ -1,53 +0,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. - */ - -package org.keycloak.adapters.jaas; - -import java.io.Serializable; -import java.security.Principal; - -/** - * @author Marek Posolda - */ -public class RolePrincipal implements Principal, Serializable { - - private String roleName = null; - - public RolePrincipal(String roleName) { - this.roleName = roleName; - } - - public boolean equals (Object p) { - if (! (p instanceof RolePrincipal)) - return false; - return getName().equals(((RolePrincipal)p).getName()); - } - - public int hashCode () { - return getName().hashCode(); - } - - public String getName () { - return this.roleName; - } - - public String toString () - { - return getName(); - } - -} diff --git a/docs/documentation/securing_apps/topics/oidc/java/jaas.adoc b/docs/documentation/securing_apps/topics/oidc/java/jaas.adoc deleted file mode 100644 index 2493818c918..00000000000 --- a/docs/documentation/securing_apps/topics/oidc/java/jaas.adoc +++ /dev/null @@ -1,37 +0,0 @@ -[[_jaas_adapter]] -==== JAAS plugin - -include::adapter-deprecation-notice.adoc[] - -It's generally not needed to use JAAS for most of the applications, especially if they are HTTP based, and you should most likely choose one of our other adapters. -However, some applications and systems may still rely on pure legacy JAAS solution. -{project_name} provides two login modules to help in these situations. - -The provided login modules are: - -org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule:: - This login module allows to authenticate with username/password from {project_name}. - It's using <<_resource_owner_password_credentials_flow,Resource Owner Password Credentials>> flow to validate if the provided - username/password is valid. It's useful for non-web based systems, which need to rely on JAAS and want to use {project_name}, but can't use the standard browser - based flows due to their non-web nature. Example of such application could be messaging or SSH. - -org.keycloak.adapters.jaas.BearerTokenLoginModule:: - This login module allows to authenticate with {project_name} access token passed to it through CallbackHandler as password. - It may be useful for example in case, when you have {project_name} access token from standard based authentication flow and your web application then - needs to talk to external non-web based system, which rely on JAAS. For example a messaging system. - -Both modules use the following configuration properties: - -keycloak-config-file:: - The location of the `keycloak.json` configuration file. The configuration file can either be located on the filesystem or on the classpath. If it's located - on the classpath you need to prefix the location with `classpath:` (for example `classpath:/path/keycloak.json`). - This is _REQUIRED._ - -`role-principal-class`:: - Configure alternative class for Role principals attached to JAAS Subject. - Default value is `org.keycloak.adapters.jaas.RolePrincipal`. Note: The class is required to have a constructor with a single `String` argument. - -`scope`:: - This option is only applicable to the `DirectAccessGrantsLoginModule`. The specified value will be used as the OAuth2 `scope` - parameter in the Resource Owner Password Credentials Grant request. - diff --git a/docs/documentation/securing_apps/topics/oidc/java/java-adapters.adoc b/docs/documentation/securing_apps/topics/oidc/java/java-adapters.adoc index 2de1971a401..29ebf96a88c 100644 --- a/docs/documentation/securing_apps/topics/oidc/java/java-adapters.adoc +++ b/docs/documentation/securing_apps/topics/oidc/java/java-adapters.adoc @@ -21,10 +21,6 @@ ifeval::[{project_community}==true] include::servlet-filter-adapter.adoc[] endif::[] -ifeval::[{project_community}==true] -include::jaas.adoc[] -endif::[] - ifeval::[{project_community}==true] include::adapter-context.adoc[] include::adapter_error_handling.adoc[] diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/eap6/src/main/resources/config/fuse/add-hawtio.xsl b/testsuite/integration-arquillian/servers/app-server/jboss/eap6/src/main/resources/config/fuse/add-hawtio.xsl index 756aebe242f..c028a986cae 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/eap6/src/main/resources/config/fuse/add-hawtio.xsl +++ b/testsuite/integration-arquillian/servers/app-server/jboss/eap6/src/main/resources/config/fuse/add-hawtio.xsl @@ -14,23 +14,12 @@ - - - - - - - - - - - diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/jaas/LoginModulesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/jaas/LoginModulesTest.java deleted file mode 100644 index 7cc360e748e..00000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/jaas/LoginModulesTest.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright 2017 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.jaas; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.invoke.MethodHandles; -import java.net.URI; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.login.AppConfigurationEntry; -import javax.security.auth.login.Configuration; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.junit.AfterClass; -import org.junit.Assume; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.keycloak.KeycloakPrincipal; -import org.keycloak.adapters.jaas.AbstractKeycloakLoginModule; -import org.keycloak.adapters.jaas.BearerTokenLoginModule; -import org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule; -import org.keycloak.adapters.jaas.RolePrincipal; -import org.keycloak.admin.client.resource.ClientResource; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.testsuite.AbstractKeycloakTest; -import org.keycloak.testsuite.Assert; -import org.keycloak.testsuite.admin.ApiUtil; -import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED; -import org.keycloak.testsuite.utils.io.IOUtil; - -/** - * @author Marek Posolda - */ -public class LoginModulesTest extends AbstractKeycloakTest { - - public static final URI DIRECT_GRANT_CONFIG; - public static final URI BEARER_CONFIG; - - private static final File DIRECT_GRANT_CONFIG_FILE; - private static final File BEARER_CONFIG_FILE; - - static { - try { - DIRECT_GRANT_CONFIG = MethodHandles.lookup().lookupClass().getResource("/adapter-test/customer-portal/WEB-INF/keycloak.json").toURI(); - BEARER_CONFIG = MethodHandles.lookup().lookupClass().getResource("/adapter-test/customer-db-audience-required/WEB-INF/keycloak.json").toURI(); - - DIRECT_GRANT_CONFIG_FILE = File.createTempFile("LoginModulesTest", "testDirectAccessGrantLoginModuleLoginFailed"); - BEARER_CONFIG_FILE = File.createTempFile("LoginModulesTest", "testBearerLoginFailedLogin"); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void addTestRealms(List testRealms) { - testRealms.add(IOUtil.loadRealm("/adapter-test/demorealm.json")); - } - - private static void enabled() { - Assume.assumeTrue(AUTH_SERVER_SSL_REQUIRED); - } - - @BeforeClass - public static void createTemporaryFiles() throws Exception { - enabled(); - - copyContentAndReplaceAuthServerAddress(new File(DIRECT_GRANT_CONFIG), DIRECT_GRANT_CONFIG_FILE); - copyContentAndReplaceAuthServerAddress(new File(BEARER_CONFIG), BEARER_CONFIG_FILE); - } - - @AfterClass - public static void removeTemporaryFiles() { - DIRECT_GRANT_CONFIG_FILE.deleteOnExit(); - BEARER_CONFIG_FILE.deleteOnExit(); - } - - private static void copyContentAndReplaceAuthServerAddress(File input, File output) throws IOException { - try (InputStream inputStream = httpsAwareConfigurationStream(new FileInputStream(input))) { - try (FileOutputStream outputStream = new FileOutputStream(output)) { - byte[] buffer = new byte[inputStream.available()]; - inputStream.read(buffer); - outputStream.write(buffer); - } - } - } - - @Before - public void generateAudienceClientScope() { - if (ApiUtil.findClientScopeByName(adminClient.realm("demo"), "customer-db-audience-required") != null) { - return; - } - - // Generate audience client scope - String clientScopeId = testingClient.testing().generateAudienceClientScope("demo", "customer-db-audience-required"); - - ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("demo"), "customer-portal"); - client.addOptionalClientScope(clientScopeId); - } - - - @Test - public void testDirectAccessGrantLoginModuleLoginFailed() throws Exception { - LoginContext loginContext = new LoginContext("does-not-matter", null, - createJaasCallbackHandler("bburke@redhat.com", "bad-password"), - createJaasConfigurationForDirectGrant(null)); - - try { - loginContext.login(); - Assert.fail("Not expected to successfully login"); - } catch (LoginException le) { - // Ignore - } - } - - - @Test - public void testDirectAccessGrantLoginModuleLoginSuccess() throws Exception { - oauth.realm("demo"); - - LoginContext loginContext = directGrantLogin(null); - Subject subject = loginContext.getSubject(); - - // Assert principals in subject - KeycloakPrincipal principal = subject.getPrincipals(KeycloakPrincipal.class).iterator().next(); - Assert.assertEquals("bburke@redhat.com", principal.getKeycloakSecurityContext().getToken().getPreferredUsername()); - assertToken(principal.getKeycloakSecurityContext().getTokenString(), true); - - Set roles = subject.getPrincipals(RolePrincipal.class); - Assert.assertEquals(1, roles.size()); - Assert.assertEquals("user", roles.iterator().next().getName()); - - // Logout and assert token not valid anymore - loginContext.logout(); - assertToken(principal.getKeycloakSecurityContext().getTokenString(), false); - } - - - @Test - public void testBearerLoginFailedLogin() throws Exception { - oauth.realm("demo"); - - LoginContext directGrantCtx = directGrantLogin(null); - String accessToken = directGrantCtx.getSubject().getPrincipals(KeycloakPrincipal.class).iterator().next() - .getKeycloakSecurityContext().getTokenString(); - - LoginContext bearerCtx = new LoginContext("does-not-matter", null, - createJaasCallbackHandler("doesn-not-matter", accessToken), - createJaasConfigurationForBearer()); - - // Login should fail due insufficient audience in the token - try { - bearerCtx.login(); - Assert.fail("Not expected to successfully login"); - } catch (LoginException le) { - // Ignore - } - - directGrantCtx.logout(); - } - - - @Test - public void testBearerLoginSuccess() throws Exception { - oauth.realm("demo"); - - LoginContext directGrantCtx = directGrantLogin("customer-db-audience-required"); - String accessToken = directGrantCtx.getSubject().getPrincipals(KeycloakPrincipal.class).iterator().next() - .getKeycloakSecurityContext().getTokenString(); - - LoginContext bearerCtx = new LoginContext("does-not-matter", null, - createJaasCallbackHandler("doesn-not-matter", accessToken), - createJaasConfigurationForBearer()); - - // Login should be successful - bearerCtx.login(); - - // Assert subject - Subject subject = bearerCtx.getSubject(); - - KeycloakPrincipal principal = subject.getPrincipals(KeycloakPrincipal.class).iterator().next(); - Assert.assertEquals("bburke@redhat.com", principal.getKeycloakSecurityContext().getToken().getPreferredUsername()); - assertToken(principal.getKeycloakSecurityContext().getTokenString(), true); - - Set roles = subject.getPrincipals(RolePrincipal.class); - Assert.assertEquals(1, roles.size()); - Assert.assertEquals("user", roles.iterator().next().getName()); - - // Logout - bearerCtx.logout(); - directGrantCtx.logout(); - } - - - private LoginContext directGrantLogin(String scope) throws LoginException { - LoginContext loginContext = new LoginContext("does-not-matter", null, - createJaasCallbackHandler("bburke@redhat.com", "password"), - createJaasConfigurationForDirectGrant(scope)); - - loginContext.login(); - - return loginContext; - } - - - private void assertToken(String accessToken, boolean expectActive) throws IOException { - String introspectionResponse = oauth.introspectAccessTokenWithClientCredential("customer-portal", "password", accessToken); - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode jsonNode = objectMapper.readTree(introspectionResponse); - Assert.assertEquals(expectActive, jsonNode.get("active").asBoolean()); - } - - - private CallbackHandler createJaasCallbackHandler(final String principal, final String password) { - return new CallbackHandler() { - - @Override - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - for (Callback callback : callbacks) { - if (callback instanceof NameCallback) { - NameCallback nameCallback = (NameCallback) callback; - nameCallback.setName(principal); - } else if (callback instanceof PasswordCallback) { - PasswordCallback passwordCallback = (PasswordCallback) callback; - passwordCallback.setPassword(password.toCharArray()); - } else { - throw new UnsupportedCallbackException(callback, "Unsupported callback: " + callback.getClass().getCanonicalName()); - } - } - } - }; - } - - - private Configuration createJaasConfigurationForDirectGrant(String scope) { - return new Configuration() { - - @Override - public AppConfigurationEntry[] getAppConfigurationEntry(String name) { - Map options = new HashMap<>(); - options.put(AbstractKeycloakLoginModule.KEYCLOAK_CONFIG_FILE_OPTION, DIRECT_GRANT_CONFIG_FILE.getAbsolutePath()); - if (scope != null) { - options.put(DirectAccessGrantsLoginModule.SCOPE_OPTION, scope); - } - - AppConfigurationEntry LMConfiguration = new AppConfigurationEntry(DirectAccessGrantsLoginModule.class.getName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); - return new AppConfigurationEntry[] { LMConfiguration }; - } - }; - } - - - private Configuration createJaasConfigurationForBearer() { - return new Configuration() { - - @Override - public AppConfigurationEntry[] getAppConfigurationEntry(String name) { - Map options = new HashMap<>(); - options.put(AbstractKeycloakLoginModule.KEYCLOAK_CONFIG_FILE_OPTION, BEARER_CONFIG_FILE.getAbsolutePath()); - - AppConfigurationEntry LMConfiguration = new AppConfigurationEntry(BearerTokenLoginModule.class.getName(), AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); - return new AppConfigurationEntry[] { LMConfiguration }; - } - }; - } -} diff --git a/testsuite/integration-arquillian/tests/base/testsuites/base-suite b/testsuite/integration-arquillian/tests/base/testsuites/base-suite index f6f72523f00..d537397e8e1 100644 --- a/testsuite/integration-arquillian/tests/base/testsuites/base-suite +++ b/testsuite/integration-arquillian/tests/base/testsuites/base-suite @@ -19,7 +19,6 @@ feature,4 federation,5 forms,5 i18n,5 -jaas,5 javascript,5 keys,4 login,4 diff --git a/testsuite/integration-arquillian/util/src/main/java/org/keycloak/testsuite/utils/fuse/FuseUtils.java b/testsuite/integration-arquillian/util/src/main/java/org/keycloak/testsuite/utils/fuse/FuseUtils.java index cdda36d7478..814bd813891 100644 --- a/testsuite/integration-arquillian/util/src/main/java/org/keycloak/testsuite/utils/fuse/FuseUtils.java +++ b/testsuite/integration-arquillian/util/src/main/java/org/keycloak/testsuite/utils/fuse/FuseUtils.java @@ -114,7 +114,7 @@ public class FuseUtils { "feature:repo-add mvn:org.keycloak/keycloak-osgi-features/" + fuseAdapterVersion + "/xml/features; " + "feature:repo-add mvn:org.keycloak.testsuite/fuse-example-keycloak-features/" + projectVersion + "/xml/features; " + "feature:install pax-web-http-undertow; " + - "feature:install keycloak-jaas keycloak-pax-http-undertow; " + + "feature:install keycloak-pax-http-undertow; " + "feature:install keycloak-fuse-7.0-example", Result.OK); @@ -135,7 +135,7 @@ public class FuseUtils { "system:property -p hawtio.keycloakClientConfig ${karaf.etc}/keycloak-hawtio-client.json; " + "system:property -p hawtio.keycloakServerConfig ${karaf.etc}/keycloak-bearer.json; " + "system:property -p hawtio.roles admin,manager,viewer,ssh; " + - "system:property -p hawtio.rolePrincipalClasses org.keycloak.adapters.jaas.RolePrincipal,org.apache.karaf.jaas.boot.principal.RolePrincipal;", + "system:property -p hawtio.rolePrincipalClasses org.apache.karaf.jaas.boot.principal.RolePrincipal;", Result.EMPTY); // KEYCLOAK-17873 For older version of Fuse @@ -189,7 +189,7 @@ public class FuseUtils { "system-property -p hawtio.keycloakEnabled true; " + "system-property -p hawtio.realm keycloak; " + "system-property -p hawtio.keycloakClientConfig file://" + appServerHome + "/etc/keycloak-hawtio-client.json; " + - "system-property -p hawtio.rolePrincipalClasses org.keycloak.adapters.jaas.RolePrincipal,org.apache.karaf.jaas.boot.principal.RolePrincipal; ", + "system-property -p hawtio.rolePrincipalClasses org.apache.karaf.jaas.boot.principal.RolePrincipal; ", Result.EMPTY); String output = getCommandOutput(managementUser, managementPassword, "osgi:list | grep hawtio | grep web;");