keycloak-aplcache

role mappers and testing

3/9/2015 10:03:35 PM

Details

diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
index 5bcc031..3de9a87 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
@@ -30,6 +30,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.UserSessionProvider;
@@ -372,85 +373,22 @@ public class OIDCLoginProtocolService {
             Map<String, String> err = new HashMap<String, String>();
             err.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_GRANT);
             err.put(OAuth2Constants.ERROR_DESCRIPTION, "Token invalid");
+            logger.error("Invalid token. Token verification failed.");
             event.error(Errors.INVALID_TOKEN);
             return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
                     .build();
         }
         event.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");
-            event.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");
-            event.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");
-            event.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");
-            event.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");
-            event.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");
-            event.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);
+            tokenManager.validateToken(session, uriInfo, clientConnection, realm, token);
         } 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");
+            Map<String, String> error = new HashMap<String, String>();
+            error.put(OAuth2Constants.ERROR, e.getError());
+            if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
             event.error(Errors.INVALID_TOKEN);
-            return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
-                    .build();
-
+            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
         }
-
         event.success();
 
         return Response.ok(token, MediaType.APPLICATION_JSON_TYPE).build();
@@ -666,16 +604,6 @@ public class OIDCLoginProtocolService {
 
         AccessToken token = tokenManager.createClientAccessToken(session, accessCode.getRequestedRoles(), realm, client, user, userSession, clientSession);
 
-        try {
-            tokenManager.verifyAccess(token, realm, client, user);
-        } catch (OAuthErrorException e) {
-            Map<String, String> error = new HashMap<String, String>();
-            error.put(OAuth2Constants.ERROR, e.getError());
-            if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
-            event.error(Errors.INVALID_CODE);
-            return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
-        }
-
         AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
                 .accessToken(token)
                 .generateIDToken()
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 14fb6ec..07fd828 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -59,12 +59,22 @@ public class TokenManager {
         }
     }
 
-    public AccessTokenResponse refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel client, String encodedRefreshToken, EventBuilder event) throws OAuthErrorException {
-        RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken);
-
-        event.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
+    public static class TokenValidation {
+        public final UserModel user;
+        public final UserSessionModel userSession;
+        public final ClientSessionModel clientSession;
+        public final AccessToken newToken;
+
+        public TokenValidation(UserModel user, UserSessionModel userSession, ClientSessionModel clientSession, AccessToken newToken) {
+            this.user = user;
+            this.userSession = userSession;
+            this.clientSession = clientSession;
+            this.newToken = newToken;
+        }
+    }
 
-        UserModel user = session.users().getUserById(refreshToken.getSubject(), realm);
+    public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, AccessToken oldToken) throws OAuthErrorException {
+        UserModel user = session.users().getUserById(oldToken.getSubject(), realm);
         if (user == null) {
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown user");
         }
@@ -73,24 +83,14 @@ public class TokenManager {
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "User disabled", "User disabled");
         }
 
-        UserSessionModel userSession = session.sessions().getUserSession(realm, refreshToken.getSessionState());
-        int currentTime = Time.currentTime();
+        UserSessionModel userSession = session.sessions().getUserSession(realm, oldToken.getSessionState());
         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
             AuthenticationManager.logout(session, realm, userSession, uriInfo, connection);
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
         }
-
-        if (!client.getClientId().equals(refreshToken.getIssuedFor())) {
-            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Unmatching clients", "Unmatching clients");
-        }
-
-        if (refreshToken.getIssuedAt() < client.getNotBefore()) {
-            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
-        }
-
         ClientSessionModel clientSession = null;
         for (ClientSessionModel clientSessionModel : userSession.getClientSessions()) {
-            if (clientSessionModel.getId().equals(refreshToken.getClientSession())) {
+            if (clientSessionModel.getId().equals(oldToken.getClientSession())) {
                 clientSession = clientSessionModel;
                 break;
             }
@@ -98,20 +98,48 @@ public class TokenManager {
 
         if (clientSession == null) {
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Client session not active", "Client session not active");
+        }
+
+        ClientModel client = clientSession.getClient();
+
+        if (!client.getClientId().equals(oldToken.getIssuedFor())) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Unmatching clients", "Unmatching clients");
+        }
 
+        if (oldToken.getIssuedAt() < client.getNotBefore()) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
         }
+        if (oldToken.getIssuedAt() < realm.getNotBefore()) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
+        }
+
+
+        // recreate token.
+        Set<RoleModel> requestedRoles = TokenManager.getAccess(null, clientSession.getClient(), user);
+        AccessToken newToken = createClientAccessToken(session, requestedRoles, realm, client, user, userSession, clientSession);
+        verifyAccess(oldToken, newToken);
+
+        return new TokenValidation(user, userSession, clientSession, newToken);
+
 
-        verifyAccess(refreshToken, realm, client, user);
+    }
+
+    public AccessTokenResponse refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event) throws OAuthErrorException {
+        RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken);
+
+        event.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
 
