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);
+ }
+
}