From 2a78d0d4d0001b6cd3953e2a6f182bca0a70d23e Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 24 Nov 2014 18:56:40 +0100 Subject: [PATCH] KEYCLOAK-859 Added DirectAccessGrantsLoginModule --- .../adapters/AdapterDeploymentContext.java | 2 +- .../adapters/BearerTokenLoginModule.java | 258 ------------------ .../jaas/AbstractKeycloakLoginModule.java | 196 +++++++++++++ .../adapters/jaas/BearerTokenLoginModule.java | 32 +++ .../jaas/DirectAccessGrantsLoginModule.java | 187 +++++++++++++ .../keycloak/adapters/jaas/RolePrincipal.java | 37 +++ 6 files changed, 453 insertions(+), 259 deletions(-) delete mode 100755 integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenLoginModule.java create mode 100644 integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java create mode 100755 integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/BearerTokenLoginModule.java create mode 100644 integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java create mode 100644 integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/RolePrincipal.java diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java index 532b2097e5..6258645273 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java @@ -96,7 +96,7 @@ public class AdapterDeploymentContext { } } - protected void resolveRealmKey(KeycloakDeployment deployment) { + public void resolveRealmKey(KeycloakDeployment deployment) { if (deployment.getClient() == null) { throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs"); } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenLoginModule.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenLoginModule.java deleted file mode 100755 index 965f2b4a3e..0000000000 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenLoginModule.java +++ /dev/null @@ -1,258 +0,0 @@ -package org.keycloak.adapters; - -import java.io.InputStream; -import java.io.Serializable; -import java.security.Principal; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; - -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 org.keycloak.KeycloakPrincipal; -import org.keycloak.RSATokenVerifier; -import org.keycloak.VerificationException; -import org.keycloak.representations.AccessToken; -import org.keycloak.representations.adapters.config.AdapterConfig; - -/** - * 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 must be equal to "Bearer" and password is keycloak access token. - * - * @author Marek Posolda - */ -public class BearerTokenLoginModule implements LoginModule { - - private final static Logger log = Logger.getLogger("" + BearerTokenLoginModule.class); - - public static final String KEYCLOAK_CONFIG_FILE_OPTION = "keycloak-config-file"; - public static final String REALM_OPTION = "realm"; - public static final String RESOURCE_OPTION = "resource"; - public static final String PUBLIC_KEY_OPTION = "realm-public-key"; - public static final String AUTH_SERVER_URL_OPTION = "auth-server-url"; - public static final String USE_RESOURCE_ROLE_MAPPINGS_OPTION = "use-resource-role-mappings"; - public static final String PRINCIPAL_ATTRIBUTE_OPTION = "principal-attribute"; - - private Subject subject; - private CallbackHandler callbackHandler; - private Auth auth; - - private static volatile KeycloakDeployment deployment; - - @Override - public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { - this.subject = subject; - this.callbackHandler = callbackHandler; - - // Static as we don't want to parse config file in each authentication - if (deployment == null) { - KeycloakDeployment kd; - String configFile = (String)options.get(KEYCLOAK_CONFIG_FILE_OPTION); - if (configFile != null) { - InputStream is = loadKeycloakConfigFile(configFile); - kd = KeycloakDeploymentBuilder.build(is); - } else { - // init everything from provided options - String realm = (String) options.get(REALM_OPTION); - if (realm == null) { - throw new IllegalArgumentException("Realm is mandatory if you didn't provide keycloak-config-file"); - } - String authServerUrl = (String) options.get(AUTH_SERVER_URL_OPTION); - String publicKey = (String) options.get(PUBLIC_KEY_OPTION); - if (publicKey == null && authServerUrl == null) { - throw new IllegalArgumentException("Option " + PUBLIC_KEY_OPTION + " is mandatory if you didn't provide keycloak-config-file or auth-server-url to resolver public key"); - } - String resource = (String) options.get(RESOURCE_OPTION); - String resRoleMappings = (String) options.get(USE_RESOURCE_ROLE_MAPPINGS_OPTION); - boolean useResourceRoleMappings = resRoleMappings == null ? false : Boolean.parseBoolean(resRoleMappings); - if (useResourceRoleMappings && resource == null) { - throw new IllegalArgumentException("You want resource-role-mappings, but you didn't provide resource in configuration"); - } - String principalAttribute = (String) options.get(PRINCIPAL_ATTRIBUTE_OPTION); - - AdapterConfig cfg = new AdapterConfig(); - cfg.setRealm(realm); - cfg.setResource(resource); - cfg.setUseResourceRoleMappings(useResourceRoleMappings); - cfg.setAuthServerUrl(authServerUrl); - cfg.setBearerOnly(true); - cfg.setPrincipalAttribute(principalAttribute); - cfg.setRealmKey(publicKey); - kd = KeycloakDeploymentBuilder.build(cfg); - } - - if (kd.getRealmKey() == null) { - new AdapterDeploymentContext().resolveRealmKey(kd); - } - deployment = kd; - } - } - - protected InputStream loadKeycloakConfigFile(String keycloakConfigFile) { - return FindFile.findFile(keycloakConfigFile); - } - - @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 = bearerAuth(username, password); - if (auth != null) { - this.auth = auth; - return true; - } else { - return false; - } - } catch (UnsupportedCallbackException uce) { - LoginException le = new LoginException("Error: " + uce.getCallback().toString() - + " not available to gather authentication information from the user"); - le.initCause(uce); - throw le; - } catch (Exception ioe) { - LoginException le = new LoginException(ioe.toString()); - le.initCause(ioe); - throw le; - } - } - - protected Auth bearerAuth(String username, String tokenString) throws VerificationException { - if (!"Bearer".equalsIgnoreCase(username)) { - log.fine("Username is expected to be bearer but is " + username + ". Ignoring login module"); - return null; - } - - AccessToken token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealm()); - - 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); - } - - @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()) { - RolePrincipal rolePrinc = new RolePrincipal(roleName); - this.subject.getPrincipals().add(rolePrinc); - } - } - - return true; - } - - // Might be needed if subclass wants to setup security context in some env specific way - protected Auth getAuth() { - return auth; - } - - @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; - } - - private static class RolePrincipal implements Principal, Serializable { - private static final long serialVersionUID = -5538962177019315447L; - 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(); - } - } - - 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/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java new file mode 100644 index 0000000000..bf5f52714b --- /dev/null +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java @@ -0,0 +1,196 @@ +package org.keycloak.adapters.jaas; + +import java.io.InputStream; +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; + +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 org.jboss.logging.Logger; +import org.keycloak.KeycloakPrincipal; +import org.keycloak.RSATokenVerifier; +import org.keycloak.VerificationException; +import org.keycloak.adapters.AdapterDeploymentContext; +import org.keycloak.adapters.AdapterUtils; +import org.keycloak.adapters.FindFile; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.KeycloakDeploymentBuilder; +import org.keycloak.adapters.RefreshableKeycloakSecurityContext; +import org.keycloak.representations.AccessToken; + +/** + * @author Marek Posolda + */ +public abstract class AbstractKeycloakLoginModule implements LoginModule { + + public static final String KEYCLOAK_CONFIG_FILE_OPTION = "keycloak-config-file"; + + protected Subject subject; + protected CallbackHandler callbackHandler; + protected Auth auth; + protected KeycloakDeployment deployment; + + // 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); + 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) { + InputStream is = FindFile.findFile(keycloakConfigFile); + KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is); + if (kd.getRealmKey() == null) { + new AdapterDeploymentContext().resolveRealmKey(kd); + } + + return kd; + } + + @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()) { + RolePrincipal rolePrinc = new RolePrincipal(roleName); + this.subject.getPrincipals().add(rolePrinc); + } + } + + return true; + } + + @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 = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealm()); + + 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/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/BearerTokenLoginModule.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/BearerTokenLoginModule.java new file mode 100755 index 0000000000..b6671c6a56 --- /dev/null +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/BearerTokenLoginModule.java @@ -0,0 +1,32 @@ +package org.keycloak.adapters.jaas; + +import org.jboss.logging.Logger; +import org.keycloak.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 must be equal to "Bearer" 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 { + if (!"Bearer".equalsIgnoreCase(username)) { + log.debug("Username is expected to be bearer but is " + username + ". Ignoring login module"); + return null; + } + + return bearerAuth(password); + } + + @Override + protected Logger getLogger() { + return log; + } + +} diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java new file mode 100644 index 0000000000..ae1c0bd018 --- /dev/null +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java @@ -0,0 +1,187 @@ +package org.keycloak.adapters.jaas; + +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; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.LoginException; + +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.VerificationException; +import org.keycloak.constants.ServiceUrlConstants; +import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.util.BasicAuthHelper; +import org.keycloak.util.JsonSerialization; +import org.keycloak.util.KeycloakUriBuilder; + +/** + * 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); + + private String refreshToken; + + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { + super.initialize(subject, callbackHandler, sharedState, options); + + // 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 { + if ("Bearer".equalsIgnoreCase(username)) { + log.debug("Username is not expected to be bearer for this login module. Ignoring login module"); + return null; + } + return directGrantAuth(username, password); + } + + @Override + protected Logger getLogger() { + return log; + } + + protected Auth directGrantAuth(String username, String password) throws IOException, VerificationException { + String authServerBaseUrl = deployment.getAuthServerBaseUrl(); + URI directGrantUri = KeycloakUriBuilder.fromUri(authServerBaseUrl).path(ServiceUrlConstants.TOKEN_SERVICE_DIRECT_GRANT_PATH).build(deployment.getRealm()); + HttpPost post = new HttpPost(directGrantUri); + + List formparams = new ArrayList(); + formparams.add(new BasicNameValuePair("username", username)); + formparams.add(new BasicNameValuePair("password", password)); + + if (deployment.isPublicClient()) { // if client is public access type + formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, deployment.getResourceName())); + } else { + String clientId = deployment.getResourceName(); + String clientSecret = deployment.getResourceCredentials().get("secret"); + String authorization = BasicAuthHelper.createHeader(clientId, clientSecret); + post.setHeader("Authorization", authorization); + } + 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(); + Map errors = (Map) JsonSerialization.readValue(is, Map.class); + errorBuilder.append(", OAuth2 error. Error: " + errors.get(OAuth2Constants.ERROR)) + .append(", Error description: " + errors.get(OAuth2Constants.ERROR_DESCRIPTION)); + } + 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(); + + return bearerAuth(tokenResponse.getToken()); + } + + @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(); + if (deployment.isPublicClient()) { // if client is public access type + formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, deployment.getResourceName())); + } else { + String clientId = deployment.getResourceName(); + String clientSecret = deployment.getResourceCredentials().get("secret"); + String authorization = BasicAuthHelper.createHeader(clientId, clientSecret); + post.setHeader("Authorization", authorization); + } + + 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) { + Map errors = (Map) JsonSerialization.readValue(is, Map.class); + errorBuilder.append(", OAuth2 error. Error: " + errors.get(OAuth2Constants.ERROR)) + .append(", Error description: " + errors.get(OAuth2Constants.ERROR_DESCRIPTION)); + + } 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/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/RolePrincipal.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/RolePrincipal.java new file mode 100644 index 0000000000..a4e442305c --- /dev/null +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/RolePrincipal.java @@ -0,0 +1,37 @@ +package org.keycloak.adapters.jaas; + +import java.io.Serializable; +import java.security.Principal; + +/** + * @author Marek Posolda + */ +public class RolePrincipal implements Principal, Serializable { + + private static final long serialVersionUID = -5538962177019315447L; + 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(); + } + +}