keycloak-uncached

Merge pull request #579 from patriot1burke/master remote

8/1/2014 2:34:11 PM

Details

diff --git a/audit/api/src/main/java/org/keycloak/audit/Details.java b/audit/api/src/main/java/org/keycloak/audit/Details.java
old mode 100644
new mode 100755
index 1a8df0a..fdb3448
--- a/audit/api/src/main/java/org/keycloak/audit/Details.java
+++ b/audit/api/src/main/java/org/keycloak/audit/Details.java
@@ -17,6 +17,7 @@ public interface Details {
     String REMEMBER_ME = "remember_me";
     String TOKEN_ID = "token_id";
     String REFRESH_TOKEN_ID = "refresh_token_id";
+    String VALIDATE_ACCESS_TOKEN = "validate_access_token";
     String UPDATED_REFRESH_TOKEN_ID = "updated_refresh_token_id";
 
 }
diff --git a/audit/api/src/main/java/org/keycloak/audit/EventType.java b/audit/api/src/main/java/org/keycloak/audit/EventType.java
old mode 100644
new mode 100755
index 707f5fd..0913ef3
--- a/audit/api/src/main/java/org/keycloak/audit/EventType.java
+++ b/audit/api/src/main/java/org/keycloak/audit/EventType.java
@@ -14,6 +14,8 @@ public enum EventType {
     CODE_TO_TOKEN,
     CODE_TO_TOKEN_ERROR,
     REFRESH_TOKEN,
+    VALIDATE_ACCESS_TOKEN,
+    VALIDATE_ACCESS_TOKEN_ERROR,
     REFRESH_TOKEN_ERROR,
     SOCIAL_LINK,
     SOCIAL_LINK_ERROR,
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
index 7b3c388..39a51ef 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
@@ -10,5 +10,11 @@ import java.util.List;
  */
 public interface UserFederationProviderFactory extends ProviderFactory<UserFederationProvider> {
     UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model);
+
+    /**
+     * Config options to display in generic admin console page for federation
+     *
+     * @return
+     */
     List<String> getConfigurationOptions();
 }
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 ee2282f..625055f 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -11,6 +11,8 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.jboss.resteasy.spi.UnauthorizedException;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.OAuthErrorException;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.VerificationException;
 import org.keycloak.audit.Audit;
 import org.keycloak.audit.Details;
 import org.keycloak.audit.Errors;
@@ -45,6 +47,7 @@ import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.services.validation.Validation;
 import org.keycloak.util.Base64Url;
 import org.keycloak.util.BasicAuthHelper;
+import org.keycloak.util.Time;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -137,6 +140,11 @@ public class TokenService {
         return uriBuilder.path(TokenService.class, "accessCodeToToken");
     }
 
