keycloak-memoizeit

oauth

7/28/2013 11:34:54 PM

Details

diff --git a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
index d96b32b..63032d7 100755
--- a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
+++ b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
@@ -1,8 +1,13 @@
 package org.keycloak.services.managers;
 
 import org.keycloak.representations.SkeletonKeyToken;
+import org.picketlink.idm.model.Role;
 import org.picketlink.idm.model.User;
 
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.UUID;
 
 /**
@@ -11,9 +16,13 @@ import java.util.UUID;
 */
 public class AccessCodeEntry {
     protected String id = UUID.randomUUID().toString() + System.currentTimeMillis();
+    protected String code;
     protected long expiration;
     protected SkeletonKeyToken token;
+    protected User user;
     protected User client;
+    protected List<Role> realmRolesRequested = new ArrayList<Role>();
+    MultivaluedMap<String, Role> resourceRolesRequested = new MultivaluedHashMap<String, Role>();
 
     public boolean isExpired() {
         return expiration != 0 && (System.currentTimeMillis() / 1000) > expiration;
@@ -23,6 +32,14 @@ public class AccessCodeEntry {
         return id;
     }
 
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
     public long getExpiration() {
         return expiration;
     }
@@ -46,4 +63,20 @@ public class AccessCodeEntry {
     public void setClient(User client) {
         this.client = client;
     }
+
+    public User getUser() {
+        return user;
+    }
+
+    public void setUser(User user) {
+        this.user = user;
+    }
+
+    public List<Role> getRealmRolesRequested() {
+        return realmRolesRequested;
+    }
+
+    public MultivaluedMap<String, Role> getResourceRolesRequested() {
+        return resourceRolesRequested;
+    }
 }
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 68ef550..1c4bb00 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -3,19 +3,16 @@ 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.picketlink.idm.model.Role;
 import org.picketlink.idm.model.User;
 
-import javax.ws.rs.ForbiddenException;
-import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.NewCookie;
-import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
@@ -40,6 +37,9 @@ public class TokenManager {
         accessCodeMap.clear();
     }
 
+    public AccessCodeEntry getAccessCode(String key) {
+        return accessCodeMap.get(key);
+    }
 
     public AccessCodeEntry pullAccessCode(String key) {
         return accessCodeMap.remove(key);
@@ -54,16 +54,57 @@ public class TokenManager {
         return cookie;
     }
 
-    public String createAccessCode(String scopeParam, RealmModel realm, User client, User user)
-    {
-        SkeletonKeyToken token = null;
-        if (scopeParam != null) token = createScopedToken(scopeParam, realm, client, user);
-        else token = createUnscopedToken(realm, client, user);
-
+    public AccessCodeEntry createAccessCode(String scopeParam, RealmModel realm, User client, User user) {
         AccessCodeEntry code = new AccessCodeEntry();
+        SkeletonKeyScope scopeMap = null;
+        if (scopeParam != null) scopeMap = decodeScope(scopeParam);
+        List<Role> realmRolesRequested = code.getRealmRolesRequested();
+        MultivaluedMap<String, Role> resourceRolesRequested = code.getResourceRolesRequested();
+        Set<String> realmMapping = realm.getRoleMappings(user);
+
+        if (realmMapping != null && realmMapping.size() > 0 && (scopeMap == null || scopeMap.containsKey("realm"))) {
+            Set<String> scope = realm.getScope(client);
+            if (scope.size() > 0) {
+                Set<String> scopeRequest = null;
+                if (scopeMap != null) {
+                    scopeRequest.addAll(scopeMap.get("realm"));
+                    if (scopeRequest.contains(RealmManager.WILDCARD_ROLE)) scopeRequest = null;
+                }
+                for (String role : realmMapping) {
+                    if (
+                            (scopeRequest == null || scopeRequest.contains(role)) &&
+                                    (scope.contains("*") || scope.contains(role))
+                            )
+                        realmRolesRequested.add(realm.getIdm().getRole(role));
+                }
+            }
+        }
+        for (ResourceModel resource : realm.getResources()) {
+            Set<String> mapping = resource.getRoleMappings(user);
+            if (mapping != null && mapping.size() > 0 && (scopeMap == null || scopeMap.containsKey(resource.getName()))) {
+                Set<String> scope = resource.getScope(client);
+                if (scope.size() > 0) {
+                    Set<String> scopeRequest = null;
+                    if (scopeMap != null) {
+                        scopeRequest.addAll(scopeMap.get(resource.getName()));
+                        if (scopeRequest.contains(RealmManager.WILDCARD_ROLE)) scopeRequest = null;
+                    }
+                    for (String role : mapping) {
+                        if (
+                                (scopeRequest == null || scopeRequest.contains(role)) &&
+                                        (scope.contains("*") || scope.contains(role))
+                                )
+                            resourceRolesRequested.add(resource.getName(), resource.getIdm().getRole(role));
+                    }
+                }
+            }
+        }
+
+
+        createToken(code, realm, client, user);
         code.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
-        code.setToken(token);
         code.setClient(client);
+        code.setUser(user);
         accessCodeMap.put(code.getId(), code);
         String accessCode = null;
         try {
@@ -71,30 +112,8 @@ public class TokenManager {
         } catch (UnsupportedEncodingException e) {
             throw new RuntimeException(e);
         }
-        return accessCode;
-    }
-
-    public SkeletonKeyToken createScopedToken(SkeletonKeyScope scope, RealmModel realm, User client, User user) {
-        SkeletonKeyToken token = initToken(realm, client, user);
-        Map<String, ResourceModel> resourceMap = realm.getResourceMap();
-
-        for (String res : scope.keySet()) {
-            ResourceModel resource = resourceMap.get(res);
-            Set<String> scopeMapping = resource.getScope(client);
-            Set<String> roleMapping = resource.getRoleMappings(user);
-            SkeletonKeyToken.Access access = token.addAccess(resource.getName());
-            for (String role : scope.get(res)) {
-                if (!scopeMapping.contains("*") && !scopeMapping.contains(role)) {
-                    throw new ForbiddenException(Response.status(403).entity("<h1>Security Alert</h1><p>Known client not authorized for the requested scope.</p>").type("text/html").build());
-                }
-                if (!roleMapping.contains(role)) {
-                    throw new ForbiddenException(Response.status(403).entity("<h1>Security Alert</h1><p>Known client not authorized for the requested scope.</p>").type("text/html").build());
-
-                }
-                access.addRole(role);
-            }
-        }
-        return token;
+        code.setCode(accessCode);
+        return code;
     }
 
     protected SkeletonKeyToken initToken(RealmModel realm, User client, User user) {
@@ -110,38 +129,29 @@ public class TokenManager {
         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 createUnscopedToken(RealmModel realm, User client, User user) {
+    protected void createToken(AccessCodeEntry accessCodeEntry, RealmModel realm, User client, User user) {
 
         SkeletonKeyToken token = initToken(realm, client, user);
 
-        Set<String> realmMapping = realm.getRoleMappings(user);
-
-        if (realmMapping != null && realmMapping.size() > 0) {
-            Set<String> scope = realm.getScope(client);
+        if (accessCodeEntry.getRealmRolesRequested().size() > 0) {
             SkeletonKeyToken.Access access = new SkeletonKeyToken.Access();
-            for (String role : realmMapping) {
-                if (scope.contains("*") || scope.contains(role)) access.addRole(role);
+            for (Role role : accessCodeEntry.getRealmRolesRequested()) {
+                access.addRole(role.getName());
             }
             token.setRealmAccess(access);
         }
-        List<ResourceModel> resources = realm.getResources();
-        for (ResourceModel resource : resources) {
-            Set<String> scope = resource.getScope(client);
-            Set<String> 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);
+
+        if (accessCodeEntry.getResourceRolesRequested().size() > 0) {
+            Map<String, ResourceModel> resourceMap = realm.getResourceMap();
+            for (String resourceName : accessCodeEntry.getResourceRolesRequested().keySet()) {
+                ResourceModel resource = resourceMap.get(resourceName);
+                SkeletonKeyToken.Access access = token.addAccess(resourceName).verifyCaller(resource.isSurrogateAuthRequired());
+                for (Role role : accessCodeEntry.getResourceRolesRequested().get(resourceName)) {
+                    access.addRole(role.getName());
+                }
             }
         }
-        return token;
-
+        accessCodeEntry.setToken(token);
     }
 
     public String encodeScope(SkeletonKeyScope scope) {
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 eb52cfe..f450491 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -1,12 +1,13 @@
 package org.keycloak.services.resources;
 
-import org.jboss.resteasy.jose.Base64Url;
 import org.jboss.resteasy.jose.jws.JWSBuilder;
 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.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
+import org.jboss.resteasy.spi.HttpResponse;
+import org.keycloak.TokenIdGenerator;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.SkeletonKeyScope;
 import org.keycloak.representations.SkeletonKeyToken;
@@ -17,7 +18,6 @@ import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.models.RealmModel;
-import org.keycloak.services.models.RequiredCredentialModel;
 import org.keycloak.services.models.ResourceModel;
 import org.picketlink.idm.IdentitySession;
 import org.picketlink.idm.model.Role;
@@ -31,15 +31,20 @@ import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
 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;
 import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.ext.Providers;
+import java.net.URI;
 import java.security.PrivateKey;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -66,10 +71,13 @@ public class TokenService {
     protected IdentitySession identitySession;
     @Context
     HttpRequest request;
+    @Context
+    HttpResponse response;
 
 
     protected String securityFailurePath = "/securityFailure.jsp";
     protected String loginFormPath = "/loginForm.jsp";
+    protected String oauthFormPath = "/oauthForm.jsp";
 
     protected RealmModel realm;
     protected TokenManager tokenManager;
@@ -110,6 +118,10 @@ public class TokenService {
         return tokenServiceBaseUrl(uriInfo).path(TokenService.class, "processLogin");
     }
 
+    public static UriBuilder processOAuthUrl(UriInfo uriInfo) {
+        return tokenServiceBaseUrl(uriInfo).path(TokenService.class, "processOAuth");
+    }
+
 
     @Path("grants/identity-token")
     @POST
@@ -211,16 +223,35 @@ public class TokenService {
             return null;
         }
 
-        return redirectAccessCode(scopeParam, state, redirect, client, user);
+        return processAccessCode(scopeParam, state, redirect, client, user);
+    }
+
+    protected Response processAccessCode(String scopeParam, String state, String redirect, User client, User user) {
+        Role resourceRole = realm.getIdm().getRole(RealmManager.RESOURCE_ROLE);
+        Role oauthClientRole = realm.getIdm().getRole(RealmManager.OAUTH_CLIENT_ROLE);
+        boolean isResource = realm.getIdm().hasRole(client, resourceRole);
+        if (!isResource && !realm.getIdm().hasRole(client, oauthClientRole)) {
+            securityFailureForward("Login requester not allowed to request login.");
+            identitySession.close();
+            return null;
+        }
+        AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, realm, client, user);
+
+        if (!isResource && accessCode.getRealmRolesRequested().size() > 0 && accessCode.getResourceRolesRequested().size() > 0) {
+            oauthGrantPage(accessCode, client, state, redirect);
+            identitySession.close();
+            return null;
+        }
+        return redirectAccessCode(accessCode, state, redirect);
     }
 
-    protected Response redirectAccessCode(String scopeParam, String state, String redirect, User client, User user) {
-        String accessCode = tokenManager.createAccessCode(scopeParam, realm, client, user);
-        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", accessCode);
+    protected Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect) {
+        String code = accessCode.getCode();
+        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", code);
         if (state != null) redirectUri.queryParam("state", state);
         Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
         if (realm.isCookieLoginAllowed()) {
-           location.cookie(tokenManager.createLoginCookie(realm, user, uriInfo));
+            location.cookie(tokenManager.createLoginCookie(realm, accessCode.getUser(), uriInfo));
         }
         return location.build();
     }
@@ -393,16 +424,16 @@ public class TokenService {
         }
         Role resourceRole = realm.getIdm().getRole(RealmManager.RESOURCE_ROLE);
         Role oauthClientRole = realm.getIdm().getRole(RealmManager.OAUTH_CLIENT_ROLE);
-        if (!realm.getIdm().hasRole(client, resourceRole) && !realm.getIdm().hasRole(client, oauthClientRole)) {
+        boolean isResource = realm.getIdm().hasRole(client, resourceRole);
+        if (!isResource && !realm.getIdm().hasRole(client, oauthClientRole)) {
             securityFailureForward("Login requester not allowed to request login.");
             identitySession.close();
             return null;
-
         }
 
         User user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
         if (user != null) {
-            return redirectAccessCode(scopeParam, state, redirect, client, user);
+            return processAccessCode(scopeParam, state, redirect, client, user);
         }
 
         forwardToLoginForm(redirect, clientId, scopeParam, state);
@@ -424,117 +455,52 @@ public class TokenService {
         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) {
-            html.append("<h1>Grant Request For ").append(realm.getName()).append(" Realm</h1>");
-            if (validationError != null) {
-                try {
-                    Thread.sleep(1000); // put in a delay
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                html.append("<p/><p><b>").append(validationError).append("</b></p>");
-            }
-            html.append("<p>A Third Party is requesting access to the following resources</p>");
-            html.append("<table>");
-            SkeletonKeyScope scope = tokenManager.decodeScope(scopeParam);
-            Map<String, ResourceModel> resourceMap = realm.getResourceMap();
-
-            for (String res : scope.keySet()) {
-                ResourceModel resource = resourceMap.get(res);
-                html.append("<tr><td><b>Resource: </b>").append(resource.getName()).append("</td><td><b>Roles:</b>");
-                Set<String> scopeMapping = resource.getScope(client);
-                for (String role : scope.get(res)) {
-                    html.append(" ").append(role);
-                    if (!scopeMapping.contains("*") && !scopeMapping.contains(role)) {
-                        return Response.ok("<h1>Security Alert</h1><p>Known client not authorized for the requested scope.</p>").type("text/html").build();
-                    }
-                }
-                html.append("</td></tr>");
-            }
-            html.append("</table><p>To Authorize, please login below</p>");
-        } else {
-            Set<String> scopeMapping = realm.getScope(client);
-            if (scopeMapping.contains("*")) {
-                html.append("<h1>Login For ").append(realm.getName()).append(" Realm</h1>");
-                if (validationError != null) {
-                    try {
-                        Thread.sleep(1000); // put in a delay
-                    } catch (InterruptedException e) {
-                        throw new RuntimeException(e);
-                    }
-                    html.append("<p/><p><b>").append(validationError).append("</b></p>");
-                }
-            } else {
-                html.append("<h1>Grant Request For ").append(realm.getName()).append(" Realm</h1>");
-                if (validationError != null) {
-                    try {
-                        Thread.sleep(1000); // put in a delay
-                    } catch (InterruptedException e) {
-                        throw new RuntimeException(e);
-                    }
-                    html.append("<p/><p><b>").append(validationError).append("</b></p>");
-                }
-                SkeletonKeyScope scope = new SkeletonKeyScope();
-                List<ResourceModel> resources = realm.getResources();
-                boolean found = false;
-                for (ResourceModel resource : resources) {
-                    Set<String> resourceScope = resource.getScope(client);
-                    if (resourceScope == null) continue;
-                    if (resourceScope.size() == 0) continue;
-                    if (!found) {
-                        found = true;
-                        html.append("<p>A Third Party is requesting access to the following resources</p>");
-                        html.append("<table>");
-                    }
-                    html.append("<tr><td><b>Resource: </b>").append(resource.getName()).append("</td><td><b>Roles:</b>");
-                    // todo add description of role
-                    for (String role : resourceScope) {
-                        html.append(" ").append(role);
-                        scope.add(resource.getName(), role);
-                    }
-                }
-                if (!found) {
-                    return Response.ok("<h1>Security Alert</h1><p>Known client not authorized to access this realm.</p>").type("text/html").build();
-                }
-                html.append("</table>");
-                try {
-                    String json = JsonSerialization.toString(scope, false);
-                    scopeParam = Base64Url.encode(json.getBytes("UTF-8"));
-                } catch (Exception e) {
-                    throw new RuntimeException(e);
-                }
-
-            }
-        }
-
-        UriBuilder formActionUri = processLoginUrl(uriInfo);
-        String action = formActionUri.build(realm.getId()).toString();
-        html.append("<form action=\"").append(action).append("\" method=\"POST\">");
-        html.append("Username: <input type=\"text\" name=\"username\" size=\"20\"><br>");
-
-        for (RequiredCredentialModel credential : realm.getRequiredCredentials()) {
-            if (!credential.isInput()) continue;
-            html.append(credential.getType()).append(": ");
-            if (credential.isSecret()) {
-                html.append("<input type=\"password\" name=\"").append(credential.getType()).append("\"  size=\"20\"><br>");
-
-            } else {
-                html.append("<input type=\"text\" name=\"").append(credential.getType()).append("\"  size=\"20\"><br>");
-            }
-        }
-        html.append("<input type=\"hidden\" name=\"client_id\" value=\"").append(clientId).append("\">");
-        if (scopeParam != null) {
-            html.append("<input type=\"hidden\" name=\"scope\" value=\"").append(scopeParam).append("\">");
-        }
-        if (state != null) html.append("<input type=\"hidden\" name=\"state\" value=\"").append(state).append("\">");
-        html.append("<input type=\"hidden\" name=\"redirect_uri\" value=\"").append(redirect).append("\">");
-        html.append("<input type=\"submit\" value=\"");
-        if (scopeParam == null) html.append("Login");
-        else html.append("Grant Access");
-        html.append("\">");
-        html.append("</form>");
-        return Response.ok(html.toString()).type("text/html").build();
+    @Path("oauth/grant")
+    @POST
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response processOAuth(MultivaluedMap<String, String> formData) {
+        String redirect = formData.getFirst("redirect_uri");
+        String state = formData.getFirst("state");
+        if (formData.containsKey("cancel")) {
+            return redirectAccessDenied(redirect, state);
+        }
+        String code = formData.getFirst("code");
+
+        JWSInput input = new JWSInput(code, providers);
+        boolean verifiedCode = false;
+        try {
+            verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
+        } catch (Exception ignored) {
+            logger.debug("Failed to verify signature", ignored);
+        }
+        if (!verifiedCode) {
+            return redirectAccessDenied(redirect, state);
+        }
+        String key = input.readContent(String.class);
+        AccessCodeEntry accessCodeEntry = tokenManager.getAccessCode(key);
+        if (accessCodeEntry == null) {
+            return redirectAccessDenied(redirect, state);
+        }
+        return redirectAccessCode(accessCodeEntry, state, redirect);
+    }
+
+    protected Response redirectAccessDenied(String redirect, String state) {
+        UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("error", "access_denied");
+        if (state != null) redirectUri.queryParam("state", state);
+        Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
+        return location.build();
     }
+
+    protected void oauthGrantPage(AccessCodeEntry accessCode, User client, String state, String redirect_uri) {
+        request.setAttribute("realmRolesRequested", accessCode.getRealmRolesRequested());
+        request.setAttribute("resourceRolesRequested", accessCode.getResourceRolesRequested());
+        request.setAttribute("state", state);
+        request.setAttribute("redirect_uri", redirect_uri);
+        request.setAttribute("client", client);
+        request.setAttribute("action", processOAuthUrl(uriInfo));
+        request.setAttribute("accessCode", accessCode.getCode());
+
+        request.forward(oauthFormPath);
+    }
+
 }