Merge pull request #254 from patriot1burke/master

add claims to grant page
This commit is contained in:
Bill Burke 2014-02-28 10:45:54 -05:00
commit 581d64614e
11 changed files with 135 additions and 82 deletions

View file

@ -15,34 +15,22 @@ import java.security.PublicKey;
*/ */
public class PublishedRealmRepresentation { public class PublishedRealmRepresentation {
protected String realm; protected String realm;
protected String self;
@JsonProperty("public_key") @JsonProperty("public_key")
protected String publicKeyPem; protected String publicKeyPem;
@JsonProperty("authorization") @JsonProperty("token-service")
protected String authorizationUrl; protected String tokenServiceUrl;
@JsonProperty("codes") @JsonProperty("account-service")
protected String codeUrl; protected String accountServiceUrl;
@JsonProperty("grants") @JsonProperty("admin-api")
protected String grantUrl; protected String adminApiUrl;
@JsonProperty("admin-role")
protected String adminRole;
@JsonIgnore @JsonIgnore
protected volatile transient PublicKey publicKey; protected volatile transient PublicKey publicKey;
public String getAdminRole() {
return adminRole;
}
public void setAdminRole(String adminRole) {
this.adminRole = adminRole;
}
public String getRealm() { public String getRealm() {
return realm; return realm;
} }
@ -51,14 +39,6 @@ public class PublishedRealmRepresentation {
this.realm = realm; this.realm = realm;
} }
public String getSelf() {
return self;
}
public void setSelf(String self) {
this.self = self;
}
public String getPublicKeyPem() { public String getPublicKeyPem() {
return publicKeyPem; return publicKeyPem;
} }
@ -97,28 +77,27 @@ public class PublishedRealmRepresentation {
this.publicKeyPem = PemUtils.removeBeginEnd(s); this.publicKeyPem = PemUtils.removeBeginEnd(s);
} }
public String getTokenServiceUrl() {
public String getAuthorizationUrl() { return tokenServiceUrl;
return authorizationUrl;
} }
public void setAuthorizationUrl(String authorizationUrl) { public void setTokenServiceUrl(String tokenServiceUrl) {
this.authorizationUrl = authorizationUrl; this.tokenServiceUrl = tokenServiceUrl;
} }
public String getCodeUrl() { public String getAccountServiceUrl() {
return codeUrl; return accountServiceUrl;
} }
public void setCodeUrl(String codeUrl) { public void setAccountServiceUrl(String accountServiceUrl) {
this.codeUrl = codeUrl; this.accountServiceUrl = accountServiceUrl;
} }
public String getGrantUrl() { public String getAdminApiUrl() {
return grantUrl; return adminApiUrl;
} }
public void setGrantUrl(String grantUrl) { public void setAdminApiUrl(String adminApiUrl) {
this.grantUrl = grantUrl; this.adminApiUrl = adminApiUrl;
} }
} }

View file

@ -48,6 +48,9 @@
<listitem> <listitem>
OAuth 2.0 Grant requests OAuth 2.0 Grant requests
</listitem> </listitem>
<listitem>
OpenID Connect Support.
</listitem>
<listitem> <listitem>
CORS Support CORS Support
</listitem> </listitem>

View file