+    public static UriBuilder validateAccessTokenUrl(UriBuilder baseUriBuilder) {
+        UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
+        return uriBuilder.path(TokenService.class, "validateAccessToken");
+    }
+
     public static UriBuilder grantAccessTokenUrl(UriInfo uriInfo) {
         UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
         return grantAccessTokenUrl(baseUriBuilder);
@@ -296,6 +304,105 @@ public class TokenService {
     }
 
     /**
+     * Validate encoded access token.
+     *
+     * @param tokenString
+     * @return Unmarshalled token
+     */
+    @Path("validate")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response validateAccessToken(@QueryParam("access_token") String tokenString) {
+        audit.event(EventType.VALIDATE_ACCESS_TOKEN);
+        AccessToken token = null;
+        try {
+            token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName());
+        } catch (Exception e) {
+            Map<String, String> err = new HashMap<String, String>();
+            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
+            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Token invalid");
+            audit.error(Errors.INVALID_TOKEN);
+            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
+                    .build();
+        }
+        audit.user(token.getSubject()).session(token.getSessionState()).detail(Details.VALIDATE_ACCESS_TOKEN, token.getId());
+
+        if (token.isExpired()
+                || token.getIssuedAt() < realm.getNotBefore()
+                ) {
+            Map<String, String> err = new HashMap<String, String>();
+            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
+            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Token expired");
+            audit.error(Errors.INVALID_TOKEN);
+            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
+                    .build();
+        }
+
+
+        UserModel user = session.users().getUserById(token.getSubject(), realm);
+        if (user == null) {
+            Map<String, String> err = new HashMap<String, String>();
+            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
+            err.put(OAuth2Constants.ERROR_DESCRIPTION, "User does not exist");
+            audit.error(Errors.USER_NOT_FOUND);
+            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
+                    .build();
+        }
+
+        if (!user.isEnabled()) {
+            Map<String, String> err = new HashMap<String, String>();
+            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
+            err.put(OAuth2Constants.ERROR_DESCRIPTION, "User disabled");
+            audit.error(Errors.USER_DISABLED);
+            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
+                    .build();
+        }
+
+        UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
+        if (!AuthenticationManager.isSessionValid(realm, userSession)) {
+            Map<String, String> err = new HashMap<String, String>();
+            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
+            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Expired session");
+            audit.error(Errors.USER_SESSION_NOT_FOUND);
+            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
+                    .build();
+        }
+
+        ClientModel client = realm.findClient(token.getIssuedFor());
+        if (client == null) {
+            Map<String, String> err = new HashMap<String, String>();
+            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_CLIENT);
+            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Issued for client no longer exists");
+            audit.error(Errors.CLIENT_NOT_FOUND);
+            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
+                    .build();
+
+        }
+
+        if (token.getIssuedAt() < client.getNotBefore()) {
+            Map<String, String> err = new HashMap<String, String>();
+            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_CLIENT);
+            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Issued for client no longer exists");
+            audit.error(Errors.INVALID_TOKEN);
+            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
+                    .build();
+        }
+
+        try {
+            tokenManager.verifyAccess(token, realm, client, user);
+        } catch (OAuthErrorException e) {
+            Map<String, String> err = new HashMap<String, String>();
+            err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_SCOPE);
+            err.put(OAuth2Constants.ERROR_DESCRIPTION, "Role mappings have changed");
+            audit.error(Errors.INVALID_TOKEN);
+            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
+                    .build();
+
+        }
+        return Response.ok(token, MediaType.APPLICATION_JSON_TYPE).build();
+    }
+
+    /**
      * URL for making refresh token requests.
      *
      * @See <a href="http://tools.ietf.org/html/rfc6749#section-6">http://tools.ietf.org/html/rfc6749#section-6</a>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index 3b57ca3..7bba7c9 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
@@ -365,4 +365,6 @@ public class AdapterTest {
 
     }
 
+
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index ba7c41d..980879c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -34,6 +34,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.resources.TokenService;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
@@ -41,8 +42,22 @@ import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.util.BasicAuthHelper;
 import org.openqa.selenium.WebDriver;
 
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.lessThanOrEqualTo;
@@ -108,6 +123,7 @@ public class AccessTokenTest {
         Assert.assertEquals(token.getId(), event.getDetails().get(Details.TOKEN_ID));
         Assert.assertEquals(oauth.verifyRefreshToken(response.getRefreshToken()).getId(), event.getDetails().get(Details.REFRESH_TOKEN_ID));
         Assert.assertEquals(sessionId, token.getSessionState());
+
     }
 
     @Test
@@ -247,4 +263,63 @@ public class AccessTokenTest {
         });
     }
 
+    @Test
+    public void testValidateAccessToken() throws Exception {
+        Client client = ClientBuilder.newClient();
+        UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT);
+        URI grantUri = TokenService.grantAccessTokenUrl(builder).build("test");
+        WebTarget grantTarget = client.target(grantUri);
+        builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT);
+        URI validateUri = TokenService.validateAccessTokenUrl(builder).build("test");
+        WebTarget validateTarget = client.target(validateUri);
+
+        {
+            Response response = validateTarget.queryParam("access_token", "bad token").request().get();
+            Assert.assertEquals(400, response.getStatus());
+            HashMap<String, String> error = response.readEntity(new GenericType <HashMap<String, String>>() {});
+            Assert.assertNotNull(error.get("error"));
+        }
+
+
+        org.keycloak.representations.AccessTokenResponse tokenResponse = null;
+        {
+            String header = BasicAuthHelper.createHeader("test-app", "password");
+            Form form = new Form();
+            form.param("username", "test-user@localhost")
+                    .param("password", "password");
+            Response response = grantTarget.request()
+                    .header(HttpHeaders.AUTHORIZATION, header)
+                    .post(Entity.form(form));
+            Assert.assertEquals(200, response.getStatus());
+            tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class);
+            response.close();
+        }
+
+        {
+            Response response = validateTarget.queryParam("access_token", tokenResponse.getToken()).request().get();
+            Assert.assertEquals(200, response.getStatus());
+            AccessToken token = response.readEntity(AccessToken.class);
+            Assert.assertNotNull(token);
+            response.close();
+        }
+        {
+            builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT);
+            URI logoutUri = TokenService.logoutUrl(builder).build("test");
+            Response response = client.target(logoutUri).queryParam("session_state", tokenResponse.getSessionState()).request().get();
+            Assert.assertEquals(200, response.getStatus());
+            response.close();
+        }
+        {
+            Response response = validateTarget.queryParam("access_token", tokenResponse.getToken()).request().get();
+            Assert.assertEquals(400, response.getStatus());
+            HashMap<String, String> error = response.readEntity(new GenericType <HashMap<String, String>>() {});
+            Assert.assertNotNull(error.get("error"));
+        }
+
+        client.close();
+        events.clear();
+
+    }
+
+
 }
diff --git a/testsuite/integration/src/test/resources/testrealm.json b/testsuite/integration/src/test/resources/testrealm.json
index ced8c26..cc2a614 100755
--- a/testsuite/integration/src/test/resources/testrealm.json
+++ b/testsuite/integration/src/test/resources/testrealm.json
@@ -5,6 +5,7 @@
     "sslRequired": "external",
     "registrationAllowed": true,
     "resetPasswordAllowed": true,
+    "passwordCredentialGrantAllowed": true,
     "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
     "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
     "requiredCredentials": [ "password" ],