From 6fdff819a1c965738de2f510bd794b733996662a Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Sat, 27 Jul 2013 18:19:23 -0400 Subject: [PATCH] logout --- .../java/org/keycloak/RealmConfiguration.java | 15 +--- .../java/org/keycloak/ResourceMetadata.java | 2 +- .../java/org/keycloak/TokenIdGenerator.java | 15 ++++ .../idm/CredentialRepresentation.java | 35 ++++++++ .../idm/ResourceRepresentation.java | 35 ++++++++ .../idm/UserRepresentation.java | 51 +++-------- .../idm/admin/AdminAction.java | 66 ++++++++++++++ .../idm/admin/LogoutAction.java | 26 ++++++ .../main/webapp/WEB-INF/resteasy-oauth.json | 4 +- .../src/main/webapp/customers/view.jsp | 8 +- .../main/webapp/WEB-INF/resteasy-oauth.json | 1 + .../main/webapp/WEB-INF/resteasy-oauth.json | 4 +- .../src/main/webapp/products/view.jsp | 9 +- .../src/main/webapp/META-INF/testrealm.json | 36 ++++---- .../as7/BearerTokenAuthenticatorValve.java | 2 +- .../as7/CatalinaBearerTokenAuthenticator.java | 9 +- .../as7/OAuthAuthenticationServerValve.java | 6 +- .../as7/OAuthManagedResourceValve.java | 88 +++++++++++++------ .../adapters/as7/ServletOAuthLogin.java | 8 +- .../as7/config/ManagedResourceConfig.java | 28 +++--- .../config/ManagedResourceConfigLoader.java | 3 +- .../managers/AuthenticationManager.java | 22 +++-- .../services/managers/RealmManager.java | 64 ++++++-------- .../managers/ResourceAdminManager.java | 47 ++++++++++ .../services/managers/TokenManager.java | 63 +++++++++---- .../keycloak/services/models/RealmModel.java | 13 ++- .../services/models/ResourceModel.java | 18 +++- .../relationships/ResourceRelationship.java | 23 +++++ .../services/resources/RealmSubResource.java | 12 +-- .../resources/RegistrationService.java | 5 +- .../services/resources/TokenService.java | 65 ++++++++------ .../java/org/keycloak/test/AdapterTest.java | 2 +- .../java/org/keycloak/test/ImportTest.java | 2 +- 33 files changed, 551 insertions(+), 236 deletions(-) create mode 100755 core/src/main/java/org/keycloak/TokenIdGenerator.java create mode 100755 core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java create mode 100755 core/src/main/java/org/keycloak/representations/idm/admin/AdminAction.java create mode 100755 core/src/main/java/org/keycloak/representations/idm/admin/LogoutAction.java mode change 100644 => 100755 examples/as7-eap-demo/customer-app/src/main/webapp/customers/view.jsp mode change 100644 => 100755 examples/as7-eap-demo/product-app/src/main/webapp/products/view.jsp create mode 100755 services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java diff --git a/core/src/main/java/org/keycloak/RealmConfiguration.java b/core/src/main/java/org/keycloak/RealmConfiguration.java index 8563cdf875..5e4a7a45d4 100755 --- a/core/src/main/java/org/keycloak/RealmConfiguration.java +++ b/core/src/main/java/org/keycloak/RealmConfiguration.java @@ -15,8 +15,7 @@ public class RealmConfiguration { protected ResteasyClient client; protected UriBuilder authUrl; protected ResteasyWebTarget codeUrl; - protected String clientId; - protected Form credentials = new Form(); + protected Form resourceCredentials = new Form(); protected boolean sslRequired = true; protected String stateCookieName = "OAuth_Token_Request_State"; @@ -44,16 +43,8 @@ public class RealmConfiguration { this.authUrl = authUrl; } - public String getClientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - - public Form getCredentials() { - return credentials; + public Form getResourceCredentials() { + return resourceCredentials; } public ResteasyWebTarget getCodeUrl() { diff --git a/core/src/main/java/org/keycloak/ResourceMetadata.java b/core/src/main/java/org/keycloak/ResourceMetadata.java index 24a277297e..0a3f811d22 100755 --- a/core/src/main/java/org/keycloak/ResourceMetadata.java +++ b/core/src/main/java/org/keycloak/ResourceMetadata.java @@ -8,8 +8,8 @@ import java.security.PublicKey; * @version $Revision: 1 $ */ public class ResourceMetadata { - protected String resourceName; protected String realm; + protected String resourceName; protected KeyStore clientKeystore; protected String clientKeyPassword; protected KeyStore truststore; diff --git a/core/src/main/java/org/keycloak/TokenIdGenerator.java b/core/src/main/java/org/keycloak/TokenIdGenerator.java new file mode 100755 index 0000000000..f1b5e55d5b --- /dev/null +++ b/core/src/main/java/org/keycloak/TokenIdGenerator.java @@ -0,0 +1,15 @@ +package org.keycloak; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class TokenIdGenerator { + private static final AtomicLong counter = new AtomicLong(); + public static String generateId() { + return UUID.randomUUID().toString() + "-" + System.currentTimeMillis(); + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java new file mode 100755 index 0000000000..22593171e7 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java @@ -0,0 +1,35 @@ +package org.keycloak.representations.idm; + +/** +* @author Bill Burke +* @version $Revision: 1 $ +*/ +public class CredentialRepresentation { + protected String type; + protected String value; + protected boolean hashed; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public boolean isHashed() { + return hashed; + } + + public void setHashed(boolean hashed) { + this.hashed = hashed; + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ResourceRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ResourceRepresentation.java index 529ed35a29..15c49f994e 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ResourceRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ResourceRepresentation.java @@ -12,7 +12,10 @@ import java.util.Set; public class ResourceRepresentation { protected String self; // link protected String name; + protected String adminUrl; protected boolean surrogateAuthRequired; + protected boolean useRealmMappings; + protected List credentials; protected Set roles; protected List roleMappings; protected List scopeMappings; @@ -79,5 +82,37 @@ public class ResourceRepresentation { return mapping; } + public String getAdminUrl() { + return adminUrl; + } + public void setAdminUrl(String adminUrl) { + this.adminUrl = adminUrl; + } + + public List getCredentials() { + return credentials; + } + + public void setCredentials(List credentials) { + this.credentials = credentials; + } + + public ResourceRepresentation credential(String type, String value, boolean hashed) { + if (this.credentials == null) credentials = new ArrayList(); + CredentialRepresentation cred = new CredentialRepresentation(); + cred.setType(type); + cred.setValue(value); + cred.setHashed(hashed); + credentials.add(cred); + return this; + } + + public boolean isUseRealmMappings() { + return useRealmMappings; + } + + public void setUseRealmMappings(boolean useRealmMappings) { + this.useRealmMappings = useRealmMappings; + } } diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java index 96d2b72d43..cbbff891cb 100755 --- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java @@ -10,41 +10,12 @@ import java.util.Map; * @version $Revision: 1 $ */ public class UserRepresentation { - public static class Credential { - protected String type; - protected String value; - protected boolean hashed; - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - public boolean isHashed() { - return hashed; - } - - public void setHashed(boolean hashed) { - this.hashed = hashed; - } - } protected String self; // link protected String username; protected boolean enabled; protected Map attributes; - protected List credentials; + protected List credentials; public String getSelf() { return self; @@ -70,23 +41,23 @@ public class UserRepresentation { this.attributes = attributes; } - public List getCredentials() { - return credentials; - } - - public void setCredentials(List credentials) { - this.credentials = credentials; - } - public UserRepresentation attribute(String name, String value) { if (this.attributes == null) attributes = new HashMap(); attributes.put(name, value); return this; } + public List getCredentials() { + return credentials; + } + + public void setCredentials(List credentials) { + this.credentials = credentials; + } + public UserRepresentation credential(String type, String value, boolean hashed) { - if (this.credentials == null) credentials = new ArrayList(); - Credential cred = new Credential(); + if (this.credentials == null) credentials = new ArrayList(); + CredentialRepresentation cred = new CredentialRepresentation(); cred.setType(type); cred.setValue(value); cred.setHashed(hashed); diff --git a/core/src/main/java/org/keycloak/representations/idm/admin/AdminAction.java b/core/src/main/java/org/keycloak/representations/idm/admin/AdminAction.java new file mode 100755 index 0000000000..0a7f553acf --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/admin/AdminAction.java @@ -0,0 +1,66 @@ +package org.keycloak.representations.idm.admin; + +import org.codehaus.jackson.annotate.JsonIgnore; +import org.codehaus.jackson.annotate.JsonProperty; + +/** + * Posted to managed client from admin server. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class AdminAction { + protected String id; + protected long expiration; + protected String resource; + protected String action; + + public AdminAction() { + } + + public AdminAction(String id, long expiration, String resource, String action) { + this.id = id; + this.expiration = expiration; + this.resource = resource; + this.action = action; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + @JsonIgnore + public boolean isExpired() + { + long time = System.currentTimeMillis() / 1000; + return time > expiration; + } + + public long getExpiration() { + return expiration; + } + + public void setExpiration(long expiration) { + this.expiration = expiration; + } + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/admin/LogoutAction.java b/core/src/main/java/org/keycloak/representations/idm/admin/LogoutAction.java new file mode 100755 index 0000000000..946b7f85aa --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/admin/LogoutAction.java @@ -0,0 +1,26 @@ +package org.keycloak.representations.idm.admin; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class LogoutAction extends AdminAction { + public static final String LOGOUT_ACTION = "logout"; + protected String user; + + public LogoutAction() { + } + + public LogoutAction(String id, long expiration, String resource, String user) { + super(id, expiration, resource, LOGOUT_ACTION); + this.user = user; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } +} diff --git a/examples/as7-eap-demo/customer-app/src/main/webapp/WEB-INF/resteasy-oauth.json b/examples/as7-eap-demo/customer-app/src/main/webapp/WEB-INF/resteasy-oauth.json index e0df12d795..a36b5cf397 100755 --- a/examples/as7-eap-demo/customer-app/src/main/webapp/WEB-INF/resteasy-oauth.json +++ b/examples/as7-eap-demo/customer-app/src/main/webapp/WEB-INF/resteasy-oauth.json @@ -1,11 +1,11 @@ { "realm" : "demo", + "resource" : "customer-portal", "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/login", "code-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes", "ssl-not-required" : true, - "client-id" : "customer-portal", - "client-credentials" : { + "credentials" : { "password" : "password" } } diff --git a/examples/as7-eap-demo/customer-app/src/main/webapp/customers/view.jsp b/examples/as7-eap-demo/customer-app/src/main/webapp/customers/view.jsp old mode 100644 new mode 100755 index f6bd0c5277..91657c93da --- a/examples/as7-eap-demo/customer-app/src/main/webapp/customers/view.jsp +++ b/examples/as7-eap-demo/customer-app/src/main/webapp/customers/view.jsp @@ -1,11 +1,15 @@ -<%@ page language="java" contentType="text/html; charset=ISO-8859-1" +<%@ page import="javax.ws.rs.core.*" language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> Customer View Page -

Goto: products | logout

+<% + String logoutUri = UriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/tokens/logout") + .queryParam("redirect_uri", "http://localhost:8080/customer-portal").build().toString(); +%> +

Goto: products | logout

User <%=request.getUserPrincipal().getName()%> made this request.

Customer Listing

<% diff --git a/examples/as7-eap-demo/database-service/src/main/webapp/WEB-INF/resteasy-oauth.json b/examples/as7-eap-demo/database-service/src/main/webapp/WEB-INF/resteasy-oauth.json index 156706f4e4..cacacd0891 100755 --- a/examples/as7-eap-demo/database-service/src/main/webapp/WEB-INF/resteasy-oauth.json +++ b/examples/as7-eap-demo/database-service/src/main/webapp/WEB-INF/resteasy-oauth.json @@ -1,4 +1,5 @@ { "realm" : "demo", + "resource" : "database-service", "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB" } diff --git a/examples/as7-eap-demo/product-app/src/main/webapp/WEB-INF/resteasy-oauth.json b/examples/as7-eap-demo/product-app/src/main/webapp/WEB-INF/resteasy-oauth.json index 53ad29f278..26bc1fef92 100755 --- a/examples/as7-eap-demo/product-app/src/main/webapp/WEB-INF/resteasy-oauth.json +++ b/examples/as7-eap-demo/product-app/src/main/webapp/WEB-INF/resteasy-oauth.json @@ -1,11 +1,11 @@ { "realm" : "demo", + "resource" : "product-portal", "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", "auth-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/login", "code-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes", "ssl-not-required" : true, - "client-id" : "product-portal", - "client-credentials" : { + "credentials" : { "password" : "password" } } diff --git a/examples/as7-eap-demo/product-app/src/main/webapp/products/view.jsp b/examples/as7-eap-demo/product-app/src/main/webapp/products/view.jsp old mode 100644 new mode 100755 index 5a9a6410e6..fe8d990ba6 --- a/examples/as7-eap-demo/product-app/src/main/webapp/products/view.jsp +++ b/examples/as7-eap-demo/product-app/src/main/webapp/products/view.jsp @@ -1,11 +1,16 @@ -<%@ page language="java" contentType="text/html; charset=ISO-8859-1" +<%@ page import="javax.ws.rs.core.*" language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> Product View Page -

Goto: customers | logout

+<% + String logoutUri = UriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/tokens/logout") + .queryParam("redirect_uri", "http://localhost:8080/product-portal").build().toString(); +%> + +

Goto: customers | logout

User <%=request.getUserPrincipal().getName()%> made this request.

Product Listing

<% diff --git a/examples/as7-eap-demo/server/src/main/webapp/META-INF/testrealm.json b/examples/as7-eap-demo/server/src/main/webapp/META-INF/testrealm.json index 150c218e54..db107689e9 100755 --- a/examples/as7-eap-demo/server/src/main/webapp/META-INF/testrealm.json +++ b/examples/as7-eap-demo/server/src/main/webapp/META-INF/testrealm.json @@ -25,22 +25,6 @@ { "type" : "Password", "value" : "password" } ] - }, - { - "username" : "customer-portal", - "enabled" : true, - "credentials" : [ - { "type" : "Password", - "value" : "password" } - ] - }, - { - "username" : "product-portal", - "enabled" : true, - "credentials" : [ - { "type" : "Password", - "value" : "password" } - ] } ], "roleMappings" : [ @@ -49,14 +33,24 @@ "roles" : ["user"] } ], - "scopeMappings" : [ + "resources" : [ { - "username" : "customer-portal", - "roles" : ["*"] + "name" : "customer-portal", + "adminUrl" : "http://localhost:8080/customer-portal/j_admin_request", + "useRealmMappings" : true, + "credentials" : [ + { "type" : "Password", + "value" : "password" } + ] }, { - "username" : "product-portal", - "roles" : ["*"] + "name" : "product-portal", + "adminUrl" : "http://localhost:8080/product-portal/j_admin_request", + "useRealmMappings" : true, + "credentials" : [ + { "type" : "Password", + "value" : "password" } + ] } ] } \ No newline at end of file diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/BearerTokenAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/BearerTokenAuthenticatorValve.java index b87ed0bc85..6ed560c787 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/BearerTokenAuthenticatorValve.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/BearerTokenAuthenticatorValve.java @@ -63,7 +63,7 @@ public class BearerTokenAuthenticatorValve extends AuthenticatorBase implements @Override protected boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException { try { - CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(resourceMetadata, !remoteSkeletonKeyConfig.isCancelPropagation(), true); + CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(resourceMetadata, !remoteSkeletonKeyConfig.isCancelPropagation(), true, remoteSkeletonKeyConfig.isUseResourceRoleMappings()); if (bearer.login(request, response)) { return true; } diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaBearerTokenAuthenticator.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaBearerTokenAuthenticator.java index 835296b97e..b02cb45321 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaBearerTokenAuthenticator.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaBearerTokenAuthenticator.java @@ -15,6 +15,7 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.security.Principal; import java.security.cert.X509Certificate; +import java.util.HashSet; import java.util.Set; /** @@ -29,11 +30,13 @@ public class CatalinaBearerTokenAuthenticator { protected SkeletonKeyToken token; private Principal principal; protected boolean propagateToken; + protected boolean useResourceRoleMappings; - public CatalinaBearerTokenAuthenticator(ResourceMetadata resourceMetadata, boolean propagateToken, boolean challenge) { + public CatalinaBearerTokenAuthenticator(ResourceMetadata resourceMetadata, boolean propagateToken, boolean challenge, boolean useResourceRoleMappings) { this.resourceMetadata = resourceMetadata; this.challenge = challenge; this.propagateToken = propagateToken; + this.useResourceRoleMappings = useResourceRoleMappings; } public ResourceMetadata getResourceMetadata() { @@ -77,8 +80,8 @@ public class CatalinaBearerTokenAuthenticator { challengeResponse(response, "invalid_token", e.getMessage()); } boolean verifyCaller = false; - Set roles = null; - if (resourceMetadata.getResourceName() != null) { + Set roles = new HashSet(); + if (useResourceRoleMappings) { SkeletonKeyToken.Access access = token.getResourceAccess(resourceMetadata.getResourceName()); if (access != null) roles = access.getRoles(); verifyCaller = token.isVerifyCaller(resourceMetadata.getResourceName()); diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticationServerValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticationServerValve.java index 1b04c9adac..66621dfe3e 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticationServerValve.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthAuthenticationServerValve.java @@ -527,8 +527,8 @@ public class OAuthAuthenticationServerValve extends FormAuthenticator implements html.append("
"); writer = new StringWriter(); - rep.getClientCredentials().put("password", "REQUIRED"); - rep.setClientId("REQUIRED"); + rep.getCredentials().put("password", "REQUIRED"); + //rep.setClientId("REQUIRED"); rep.setTruststore("REQUIRED"); rep.setTruststorePassword("REQUIRED"); mapper.writeValue(writer, rep); @@ -561,7 +561,7 @@ public class OAuthAuthenticationServerValve extends FormAuthenticator implements public boolean bearer(Request request, HttpServletResponse response, boolean propagate) throws IOException { if (request.getHeader("Authorization") != null) { - CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(resourceMetadata, true, false); + CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(resourceMetadata, true, false, false); try { if (bearer.login(request, response)) { return true; diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthManagedResourceValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthManagedResourceValve.java index 6c1385cd2a..3614d580a0 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthManagedResourceValve.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/OAuthManagedResourceValve.java @@ -14,8 +14,9 @@ import org.apache.catalina.deploy.LoginConfig; import org.apache.catalina.realm.GenericPrincipal; import org.jboss.logging.Logger; import org.jboss.resteasy.client.jaxrs.ResteasyClient; -import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; -import org.jboss.resteasy.plugins.providers.RegisterBuiltin; +import org.jboss.resteasy.jose.jws.JWSInput; +import org.jboss.resteasy.jose.jws.crypto.RSAProvider; +import org.jboss.resteasy.jwt.JsonSerialization; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.RealmConfiguration; import org.keycloak.ResourceMetadata; @@ -24,12 +25,14 @@ import org.keycloak.SkeletonKeySession; import org.keycloak.adapters.as7.config.ManagedResourceConfig; import org.keycloak.adapters.as7.config.ManagedResourceConfigLoader; import org.keycloak.representations.SkeletonKeyToken; +import org.keycloak.representations.idm.admin.LogoutAction; import javax.security.auth.login.LoginException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.UriBuilder; import java.io.IOException; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -67,10 +70,6 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life managedResourceConfigLoader.init(true); resourceMetadata = managedResourceConfigLoader.getResourceMetadata(); remoteSkeletonKeyConfig = managedResourceConfigLoader.getRemoteSkeletonKeyConfig(); - String client_id = remoteSkeletonKeyConfig.getClientId(); - if (client_id == null) { - throw new IllegalArgumentException("Must set client-id to use with auth server"); - } realmConfiguration = new RealmConfiguration(); String authUrl = remoteSkeletonKeyConfig.getAuthUrl(); if (authUrl == null) { @@ -81,17 +80,16 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life throw new RuntimeException("You mut specify code-url"); } realmConfiguration.setMetadata(resourceMetadata); - realmConfiguration.setClientId(client_id); realmConfiguration.setSslRequired(!remoteSkeletonKeyConfig.isSslNotRequired()); - for (Map.Entry entry : managedResourceConfigLoader.getRemoteSkeletonKeyConfig().getClientCredentials().entrySet()) { - realmConfiguration.getCredentials().param(entry.getKey(), entry.getValue()); + for (Map.Entry entry : managedResourceConfigLoader.getRemoteSkeletonKeyConfig().getCredentials().entrySet()) { + realmConfiguration.getResourceCredentials().param(entry.getKey(), entry.getValue()); } ResteasyClient client = managedResourceConfigLoader.getClient(); realmConfiguration.setClient(client); - realmConfiguration.setAuthUrl(UriBuilder.fromUri(authUrl).queryParam("client_id", client_id)); + realmConfiguration.setAuthUrl(UriBuilder.fromUri(authUrl).queryParam("client_id", resourceMetadata.getResourceName())); realmConfiguration.setCodeUrl(client.target(tokenUrl)); } @@ -99,8 +97,8 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life public void invoke(Request request, Response response) throws IOException, ServletException { try { String requestURI = request.getDecodedRequestURI(); - if (requestURI.endsWith("j_oauth_remote_logout")) { - remoteLogout(request, response); + if (requestURI.endsWith("j_admin_request")) { + adminRequest(request, response); return; } super.invoke(request, response); @@ -135,33 +133,71 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life return false; } - protected void remoteLogout(Request request, HttpServletResponse response) throws IOException { + protected void adminRequest(Request request, HttpServletResponse response) throws IOException { + String token = request.getParameter("token"); + if (token == null) { + log.warn("admin request failed, no token"); + response.sendError(403, "no token"); + return; + } + + JWSInput input = new JWSInput(token); + boolean verified = false; + try { + verified = RSAProvider.verify(input, resourceMetadata.getRealmKey()); + } catch (Exception ignore) { + } + if (!verified) { + log.warn("admin request failed, unable to verify token"); + response.sendError(403, "verification failed"); + return; + } + String action = request.getParameter("action"); + if (LogoutAction.LOGOUT_ACTION.equals(action)) { + remoteLogout(input, response); + } else { + log.warn("admin request failed, unknown action"); + response.sendError(403, "Unknown action"); + } + } + + protected void remoteLogout(JWSInput token, HttpServletResponse response) throws IOException { try { log.debug("->> remoteLogout: "); - if (!bearer(true, request, response)) { - log.debug("remoteLogout: bearer auth failed"); + LogoutAction action = JsonSerialization.fromBytes(LogoutAction.class, token.getContent()); + if (action.isExpired()) { + log.warn("admin request failed, expired token"); + response.sendError(400, "Expired token"); return; } - GenericPrincipal gp = (GenericPrincipal) request.getPrincipal(); - if (!gp.hasRole(remoteSkeletonKeyConfig.getAdminRole())) { - log.debug("remoteLogout: role failure"); - response.sendError(403); + if (!LogoutAction.LOGOUT_ACTION.equals(action.getAction())) { + log.warn("Action doesn't match"); + response.sendError(400, "Action does not match"); return; } - String user = request.getParameter("user"); + if (!resourceMetadata.getResourceName().equals(action.getResource())) { + log.warn("Resource name does not match"); + response.sendError(400, "Resource name does not match"); + return; + + } + String user = action.getUser(); if (user != null) { + log.info("logout of session for: " + user); userSessionManagement.logout(user); } else { + log.info("logout of all sessions"); userSessionManagement.logoutAll(); } } catch (Exception e) { - log.error("failed to logout", e); + log.warn("failed to logout", e); + response.sendError(500, "Failed to logout"); } response.setStatus(204); } protected boolean bearer(boolean challenge, Request request, HttpServletResponse response) throws LoginException, IOException { - CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(realmConfiguration.getMetadata(), !remoteSkeletonKeyConfig.isCancelPropagation(), challenge); + CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(realmConfiguration.getMetadata(), !remoteSkeletonKeyConfig.isCancelPropagation(), challenge, remoteSkeletonKeyConfig.isUseResourceRoleMappings()); if (bearer.login(request, response)) { return true; } @@ -207,13 +243,13 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life if (!oauth.resolveCode(code)) return; SkeletonKeyToken token = oauth.getToken(); - Set roles = null; - if (resourceMetadata.getResourceName() != null) { + Set roles = new HashSet(); + if (remoteSkeletonKeyConfig.isUseResourceRoleMappings()) { SkeletonKeyToken.Access access = token.getResourceAccess(resourceMetadata.getResourceName()); - if (access != null) roles = access.getRoles(); + if (access != null) roles.addAll(access.getRoles()); } else { SkeletonKeyToken.Access access = token.getRealmAccess(); - if (access != null) roles = access.getRoles(); + if (access != null) roles.addAll(access.getRoles()); } SkeletonKeyPrincipal skp = new SkeletonKeyPrincipal(token.getPrincipal(), null); GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(context.getRealm(), skp, roles); diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java index 8e177a8c22..b3cdebbf4a 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java @@ -1,7 +1,6 @@ package org.keycloak.adapters.as7; import org.jboss.logging.Logger; -import org.jboss.resteasy.util.BasicAuthHelper; import org.keycloak.RSATokenVerifier; import org.keycloak.RealmConfiguration; import org.keycloak.VerificationException; @@ -14,7 +13,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.client.Entity; import javax.ws.rs.core.Form; -import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import java.io.IOException; @@ -144,7 +142,7 @@ public class ServletOAuthLogin { url = secureUrl.build().toString(); } return realmInfo.getAuthUrl().clone() - .queryParam("client_id", realmInfo.getClientId()) + .queryParam("client_id", realmInfo.getMetadata().getResourceName()) .queryParam("redirect_uri", url) .queryParam("state", state) .queryParam("login", "true") @@ -223,8 +221,8 @@ public class ServletOAuthLogin { if (!checkStateCookie()) return false; - String client_id = realmInfo.getClientId(); - String password = realmInfo.getCredentials().asMap().getFirst("password"); + String client_id = realmInfo.getMetadata().getResourceName(); + String password = realmInfo.getResourceCredentials().asMap().getFirst("password"); //String authHeader = BasicAuthHelper.createHeader(client_id, password); String redirectUri = stripOauthParametersFromRedirect(); Form form = new Form(); diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfig.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfig.java index 756950c210..00da36907b 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfig.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfig.java @@ -26,6 +26,8 @@ public class ManagedResourceConfig { protected String authUrl; @JsonProperty("code-url") protected String codeUrl; + @JsonProperty("use-resource-role-mappings") + protected boolean useResourceRoleMappings; @JsonProperty("ssl-not-required") protected boolean sslNotRequired; @@ -37,21 +39,27 @@ public class ManagedResourceConfig { protected String truststore; @JsonProperty("truststore-password") protected String truststorePassword; - @JsonProperty("client-id") - protected String clientId; @JsonProperty("client-keystore") protected String clientKeystore; @JsonProperty("client-keystore-password") protected String clientKeystorePassword; @JsonProperty("client-key-password") protected String clientKeyPassword; - @JsonProperty("client-credentials") - protected Map clientCredentials = new HashMap(); + @JsonProperty("credentials") + protected Map credentials = new HashMap(); @JsonProperty("connection-pool-size") protected int connectionPoolSize; @JsonProperty("cancel-propagation") protected boolean cancelPropagation; + public boolean isUseResourceRoleMappings() { + return useResourceRoleMappings; + } + + public void setUseResourceRoleMappings(boolean useResourceRoleMappings) { + this.useResourceRoleMappings = useResourceRoleMappings; + } + public boolean isSslNotRequired() { return sslNotRequired; } @@ -140,16 +148,8 @@ public class ManagedResourceConfig { this.truststorePassword = truststorePassword; } - public String getClientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - - public Map getClientCredentials() { - return clientCredentials; + public Map getCredentials() { + return credentials; } public String getClientKeystore() { diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfigLoader.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfigLoader.java index e55c3c71b4..cc6a2d6fc8 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfigLoader.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/ManagedResourceConfigLoader.java @@ -103,8 +103,9 @@ public class ManagedResourceConfigLoader { } String realm = remoteSkeletonKeyConfig.getRealm(); - String resource = remoteSkeletonKeyConfig.getResource(); if (realm == null) throw new RuntimeException("Must set 'realm' in config"); + String resource = remoteSkeletonKeyConfig.getResource(); + if (resource == null) throw new RuntimeException("Must set 'resource' in config"); String realmKeyPem = remoteSkeletonKeyConfig.getRealmKey(); if (realmKeyPem == null) { diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index fc627957dd..00c8bd22e7 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -9,6 +9,7 @@ import org.keycloak.representations.SkeletonKeyToken; import org.keycloak.representations.idm.RequiredCredentialRepresentation; import org.keycloak.services.models.RealmModel; import org.keycloak.services.models.RequiredCredentialModel; +import org.keycloak.services.resources.RealmsResource; import org.picketlink.idm.credential.Credentials; import org.picketlink.idm.credential.Password; import org.picketlink.idm.credential.TOTPCredentials; @@ -20,6 +21,8 @@ import javax.ws.rs.core.Cookie; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.NewCookie; +import javax.ws.rs.core.UriInfo; +import java.net.URI; import java.util.HashSet; import java.util.Set; @@ -45,14 +48,19 @@ public class AuthenticationManager { return realm.isRealmAdmin(user); } - protected void expireIdentityCookie(Cookie cookie) { + public void expireIdentityCookie(RealmModel realm, UriInfo uriInfo) { + URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getId()); HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class); - if (response == null) return; - NewCookie expireIt = new NewCookie(cookie.getName(), "", cookie.getPath(), null, "Expiring cookie", 0, false); + if (response == null) { + logger.info("can't expire identity cookie, no HttpResponse"); + return; + } + logger.info("Expiring identity cookie"); + NewCookie expireIt = new NewCookie(TokenManager.KEYCLOAK_IDENTITY_COOKIE, "", uri.getPath(), null, "Expiring cookie", 0, false); response.addNewCookie(expireIt); } - public User authenticateIdentityCookie(RealmModel realm, HttpHeaders headers) { + public User authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) { Cookie cookie = headers.getCookies().get(TokenManager.KEYCLOAK_IDENTITY_COOKIE); if (cookie == null) return null; @@ -61,19 +69,19 @@ public class AuthenticationManager { SkeletonKeyToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getId()); if (!token.isActive()) { logger.info("identity cookie expired"); - expireIdentityCookie(cookie); + expireIdentityCookie(realm, uriInfo); return null; } User user = realm.getIdm().getUser(token.getPrincipal()); if (user == null || !user.isEnabled()) { logger.info("Unknown user in identity cookie"); - expireIdentityCookie(cookie); + expireIdentityCookie(realm, uriInfo); return null; } return user; } catch (VerificationException e) { logger.info("Failed to verify identity cookie", e); - expireIdentityCookie(cookie); + expireIdentityCookie(realm, uriInfo); } return null; } diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index 18a10e3961..080e68858e 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -1,17 +1,16 @@ package org.keycloak.services.managers; +import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RequiredCredentialRepresentation; import org.keycloak.representations.idm.ResourceRepresentation; import org.keycloak.representations.idm.RoleMappingRepresentation; import org.keycloak.representations.idm.ScopeMappingRepresentation; import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.models.RealmModel; import org.keycloak.services.models.RequiredCredentialModel; import org.keycloak.services.models.ResourceModel; import org.keycloak.services.models.UserCredentialModel; -import org.keycloak.services.resources.RegistrationService; import org.picketlink.idm.IdentityManager; import org.picketlink.idm.IdentitySession; import org.picketlink.idm.model.Attribute; @@ -22,7 +21,6 @@ import org.picketlink.idm.model.SimpleRole; import org.picketlink.idm.model.SimpleUser; import org.picketlink.idm.model.User; -import javax.ws.rs.NotAuthorizedException; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import java.security.KeyPair; @@ -73,6 +71,7 @@ public class RealmManager { SimpleAgent agent = new SimpleAgent(RealmModel.REALM_AGENT_ID); idm.add(agent); RealmModel realm = new RealmModel(newRealm, identitySession); + idm.add(new SimpleRole("*")); return realm; } @@ -135,7 +134,7 @@ public class RealmManager { } newRealm.getIdm().add(user); if (userRep.getCredentials() != null) { - for (UserRepresentation.Credential cred : userRep.getCredentials()) { + for (CredentialRepresentation cred : userRep.getCredentials()) { UserCredentialModel credential = new UserCredentialModel(); credential.setType(cred.getType()); credential.setValue(cred.getValue()); @@ -145,25 +144,25 @@ public class RealmManager { userMap.put(user.getLoginName(), user); } - Map roles = new HashMap(); - if (rep.getRoles() != null) { for (String roleString : rep.getRoles()) { SimpleRole role = new SimpleRole(roleString.trim()); newRealm.getIdm().add(role); - roles.put(role.getName(), role); } } + if (rep.getResources() != null) { + createResources(rep, newRealm, userMap); + } + if (rep.getRoleMappings() != null) { for (RoleMappingRepresentation mapping : rep.getRoleMappings()) { User user = userMap.get(mapping.getUsername()); for (String roleString : mapping.getRoles()) { - Role role = roles.get(roleString.trim()); + Role role = newRealm.getIdm().getRole(roleString.trim()); if (role == null) { role = new SimpleRole(roleString.trim()); newRealm.getIdm().add(role); - roles.put(role.getName(), role); } newRealm.getIdm().grantRole(user, role); } @@ -173,11 +172,10 @@ public class RealmManager { if (rep.getScopeMappings() != null) { for (ScopeMappingRepresentation scope : rep.getScopeMappings()) { for (String roleString : scope.getRoles()) { - Role role = roles.get(roleString.trim()); + Role role = newRealm.getIdm().getRole(roleString.trim()); if (role == null) { role = new SimpleRole(roleString.trim()); newRealm.getIdm().add(role); - roles.put(role.getName(), role); } User user = userMap.get(scope.getUsername()); newRealm.addScope(user, role.getName()); @@ -185,43 +183,43 @@ public class RealmManager { } } - - if (!roles.containsKey("*")) { - SimpleRole wildcard = new SimpleRole("*"); - newRealm.getIdm().add(wildcard); - roles.put("*", wildcard); - } - - if (rep.getResources() != null) { - createResources(rep, newRealm, userMap); - } } protected void createResources(RealmRepresentation rep, RealmModel realm, Map userMap) { for (ResourceRepresentation resourceRep : rep.getResources()) { ResourceModel resource = realm.addResource(resourceRep.getName()); + resource.setManagementUrl(resourceRep.getAdminUrl()); resource.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired()); resource.updateResource(); - Map roles = new HashMap(); + + User resourceUser = resource.getResourceUser(); + if (resourceRep.getCredentials() != null) { + for (CredentialRepresentation cred : resourceRep.getCredentials()) { + UserCredentialModel credential = new UserCredentialModel(); + credential.setType(cred.getType()); + credential.setValue(cred.getValue()); + realm.updateCredential(resourceUser, credential); + } + } + userMap.put(resourceUser.getLoginName(), resourceUser); + + if (resourceRep.getRoles() != null) { for (String roleString : resourceRep.getRoles()) { SimpleRole role = new SimpleRole(roleString.trim()); resource.getIdm().add(role); - roles.put(role.getName(), role); } } if (resourceRep.getRoleMappings() != null) { for (RoleMappingRepresentation mapping : resourceRep.getRoleMappings()) { User user = userMap.get(mapping.getUsername()); for (String roleString : mapping.getRoles()) { - Role role = roles.get(roleString.trim()); + Role role = resource.getIdm().getRole(roleString.trim()); if (role == null) { role = new SimpleRole(roleString.trim()); resource.getIdm().add(role); - roles.put(role.getName(), role); } - Role role1 = resource.getIdm().getRole(role.getName()); - realm.getIdm().grantRole(user, role1); + realm.getIdm().grantRole(user, role); } } } @@ -229,22 +227,16 @@ public class RealmManager { for (ScopeMappingRepresentation mapping : resourceRep.getScopeMappings()) { User user = userMap.get(mapping.getUsername()); for (String roleString : mapping.getRoles()) { - Role role = roles.get(roleString.trim()); + Role role = resource.getIdm().getRole(roleString.trim()); if (role == null) { role = new SimpleRole(roleString.trim()); resource.getIdm().add(role); - roles.put(role.getName(), role); } resource.addScope(user, role.getName()); } } } - if (!roles.containsKey("*")) { - SimpleRole wildcard = new SimpleRole("*"); - resource.getIdm().add(wildcard); - roles.put("*", wildcard); - } - + if (resourceRep.isUseRealmMappings()) realm.addScope(resource.getResourceUser(), "*"); } } @@ -266,7 +258,7 @@ public class RealmManager { boolean hasBrowserCredentials = true; for (RequiredCredentialRepresentation credential : rep.getRequiredCredentials()) { boolean hasCredential = false; - for (UserRepresentation.Credential cred : userRep.getCredentials()) { + for (CredentialRepresentation cred : userRep.getCredentials()) { if (cred.getType().equals(credential.getType())) { hasCredential = true; break; diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java new file mode 100755 index 0000000000..fd2fe2ac80 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java @@ -0,0 +1,47 @@ +package org.keycloak.services.managers; + +import org.jboss.resteasy.client.jaxrs.ResteasyClient; +import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; +import org.keycloak.TokenIdGenerator; +import org.keycloak.representations.idm.admin.LogoutAction; +import org.keycloak.services.models.RealmModel; +import org.keycloak.services.models.ResourceModel; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Form; +import javax.ws.rs.core.Response; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ResourceAdminManager { + + public void logoutAll(RealmModel realm) { + singleLogOut(realm, null); + } + + public void singleLogOut(RealmModel realm, String user) { + ResteasyClient client = new ResteasyClientBuilder() + .disableTrustManager() // todo fix this, should have a trust manager or a good default + .build(); + + List resources = realm.getResources(); + for (ResourceModel resource : resources) { + logoutResource(realm, resource, user, client); + } + } + + protected boolean logoutResource(RealmModel realm, ResourceModel resource, String user, ResteasyClient client) { + LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), System.currentTimeMillis() / 1000 + 30, resource.getName(), user); + String token = new TokenManager().encodeToken(realm, adminAction); + Form form = new Form(); + form.param("token", token); + Response response = client.target(resource.getManagementUrl()).queryParam("action", "logout").request().post(Entity.form(form)); + boolean success = response.getStatus() == 204; + response.close(); + return success; + } + +} diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java index 6e7f2879a3..68ef55007b 100755 --- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java +++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java @@ -3,15 +3,17 @@ package org.keycloak.services.managers; import org.jboss.resteasy.jose.Base64Url; import org.jboss.resteasy.jose.jws.JWSBuilder; import org.jboss.resteasy.jwt.JsonSerialization; +import org.jboss.resteasy.spi.HttpResponse; +import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.representations.SkeletonKeyScope; import org.keycloak.representations.SkeletonKeyToken; import org.keycloak.services.models.RealmModel; import org.keycloak.services.models.ResourceModel; import org.keycloak.services.resources.RealmsResource; -import org.keycloak.services.resources.TokenService; import org.picketlink.idm.model.User; import javax.ws.rs.ForbiddenException; +import javax.ws.rs.core.Cookie; import javax.ws.rs.core.NewCookie; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -38,6 +40,7 @@ public class TokenManager { accessCodeMap.clear(); } + public AccessCodeEntry pullAccessCode(String key) { return accessCodeMap.remove(key); } @@ -55,7 +58,7 @@ public class TokenManager { { SkeletonKeyToken token = null; if (scopeParam != null) token = createScopedToken(scopeParam, realm, client, user); - else token = createLoginToken(realm, client, user); + else token = createUnscopedToken(realm, client, user); AccessCodeEntry code = new AccessCodeEntry(); code.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan()); @@ -72,15 +75,7 @@ public class TokenManager { } public SkeletonKeyToken createScopedToken(SkeletonKeyScope scope, RealmModel realm, User client, User user) { - SkeletonKeyToken token = new SkeletonKeyToken(); - token.id(RealmManager.generateId()); - token.principal(user.getLoginName()); - token.audience(realm.getName()); - token.issuedNow(); - token.issuedFor(client.getLoginName()); - if (realm.getTokenLifespan() > 0) { - token.expiration((System.currentTimeMillis() / 1000) + realm.getTokenLifespan()); - } + SkeletonKeyToken token = initToken(realm, client, user); Map resourceMap = realm.getResourceMap(); for (String res : scope.keySet()) { @@ -102,23 +97,53 @@ public class TokenManager { return token; } + protected SkeletonKeyToken initToken(RealmModel realm, User client, User user) { + SkeletonKeyToken token = new SkeletonKeyToken(); + token.id(RealmManager.generateId()); + token.principal(user.getLoginName()); + token.audience(realm.getName()); + token.issuedNow(); + token.issuedFor(client.getLoginName()); + if (realm.getTokenLifespan() > 0) { + token.expiration((System.currentTimeMillis() / 1000) + realm.getTokenLifespan()); + } + return token; + } + public SkeletonKeyToken createScopedToken(String scopeParam, RealmModel realm, User client, User user) { SkeletonKeyScope scope = decodeScope(scopeParam); return createScopedToken(scope, realm, client, user); } - public SkeletonKeyToken createLoginToken(RealmModel realm, User client, User user) { - Set mapping = realm.getScopes(client); - if (!mapping.contains("*")) { - throw new ForbiddenException(Response.status(403).entity("

Security Alert

Known client not authorized to request a user login.

").type("text/html").build()); + public SkeletonKeyToken createUnscopedToken(RealmModel realm, User client, User user) { + + SkeletonKeyToken token = initToken(realm, client, user); + + Set realmMapping = realm.getRoleMappings(user); + + if (realmMapping != null && realmMapping.size() > 0) { + Set scope = realm.getScope(client); + SkeletonKeyToken.Access access = new SkeletonKeyToken.Access(); + for (String role : realmMapping) { + if (scope.contains("*") || scope.contains(role)) access.addRole(role); + } + token.setRealmAccess(access); + } + List resources = realm.getResources(); + for (ResourceModel resource : resources) { + Set scope = resource.getScope(client); + Set mapping = resource.getRoleMappings(user); + if (mapping.size() == 0 || scope.size() == 0) continue; + SkeletonKeyToken.Access access = token.addAccess(resource.getName()) + .verifyCaller(resource.isSurrogateAuthRequired()); + for (String role : mapping) { + if (scope.contains("*") || scope.contains(role)) access.addRole(role); + } } - SkeletonKeyToken token = createAccessToken(realm, user); - token.issuedFor(client.getLoginName()); return token; } - public String encodeScope(SkeletonKeyScope scope) { String token = null; try { @@ -187,7 +212,7 @@ public class TokenManager { return token; } - public String encodeToken(RealmModel realm, SkeletonKeyToken token) { + public String encodeToken(RealmModel realm, Object token) { byte[] tokenBytes = null; try { tokenBytes = JsonSerialization.toByteArray(token, false); diff --git a/services/src/main/java/org/keycloak/services/models/RealmModel.java b/services/src/main/java/org/keycloak/services/models/RealmModel.java index 76f5d4d3e7..6ab850ae83 100755 --- a/services/src/main/java/org/keycloak/services/models/RealmModel.java +++ b/services/src/main/java/org/keycloak/services/models/RealmModel.java @@ -18,6 +18,8 @@ import org.picketlink.idm.model.Attribute; import org.picketlink.idm.model.Grant; import org.picketlink.idm.model.Realm; import org.picketlink.idm.model.Role; +import org.picketlink.idm.model.SimpleRole; +import org.picketlink.idm.model.SimpleUser; import org.picketlink.idm.model.Tier; import org.picketlink.idm.model.User; import org.picketlink.idm.query.IdentityQuery; @@ -293,8 +295,15 @@ public class RealmModel { relationship.setResourceName(name); relationship.setRealmAgent(realmAgent); relationship.setResourceId(newTier.getId()); + relationship.setManagementUrl(""); // Picketlink doesn't like null attribute values + User resourceUser = new SimpleUser(name); + idm.add(resourceUser); + relationship.setResourceUser(resourceUser); idm.add(relationship); - return new ResourceModel(newTier, relationship, this, identitySession); + ResourceModel resource = new ResourceModel(newTier, relationship, this, identitySession); + resource.getIdm().add(new SimpleRole("*")); + resource.addScope(resourceUser, "*"); + return resource; } public Set getRoleMappings(User user) { @@ -320,7 +329,7 @@ public class RealmModel { } - public Set getScopes(Agent agent) { + public Set getScope(Agent agent) { RelationshipQuery query = getIdm().createRelationshipQuery(ScopeRelationship.class); query.setParameter(ScopeRelationship.CLIENT, agent); List scope = query.getResultList(); diff --git a/services/src/main/java/org/keycloak/services/models/ResourceModel.java b/services/src/main/java/org/keycloak/services/models/ResourceModel.java index bad532c657..c47785bbbe 100755 --- a/services/src/main/java/org/keycloak/services/models/ResourceModel.java +++ b/services/src/main/java/org/keycloak/services/models/ResourceModel.java @@ -41,6 +41,10 @@ public class ResourceModel { getIdm().update(agent); } + public User getResourceUser() { + return agent.getResourceUser(); + } + public String getId() { return tier.getId(); } @@ -69,6 +73,14 @@ public class ResourceModel { agent.setSurrogateAuthRequired(surrogateAuthRequired); } + public String getManagementUrl() { + return agent.getManagementUrl(); + } + + public void setManagementUrl(String url) { + agent.setManagementUrl(url); + } + public List getRoles() { IdentityQuery query = getIdm().createIdentityQuery(Role.class); query.setParameter(Role.PARTITION, tier); @@ -90,10 +102,14 @@ public class ResourceModel { IdentityManager idm = getIdm(); Role role = idm.getRole(roleName); if (role == null) throw new RuntimeException("role not found"); + addScope(agent, role); + + } + + public void addScope(Agent agent, Role role) { ScopeRelationship scope = new ScopeRelationship(); scope.setClient(agent); scope.setScope(role); - } public Set getScope(Agent agent) { diff --git a/services/src/main/java/org/keycloak/services/models/relationships/ResourceRelationship.java b/services/src/main/java/org/keycloak/services/models/relationships/ResourceRelationship.java index 10d10305f0..f63e76a887 100755 --- a/services/src/main/java/org/keycloak/services/models/relationships/ResourceRelationship.java +++ b/services/src/main/java/org/keycloak/services/models/relationships/ResourceRelationship.java @@ -3,6 +3,7 @@ package org.keycloak.services.models.relationships; import org.picketlink.idm.model.AbstractAttributedType; import org.picketlink.idm.model.Agent; import org.picketlink.idm.model.Relationship; +import org.picketlink.idm.model.User; import org.picketlink.idm.model.annotation.AttributeProperty; import org.picketlink.idm.model.annotation.IdentityProperty; import org.picketlink.idm.query.RelationshipQueryParameter; @@ -23,8 +24,10 @@ public class ResourceRelationship extends AbstractAttributedType implements Rela }; protected Agent realmAgent; + protected User resourceUser; protected String resourceId; protected String resourceName; + protected String managementUrl = ""; // Picketlink doesn't like null attribute values protected boolean surrogateAuthRequired; protected boolean enabled; @@ -37,6 +40,15 @@ public class ResourceRelationship extends AbstractAttributedType implements Rela this.realmAgent = realmAgent; } + @IdentityProperty + public User getResourceUser() { + return resourceUser; + } + + public void setResourceUser(User resourceUser) { + this.resourceUser = resourceUser; + } + @AttributeProperty public String getResourceId() { return resourceId; @@ -72,4 +84,15 @@ public class ResourceRelationship extends AbstractAttributedType implements Rela public void setEnabled(boolean enabled) { this.enabled = enabled; } + + @AttributeProperty + public String getManagementUrl() + { + return managementUrl; + } + + public void setManagementUrl(String managementUrl) { + if (managementUrl == null) managementUrl = ""; // Picketlink doesn't like NULL attribute values. + this.managementUrl = managementUrl; + } } diff --git a/services/src/main/java/org/keycloak/services/resources/RealmSubResource.java b/services/src/main/java/org/keycloak/services/resources/RealmSubResource.java index 7d2ab1f0b0..4221f12cd9 100755 --- a/services/src/main/java/org/keycloak/services/resources/RealmSubResource.java +++ b/services/src/main/java/org/keycloak/services/resources/RealmSubResource.java @@ -52,9 +52,9 @@ public class RealmSubResource { StringBuffer html = new StringBuffer(); String authUri = TokenService.loginPageUrl(uriInfo).build(realm.getId()).toString(); - String codeUri = TokenService.accessCodeRequest(uriInfo).build(realm.getId()).toString(); - String grantUrl = TokenService.grantRequest(uriInfo).build(realm.getId()).toString(); - String idGrantUrl = TokenService.identityGrantRequest(uriInfo).build(realm.getId()).toString(); + String codeUri = TokenService.accessCodeToTokenUrl(uriInfo).build(realm.getId()).toString(); + String grantUrl = TokenService.grantAccessTokenUrl(uriInfo).build(realm.getId()).toString(); + String idGrantUrl = TokenService.grantIdentityTokenUrl(uriInfo).build(realm.getId()).toString(); html.append("

Realm: ").append(realm.getName()).append("

"); html.append("

auth: ").append(authUri).append("

"); @@ -76,9 +76,9 @@ public class RealmSubResource { rep.setAdminRole(ADMIN_ROLE); rep.setAuthorizationUrl(TokenService.loginPageUrl(uriInfo).build(realm.getId()).toString()); - rep.setCodeUrl(TokenService.accessCodeRequest(uriInfo).build(realm.getId()).toString()); - rep.setGrantUrl(TokenService.grantRequest(uriInfo).build(realm.getId()).toString()); - String idGrantUrl = TokenService.identityGrantRequest(uriInfo).build(realm.getId()).toString(); + rep.setCodeUrl(TokenService.accessCodeToTokenUrl(uriInfo).build(realm.getId()).toString()); + rep.setGrantUrl(TokenService.grantAccessTokenUrl(uriInfo).build(realm.getId()).toString()); + String idGrantUrl = TokenService.grantIdentityTokenUrl(uriInfo).build(realm.getId()).toString(); rep.setIdentityGrantUrl(idGrantUrl); return rep; } diff --git a/services/src/main/java/org/keycloak/services/resources/RegistrationService.java b/services/src/main/java/org/keycloak/services/resources/RegistrationService.java index 2dfda68860..7bd38fe9e4 100755 --- a/services/src/main/java/org/keycloak/services/resources/RegistrationService.java +++ b/services/src/main/java/org/keycloak/services/resources/RegistrationService.java @@ -1,6 +1,7 @@ package org.keycloak.services.resources; import org.jboss.resteasy.logging.Logger; +import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.models.RealmModel; @@ -12,10 +13,8 @@ import org.picketlink.idm.model.User; import javax.ws.rs.Consumes; import javax.ws.rs.ForbiddenException; -import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; -import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -57,7 +56,7 @@ public class RegistrationService { user = new SimpleUser(newUser.getUsername()); defaultRealm.getIdm().add(user); - for (UserRepresentation.Credential cred : newUser.getCredentials()) { + for (CredentialRepresentation cred : newUser.getCredentials()) { UserCredentialModel credModel = new UserCredentialModel(); credModel.setType(cred.getType()); credModel.setValue(cred.getValue()); diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java index 0f10ec833c..eaaaf74801 100755 --- a/services/src/main/java/org/keycloak/services/resources/TokenService.java +++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java @@ -13,6 +13,7 @@ import org.keycloak.representations.SkeletonKeyToken; import org.keycloak.services.JspRequestParameters; import org.keycloak.services.managers.AccessCodeEntry; import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.TokenManager; import org.keycloak.services.models.RealmModel; import org.keycloak.services.models.RequiredCredentialModel; @@ -31,7 +32,6 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.NewCookie; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriBuilder; @@ -51,7 +51,6 @@ public class TokenService { protected static final Logger logger = Logger.getLogger(TokenService.class); - //protected Map accessCodeMap; @Context protected UriInfo uriInfo; @@ -73,39 +72,40 @@ public class TokenService { protected RealmModel realm; protected TokenManager tokenManager; protected AuthenticationManager authManager = new AuthenticationManager(); + private ResourceAdminManager resourceAdminManager = new ResourceAdminManager(); public TokenService(RealmModel realm, TokenManager tokenManager) { this.realm = realm; this.tokenManager = tokenManager; } - public static UriBuilder tokenServiceBase(UriInfo uriInfo) { + public static UriBuilder tokenServiceBaseUrl(UriInfo uriInfo) { UriBuilder base = uriInfo.getBaseUriBuilder() .path(RealmsResource.class).path(RealmsResource.class, "getTokenService"); return base; } - public static UriBuilder accessCodeRequest(UriInfo uriInfo) { - return tokenServiceBase(uriInfo).path(TokenService.class, "accessRequest"); + public static UriBuilder accessCodeToTokenUrl(UriInfo uriInfo) { + return tokenServiceBaseUrl(uriInfo).path(TokenService.class, "accessCodeToToken"); } - public static UriBuilder grantRequest(UriInfo uriInfo) { - return tokenServiceBase(uriInfo).path(TokenService.class, "accessTokenGrant"); + public static UriBuilder grantAccessTokenUrl(UriInfo uriInfo) { + return tokenServiceBaseUrl(uriInfo).path(TokenService.class, "grantAccessToken"); } - public static UriBuilder identityGrantRequest(UriInfo uriInfo) { - return tokenServiceBase(uriInfo).path(TokenService.class, "accessTokenGrant"); + public static UriBuilder grantIdentityTokenUrl(UriInfo uriInfo) { + return tokenServiceBaseUrl(uriInfo).path(TokenService.class, "grantIdentityToken"); } public static UriBuilder loginPageUrl(UriInfo uriInfo) { - return tokenServiceBase(uriInfo).path(TokenService.class, "loginRequest"); + return tokenServiceBaseUrl(uriInfo).path(TokenService.class, "loginPage"); } - public static UriBuilder logainActionUrl(UriInfo uriInfo) { - return tokenServiceBase(uriInfo).path(TokenService.class, "login"); + public static UriBuilder processLoginUrl(UriInfo uriInfo) { + return tokenServiceBaseUrl(uriInfo).path(TokenService.class, "processLogin"); } @@ -113,7 +113,7 @@ public class TokenService { @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.APPLICATION_JSON) - public Response identityTokenGrant(MultivaluedMap form) { + public Response grantIdentityToken(MultivaluedMap form) { String username = form.getFirst(AuthenticationManager.FORM_USERNAME); if (username == null) { throw new NotAuthorizedException("No user"); @@ -142,7 +142,7 @@ public class TokenService { @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.APPLICATION_JSON) - public Response accessTokenGrant(MultivaluedMap form) { + public Response grantAccessToken(MultivaluedMap form) { String username = form.getFirst(AuthenticationManager.FORM_USERNAME); if (username == null) { throw new NotAuthorizedException("No user"); @@ -169,7 +169,7 @@ public class TokenService { @Path("auth/request/login") @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response login(MultivaluedMap formData) { + public Response processLogin(MultivaluedMap formData) { String clientId = formData.getFirst("client_id"); String scopeParam = formData.getFirst("scope"); String state = formData.getFirst("state"); @@ -226,7 +226,7 @@ public class TokenService { @Path("access/codes") @POST @Produces("application/json") - public Response accessRequest(MultivaluedMap formData) { + public Response accessCodeToToken(MultivaluedMap formData) { logger.info("accessRequest <---"); if (!realm.isEnabled()) { throw new NotAuthorizedException("Realm not enabled"); @@ -356,7 +356,7 @@ public class TokenService { String scopeParam, String state) { request.setAttribute(RealmModel.class.getName(), realm); - request.setAttribute("KEYCLOAK_LOGIN_ACTION", logainActionUrl(uriInfo).build(realm.getId())); + request.setAttribute("KEYCLOAK_LOGIN_ACTION", processLoginUrl(uriInfo).build(realm.getId())); // RESTEASY eats the form data, so we send via an attribute request.setAttribute("redirect_uri", redirect); @@ -369,11 +369,11 @@ public class TokenService { @Path("login") @GET - public Response loginRequest(@QueryParam("response_type") String responseType, - @QueryParam("redirect_uri") String redirect, - @QueryParam("client_id") String clientId, - @QueryParam("scope") String scopeParam, - @QueryParam("state") String state) { + public Response loginPage(@QueryParam("response_type") String responseType, + @QueryParam("redirect_uri") String redirect, + @QueryParam("client_id") String clientId, + @QueryParam("scope") String scopeParam, + @QueryParam("state") String state) { if (!realm.isEnabled()) { securityFailureForward("Realm not enabled"); return null; @@ -390,7 +390,7 @@ public class TokenService { return null; } - User user = authManager.authenticateIdentityCookie(realm, headers); + User user = authManager.authenticateIdentityCookie(realm, uriInfo, headers); if (user != null) { return redirectAccessCode(scopeParam, state, redirect, client, user); } @@ -400,6 +400,21 @@ public class TokenService { return null; } + @Path("logout") + @GET + public Response logout(@QueryParam("redirect_uri") String redirectUri) { + // todo do we care if anybody can trigger this? + + User user = authManager.authenticateIdentityCookie(realm, uriInfo, headers); + if (user != null) { + logger.info("Logging out: " + user.getLoginName()); + authManager.expireIdentityCookie(realm, uriInfo); + resourceAdminManager.singleLogOut(realm, user.getLoginName()); + } + // todo manage legal redirects + return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build(); + } + private Response loginForm(String validationError, String redirect, String clientId, String scopeParam, String state, RealmModel realm, User client) { StringBuffer html = new StringBuffer(); if (scopeParam != null) { @@ -431,7 +446,7 @@ public class TokenService { } html.append("

To Authorize, please login below

"); } else { - Set scopeMapping = realm.getScopes(client); + Set scopeMapping = realm.getScope(client); if (scopeMapping.contains("*")) { html.append("

Login For ").append(realm.getName()).append(" Realm

"); if (validationError != null) { @@ -485,7 +500,7 @@ public class TokenService { } } - UriBuilder formActionUri = logainActionUrl(uriInfo); + UriBuilder formActionUri = processLoginUrl(uriInfo); String action = formActionUri.build(realm.getId()).toString(); html.append("
"); html.append("Username:
"); diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java index 78622cca5a..f798539547 100755 --- a/services/src/test/java/org/keycloak/test/AdapterTest.java +++ b/services/src/test/java/org/keycloak/test/AdapterTest.java @@ -166,7 +166,7 @@ public class AdapterTest { idm.add(new SimpleRole("admin")); idm.add(new SimpleRole("user")); List roles = realmModel.getRoles(); - Assert.assertEquals(2, roles.size()); + Assert.assertEquals(3, roles.size()); SimpleUser user = new SimpleUser("bburke"); idm.add(user); Role role = idm.getRole("user"); diff --git a/services/src/test/java/org/keycloak/test/ImportTest.java b/services/src/test/java/org/keycloak/test/ImportTest.java index 335169a161..c785e21108 100755 --- a/services/src/test/java/org/keycloak/test/ImportTest.java +++ b/services/src/test/java/org/keycloak/test/ImportTest.java @@ -104,7 +104,7 @@ public class ImportTest { User user = realm.getIdm().getUser("loginclient"); Assert.assertNotNull(user); - Set scopes = realm.getScopes(user); + Set scopes = realm.getScope(user); System.out.println("Scopes size: " + scopes.size()); Assert.assertTrue(scopes.contains("*"));