-        AccessToken accessToken = initToken(realm, client, user, userSession, clientSession);
-        accessToken.setRealmAccess(refreshToken.getRealmAccess());
-        accessToken.setResourceAccess(refreshToken.getResourceAccess());
-        accessToken = transformAccessToken(session, accessToken, realm, client, user, userSession, clientSession);
+        TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken);
+        // validate authorizedClient is same as validated client
+        if (!validation.clientSession.getClient().getId().equals(authorizedClient.getId())) {
+            throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token. Token client and authorized client don't match");
+        }
 
-        userSession.setLastSessionRefresh(currentTime);
+        int currentTime = Time.currentTime();
+        validation.userSession.setLastSessionRefresh(currentTime);
 
-        AccessTokenResponse res = responseBuilder(realm, client, event, session, userSession, clientSession)
-                .accessToken(accessToken)
+        AccessTokenResponse res = responseBuilder(realm, authorizedClient, event, session, validation.userSession, validation.clientSession)
+                .accessToken(validation.newToken)
                 .generateIDToken()
                 .generateRefreshToken().build();
         return res;
@@ -198,41 +226,27 @@ public class TokenManager {
         return requestedRoles;
     }
 
-    public void verifyAccess(AccessToken token, RealmModel realm, ClientModel client, UserModel user) throws OAuthErrorException {
-        ApplicationModel clientApp = (client instanceof ApplicationModel) ? (ApplicationModel)client : null;
-
-
+    public void verifyAccess(AccessToken token, AccessToken newToken) throws OAuthErrorException {
         if (token.getRealmAccess() != null) {
+            if (newToken.getRealmAccess() == null) throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for realm roles");
+
             for (String roleName : token.getRealmAccess().getRoles()) {
-                RoleModel role = realm.getRole(roleName);
-                if (role == null) {
-                    throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid realm role " + roleName);
-                }
-                if (!user.hasRole(role)) {
+                if (!newToken.getRealmAccess().getRoles().contains(roleName)) {
                     throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for realm role: " + roleName);
                 }
-                if (!client.hasScope(role)) {
-                    throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Client no longer has realm scope: " + roleName);
-                }
             }
         }
         if (token.getResourceAccess() != null) {
             for (Map.Entry<String, AccessToken.Access> entry : token.getResourceAccess().entrySet()) {
-                ApplicationModel app = realm.getApplicationByName(entry.getKey());
-                if (app == null) {
-                    throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Application no longer exists", "Application no longer exists: " + entry.getKey());
+                AccessToken.Access appAccess = newToken.getResourceAccess(entry.getKey());
+                if (appAccess == null && !entry.getValue().getRoles().isEmpty()) {
+                    throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User or application no longer has role permissions for application key: " + entry.getKey());
+
                 }
                 for (String roleName : entry.getValue().getRoles()) {
-                    RoleModel role = app.getRole(roleName);
-                    if (role == null) {
-                        throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown application role: " + roleName);
-                    }
-                    if (!user.hasRole(role)) {
+                    if (!appAccess.getRoles().contains(roleName)) {
                         throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for application role " + roleName);
                     }
-                    if (clientApp != null && !clientApp.equals(app) && !client.hasScope(role)) {
-                        throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Client no longer has application scope" + roleName);
-                    }
                 }
             }
         }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 89ed153..6497cb5 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -276,8 +276,8 @@ public class AccountTest {
 
         events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT).assertEvent();
 
-        Assert.assertEquals("", profilePage.getFirstName());
-        Assert.assertEquals("", profilePage.getLastName());
+        Assert.assertEquals("Tom", profilePage.getFirstName());
+        Assert.assertEquals("Brady", profilePage.getLastName());
         Assert.assertEquals("test-user@localhost", profilePage.getEmail());
 
         // All fields are required, so there should be an error when something is missing.
@@ -310,8 +310,8 @@ public class AccountTest {
 
         profilePage.clickCancel();
 
-        Assert.assertEquals("", profilePage.getFirstName());
-        Assert.assertEquals("", profilePage.getLastName());
+        Assert.assertEquals("Tom", profilePage.getFirstName());
+        Assert.assertEquals("Brady", profilePage.getLastName());
         Assert.assertEquals("test-user@localhost", profilePage.getEmail());
 
         events.assertEmpty();
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 7b51c15..0091e33 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
@@ -653,6 +653,29 @@ public class AccessTokenTest {
             response.close();
         }
         client.close();
+
+        // undo mappers
+        {
+            KeycloakSession session = keycloakRule.startSession();
+            RealmModel realm = session.realms().getRealmByName("test");
+            ApplicationModel app = realm.getApplicationByName("test-app");
+            for (ProtocolMapperModel model : app.getProtocolMappers()) {
+                if (model.getName().equals("address")
+                        || model.getName().equals("hard")
+                        || model.getName().equals("hard-nested")
+                        || model.getName().equals("custom phone")
+                        || model.getName().equals("nested phone")
+                        || model.getName().equals("hard-realm")
+                        || model.getName().equals("hard-app")
+                        )   {
+                    app.removeProtocolMapper(model);
+                }
+            }
+            session.getTransaction().commit();
+            session.close();
+        }
+
+
         events.clear();
 
     }