@ -5,6 +5,7 @@ import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.keycloak.adapters.TokenGrantRequest; import org.keycloak.adapters.TokenGrantRequest;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.servlet.ServletOAuthClient; import org.keycloak.servlet.ServletOAuthClient;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
@ -50,7 +51,7 @@ public class ProductDatabaseClient {
static class TypedList extends ArrayList<String> {} static class TypedList extends ArrayList<String> {}
public static List<String> getProducts(HttpServletRequest request) throws Failure { public static AccessTokenResponse getTokenResponse(HttpServletRequest request) {
// The ServletOAuthClient is obtained by getting a context attribute // The ServletOAuthClient is obtained by getting a context attribute
// that is set in the Bootstrap context listener in this project. // that is set in the Bootstrap context listener in this project.
// You really should come up with a better way to initialize // You really should come up with a better way to initialize
@ -59,17 +60,26 @@ public class ProductDatabaseClient {
ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName()); ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
String token = null; String token = null;
try { try {
token = oAuthClient.getBearerToken(request).getToken(); return oAuthClient.getBearerToken(request);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} catch (TokenGrantRequest.HttpFailure failure) { } catch (TokenGrantRequest.HttpFailure failure) {
throw new RuntimeException(failure); throw new RuntimeException(failure);
} }
}
public static List<String> getProducts(HttpServletRequest request, String accessToken) throws Failure {
// The ServletOAuthClient is obtained by getting a context attribute
// that is set in the Bootstrap context listener in this project.
// You really should come up with a better way to initialize
// and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code
// and take a look how it works. You can also take a look at third-party-cdi example
ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
HttpClient client = oAuthClient.getClient(); HttpClient client = oAuthClient.getClient();
HttpGet get = new HttpGet("http://localhost:8080/database/products"); HttpGet get = new HttpGet("http://localhost:8080/database/products");
get.addHeader("Authorization", "Bearer " + token); get.addHeader("Authorization", "Bearer " + accessToken);
try { try {
HttpResponse response = client.execute(get); HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() != 200) { if (response.getStatusLine().getStatusCode() != 200) {

View file

@ -1,4 +1,7 @@
<%@ page import="org.keycloak.example.oauth.ProductDatabaseClient" %> <%@ page import="org.keycloak.example.oauth.ProductDatabaseClient" %>
<%@ page import="org.keycloak.representations.AccessTokenResponse" %>
<%@ page import="org.keycloak.representations.IDToken" %>
<%@ page import="org.keycloak.servlet.ServletOAuthClient" %>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%> pageEncoding="ISO-8859-1"%>
<html> <html>
@ -6,17 +9,33 @@
<title>Pull Page</title> <title>Pull Page</title>
</head> </head>
<body> <body>
<h2>Pulled Product Listing</h2>
<% <%
java.util.List<String> list = null; java.util.List<String> list = null;
try { try {
list = ProductDatabaseClient.getProducts(request); AccessTokenResponse tokenResponse = ProductDatabaseClient.getTokenResponse(request);
if (tokenResponse.getIdToken() != null) {
IDToken idToken = ServletOAuthClient.extractIdToken(tokenResponse.getIdToken());
out.println("<p><i>Change client claims in admin console to view personal info of user</i></p>");
if (idToken.getPreferredUsername() != null) {
out.println("<p>Username: " + idToken.getPreferredUsername() + "</p>");
}
if (idToken.getName() != null) {
out.println("<p>Full Name: " + idToken.getName() + "</p>");
}
if (idToken.getEmail() != null) {
out.println("<p>Email: " + idToken.getEmail() + "</p>");
}
}
list = ProductDatabaseClient.getProducts(request, tokenResponse.getToken());
} catch (ProductDatabaseClient.Failure failure) { } catch (ProductDatabaseClient.Failure failure) {
out.println("There was a failure processing request. You either didn't configure Keycloak properly, or maybe" + out.println("There was a failure processing request. You either didn't configure Keycloak properly, or maybe" +
"you just forgot to secure the database service?"); "you just forgot to secure the database service?");
out.println("Status from database service invocation was: " + failure.getStatus()); out.println("Status from database service invocation was: " + failure.getStatus());
return; return;
} }
%>
<h2>Pulled Product Listing</h2>
<%
for (String prod : list) for (String prod : list)
{ {
out.print("<p>"); out.print("<p>");

View file

@ -9,6 +9,16 @@
<div id="kc-oauth" class="content-area"> <div id="kc-oauth" class="content-area">
<h3><strong>${oauth.client}</strong> ${rb.oauthGrantRequest}</h3> <h3><strong>${oauth.client}</strong> ${rb.oauthGrantRequest}</h3>
<ul> <ul>
<#if oauth.claimsRequested??>
<li>
<span>
Personal Info:&nbsp;
<#list oauth.claimsRequested as claim>
${claim}&nbsp;
</#list>
</span>
</li>
</#if>
<#list oauth.realmRolesRequested as role> <#list oauth.realmRolesRequested as role>
<li> <li>
<span><#if role.description??>${role.description}<#else>${role.name}</#if></span> <span><#if role.description??>${role.description}<#else>${role.name}</#if></span>

View file

@ -21,12 +21,14 @@
*/ */
package org.keycloak.login.freemarker.model; package org.keycloak.login.freemarker.model;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List; import java.util.List;
/** /**
@ -38,6 +40,7 @@ public class OAuthGrantBean {
private MultivaluedMap<String, RoleModel> resourceRolesRequested; private MultivaluedMap<String, RoleModel> resourceRolesRequested;
private String code; private String code;
private ClientModel client; private ClientModel client;
private List<String> claimsRequested;
private String oAuthCode; private String oAuthCode;
private String action; private String action;
@ -46,6 +49,41 @@ public class OAuthGrantBean {
this.client = client; this.client = client;
this.realmRolesRequested = realmRolesRequested; this.realmRolesRequested = realmRolesRequested;
this.resourceRolesRequested = resourceRolesRequested; this.resourceRolesRequested = resourceRolesRequested;
// todo support locale
List<String> claims = new LinkedList<String>();
long mask = client.getAllowedClaimsMask();
if (ClaimMask.hasEmail(mask)) {
claims.add("email");
}
if (ClaimMask.hasUsername(mask)) {
claims.add("username");
}
if (ClaimMask.hasName(mask)) {
claims.add("name");
}
if (ClaimMask.hasGender(mask)) {
claims.add("gender");
}
if (ClaimMask.hasAddress(mask)) {
claims.add("address");
}
if (ClaimMask.hasPhone(mask)) {
claims.add("phone");
}
if (ClaimMask.hasPicture(mask)) {
claims.add("picture");
}
if (ClaimMask.hasProfile(mask)) {
claims.add("profile page");
}
if (ClaimMask.hasLocale(mask)) {
claims.add("locale");
}
if (ClaimMask.hasWebsite(mask)) {
claims.add("website");
}
if (claims.size() > 0) this.claimsRequested = claims;
} }
public String getCode() { public String getCode() {
@ -64,4 +102,7 @@ public class OAuthGrantBean {
return client.getClientId(); return client.getClientId();
} }
public List<String> getClaimsRequested() {
return claimsRequested;
}
} }

View file

@ -4,7 +4,9 @@ import org.apache.http.client.HttpClient;
import org.keycloak.AbstractOAuthClient; import org.keycloak.AbstractOAuthClient;
import org.keycloak.adapters.HttpClientBuilder; import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.adapters.TokenGrantRequest; import org.keycloak.adapters.TokenGrantRequest;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.util.KeycloakUriBuilder; import org.keycloak.util.KeycloakUriBuilder;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
@ -156,5 +158,15 @@ public class ServletOAuthClient extends AbstractOAuthClient {
return TokenGrantRequest.invokeRefresh(client, refreshToken, refreshUrl, clientId, credentials); return TokenGrantRequest.invokeRefresh(client, refreshToken, refreshUrl, clientId, credentials);
} }
public static IDToken extractIdToken(String idToken) {
if (idToken == null) return null;
JWSInput input = new JWSInput(idToken);
try {
return input.readJsonContent(IDToken.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
} }

View file

@ -51,10 +51,7 @@ public class OAuthClientManager {
model.setSecret(rep.getSecret()); model.setSecret(rep.getSecret());
if (rep.getClaims() != null) { if (rep.getClaims() != null) {
ClaimManager.setClaims(model, rep.getClaims()); ClaimManager.setClaims(model, rep.getClaims());
} else {
model.setAllowedClaimsMask(ClaimMask.USERNAME);
} }
return model; return model;
} }

View file

@ -74,6 +74,12 @@ public class AccountService {
this.authManager = new AppAuthManager("KEYCLOAK_ACCOUNT_IDENTITY", tokenManager); this.authManager = new AppAuthManager("KEYCLOAK_ACCOUNT_IDENTITY", tokenManager);
} }
public static UriBuilder accountServiceBaseUrl(UriInfo uriInfo) {
UriBuilder base = uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getAccountService");
return base;
}
private Response forwardToPage(String path, AccountPages page) { private Response forwardToPage(String path, AccountPages page) {
Auth auth = getAuth(false); Auth auth = getAuth(false);
if (auth != null) { if (auth != null) {

View file

@ -4,6 +4,7 @@ import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.logging.Logger; import org.jboss.resteasy.logging.Logger;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.PublishedRealmRepresentation; import org.keycloak.representations.idm.PublishedRealmRepresentation;
import org.keycloak.services.resources.admin.AdminService;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;
@ -19,7 +20,6 @@ import javax.ws.rs.core.UriInfo;
*/ */
public class PublicRealmResource { public class PublicRealmResource {
protected static final Logger logger = Logger.getLogger(PublicRealmResource.class); protected static final Logger logger = Logger.getLogger(PublicRealmResource.class);
public static final String ADMIN_ROLE = "$REALM-ADMIN$";
@Context @Context
protected UriInfo uriInfo; protected UriInfo uriInfo;
@ -30,12 +30,6 @@ public class PublicRealmResource {
this.realm = realm; this.realm = realm;
} }
public static UriBuilder realmUrl(UriInfo uriInfo) {
UriBuilder base = uriInfo.getBaseUriBuilder()
.path(RealmsResource.class).path(RealmsResource.class, "getRealmResource");
return base;
}
@GET @GET
@NoCache @NoCache
@Produces("application/json") @Produces("application/json")
@ -43,38 +37,13 @@ public class PublicRealmResource {
return realmRep(realm, uriInfo); return realmRep(realm, uriInfo);
} }
@GET
@NoCache
@Path("html")
@Produces("text/html")
public String getRealmHtml(@PathParam("realm") String id) {
StringBuffer html = new StringBuffer();
String authUri = TokenService.loginPageUrl(uriInfo).build(realm.getName()).toString();
String codeUri = TokenService.accessCodeToTokenUrl(uriInfo).build(realm.getName()).toString();
String grantUrl = TokenService.grantAccessTokenUrl(uriInfo).build(realm.getName()).toString();
html.append("<html><body><h1>Realm: ").append(realm.getName()).append("</h1>");
html.append("<p>auth: ").append(authUri).append("</p>");
html.append("<p>code: ").append(codeUri).append("</p>");
html.append("<p>grant: ").append(grantUrl).append("</p>");
html.append("<p>public key: ").append(realm.getPublicKeyPem()).append("</p>");
html.append("</body></html>");
return html.toString();
}
public static PublishedRealmRepresentation realmRep(RealmModel realm, UriInfo uriInfo) { public static PublishedRealmRepresentation realmRep(RealmModel realm, UriInfo uriInfo) {
PublishedRealmRepresentation rep = new PublishedRealmRepresentation(); PublishedRealmRepresentation rep = new PublishedRealmRepresentation();
rep.setRealm(realm.getName()); rep.setRealm(realm.getName());
rep.setSelf(realmUrl(uriInfo).build(realm.getId()).toString()); rep.setTokenServiceUrl(TokenService.tokenServiceBaseUrl(uriInfo).build(realm.getId()).toString());
rep.setAccountServiceUrl(AccountService.accountServiceBaseUrl(uriInfo).build(realm.getId()).toString());
rep.setAdminApiUrl(AdminService.adminApiUrl(uriInfo).build(realm.getId()).toString());
rep.setPublicKeyPem(realm.getPublicKeyPem()); rep.setPublicKeyPem(realm.getPublicKeyPem());
rep.setAdminRole(ADMIN_ROLE);
rep.setAuthorizationUrl(TokenService.loginPageUrl(uriInfo).build(realm.getName()).toString());
rep.setCodeUrl(TokenService.accessCodeToTokenUrl(uriInfo).build(realm.getName()).toString());
rep.setGrantUrl(TokenService.grantAccessTokenUrl(uriInfo).build(realm.getName()).toString());
return rep; return rep;
} }

View file

@ -17,6 +17,7 @@ import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.Auth; import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager; import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.TokenService; import org.keycloak.services.resources.TokenService;
import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.Flows;
@ -75,6 +76,12 @@ public class AdminService {
this.authManager = new AppAuthManager("KEYCLOAK_ADMIN_CONSOLE_IDENTITY", tokenManager); this.authManager = new AppAuthManager("KEYCLOAK_ADMIN_CONSOLE_IDENTITY", tokenManager);
} }
public static UriBuilder adminApiUrl(UriInfo uriInfo) {
UriBuilder base = uriInfo.getBaseUriBuilder().path(AdminService.class).path(AdminService.class, "getRealmsAdmin").path(RealmsAdminResource.class, "getRealmAdmin");
return base;
}
public static class WhoAmI { public static class WhoAmI {
protected String userId; protected String userId;
protected String displayName; protected String displayName;