diff --git a/core/src/main/java/org/keycloak/representations/idm/PublishedRealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/PublishedRealmRepresentation.java index 4f1f2ea688..20cc452e2f 100755 --- a/core/src/main/java/org/keycloak/representations/idm/PublishedRealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/PublishedRealmRepresentation.java @@ -15,34 +15,22 @@ import java.security.PublicKey; */ public class PublishedRealmRepresentation { protected String realm; - protected String self; @JsonProperty("public_key") protected String publicKeyPem; - @JsonProperty("authorization") - protected String authorizationUrl; + @JsonProperty("token-service") + protected String tokenServiceUrl; - @JsonProperty("codes") - protected String codeUrl; + @JsonProperty("account-service") + protected String accountServiceUrl; - @JsonProperty("grants") - protected String grantUrl; - - @JsonProperty("admin-role") - protected String adminRole; + @JsonProperty("admin-api") + protected String adminApiUrl; @JsonIgnore protected volatile transient PublicKey publicKey; - public String getAdminRole() { - return adminRole; - } - - public void setAdminRole(String adminRole) { - this.adminRole = adminRole; - } - public String getRealm() { return realm; } @@ -51,14 +39,6 @@ public class PublishedRealmRepresentation { this.realm = realm; } - public String getSelf() { - return self; - } - - public void setSelf(String self) { - this.self = self; - } - public String getPublicKeyPem() { return publicKeyPem; } @@ -97,28 +77,27 @@ public class PublishedRealmRepresentation { this.publicKeyPem = PemUtils.removeBeginEnd(s); } - - public String getAuthorizationUrl() { - return authorizationUrl; + public String getTokenServiceUrl() { + return tokenServiceUrl; } - public void setAuthorizationUrl(String authorizationUrl) { - this.authorizationUrl = authorizationUrl; + public void setTokenServiceUrl(String tokenServiceUrl) { + this.tokenServiceUrl = tokenServiceUrl; } - public String getCodeUrl() { - return codeUrl; + public String getAccountServiceUrl() { + return accountServiceUrl; } - public void setCodeUrl(String codeUrl) { - this.codeUrl = codeUrl; + public void setAccountServiceUrl(String accountServiceUrl) { + this.accountServiceUrl = accountServiceUrl; } - public String getGrantUrl() { - return grantUrl; + public String getAdminApiUrl() { + return adminApiUrl; } - public void setGrantUrl(String grantUrl) { - this.grantUrl = grantUrl; + public void setAdminApiUrl(String adminApiUrl) { + this.adminApiUrl = adminApiUrl; } } diff --git a/docbook/reference/en/en-US/modules/Overview.xml b/docbook/reference/en/en-US/modules/Overview.xml index 3910cea1aa..980c58112b 100755 --- a/docbook/reference/en/en-US/modules/Overview.xml +++ b/docbook/reference/en/en-US/modules/Overview.xml @@ -48,6 +48,9 @@ OAuth 2.0 Grant requests + + OpenID Connect Support. + CORS Support diff --git a/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java b/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java index 9ab51b2279..f848909db8 100755 --- a/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java +++ b/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java @@ -5,6 +5,7 @@ import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.keycloak.adapters.TokenGrantRequest; +import org.keycloak.representations.AccessTokenResponse; import org.keycloak.servlet.ServletOAuthClient; import org.keycloak.util.JsonSerialization; @@ -50,7 +51,7 @@ public class ProductDatabaseClient { static class TypedList extends ArrayList {} - public static List getProducts(HttpServletRequest request) throws Failure { + public static AccessTokenResponse getTokenResponse(HttpServletRequest request) { // 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 @@ -59,17 +60,26 @@ public class ProductDatabaseClient { ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName()); String token = null; try { - token = oAuthClient.getBearerToken(request).getToken(); + return oAuthClient.getBearerToken(request); } catch (IOException e) { throw new RuntimeException(e); } catch (TokenGrantRequest.HttpFailure failure) { throw new RuntimeException(failure); } + } + + public static List 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(); HttpGet get = new HttpGet("http://localhost:8080/database/products"); - get.addHeader("Authorization", "Bearer " + token); + get.addHeader("Authorization", "Bearer " + accessToken); try { HttpResponse response = client.execute(get); if (response.getStatusLine().getStatusCode() != 200) { diff --git a/examples/demo-template/third-party/src/main/webapp/pull_data.jsp b/examples/demo-template/third-party/src/main/webapp/pull_data.jsp index 0ccfbcb16b..6d998b739f 100755 --- a/examples/demo-template/third-party/src/main/webapp/pull_data.jsp +++ b/examples/demo-template/third-party/src/main/webapp/pull_data.jsp @@ -1,4 +1,7 @@ <%@ 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" pageEncoding="ISO-8859-1"%> @@ -6,17 +9,33 @@ Pull Page -

Pulled Product Listing

<% java.util.List list = null; try { - list = ProductDatabaseClient.getProducts(request); + AccessTokenResponse tokenResponse = ProductDatabaseClient.getTokenResponse(request); + if (tokenResponse.getIdToken() != null) { + IDToken idToken = ServletOAuthClient.extractIdToken(tokenResponse.getIdToken()); + out.println("

Change client claims in admin console to view personal info of user

"); + if (idToken.getPreferredUsername() != null) { + out.println("

Username: " + idToken.getPreferredUsername() + "

"); + } + if (idToken.getName() != null) { + out.println("

Full Name: " + idToken.getName() + "

"); + } + if (idToken.getEmail() != null) { + out.println("

Email: " + idToken.getEmail() + "

"); + } + } + list = ProductDatabaseClient.getProducts(request, tokenResponse.getToken()); } catch (ProductDatabaseClient.Failure failure) { 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?"); out.println("Status from database service invocation was: " + failure.getStatus()); return; } +%> +

Pulled Product Listing

+<% for (String prod : list) { out.print("

"); diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl index 5ca5a027f5..a44b2c7dc5 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl @@ -9,6 +9,16 @@

${oauth.client} ${rb.oauthGrantRequest}

    + <#if oauth.claimsRequested??> +
  • + + Personal Info:  + <#list oauth.claimsRequested as claim> + ${claim}  + + +
  • + <#list oauth.realmRolesRequested as role>
  • <#if role.description??>${role.description}<#else>${role.name} diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java index 94fdf8f42c..664b25bfdc 100755 --- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java +++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java @@ -21,12 +21,14 @@ */ package org.keycloak.login.freemarker.model; +import org.keycloak.models.ClaimMask; import org.keycloak.models.ClientModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import javax.ws.rs.core.MultivaluedMap; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; /** @@ -38,6 +40,7 @@ public class OAuthGrantBean { private MultivaluedMap resourceRolesRequested; private String code; private ClientModel client; + private List claimsRequested; private String oAuthCode; private String action; @@ -46,6 +49,41 @@ public class OAuthGrantBean { this.client = client; this.realmRolesRequested = realmRolesRequested; this.resourceRolesRequested = resourceRolesRequested; + + // todo support locale + List claims = new LinkedList(); + 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() { @@ -64,4 +102,7 @@ public class OAuthGrantBean { return client.getClientId(); } + public List getClaimsRequested() { + return claimsRequested; + } } diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java index 44c0b9c25f..365d4690db 100755 --- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java +++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java @@ -4,7 +4,9 @@ import org.apache.http.client.HttpClient; import org.keycloak.AbstractOAuthClient; import org.keycloak.adapters.HttpClientBuilder; import org.keycloak.adapters.TokenGrantRequest; +import org.keycloak.jose.jws.JWSInput; import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.representations.IDToken; import org.keycloak.util.KeycloakUriBuilder; import javax.servlet.http.Cookie; @@ -156,5 +158,15 @@ public class ServletOAuthClient extends AbstractOAuthClient { 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); + } + } + } diff --git a/services/src/main/java/org/keycloak/services/managers/OAuthClientManager.java b/services/src/main/java/org/keycloak/services/managers/OAuthClientManager.java index c8e7a844e5..7ed6f05f7c 100755 --- a/services/src/main/java/org/keycloak/services/managers/OAuthClientManager.java +++ b/services/src/main/java/org/keycloak/services/managers/OAuthClientManager.java @@ -51,10 +51,7 @@ public class OAuthClientManager { model.setSecret(rep.getSecret()); if (rep.getClaims() != null) { ClaimManager.setClaims(model, rep.getClaims()); - } else { - model.setAllowedClaimsMask(ClaimMask.USERNAME); } - return model; } diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 19f00712e1..3814691e70 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -74,6 +74,12 @@ public class AccountService { 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) { Auth auth = getAuth(false); if (auth != null) { diff --git a/services/src/main/java/org/keycloak/services/resources/PublicRealmResource.java b/services/src/main/java/org/keycloak/services/resources/PublicRealmResource.java index 944f1a99bf..81f12a2432 100755 --- a/services/src/main/java/org/keycloak/services/resources/PublicRealmResource.java +++ b/services/src/main/java/org/keycloak/services/resources/PublicRealmResource.java @@ -4,6 +4,7 @@ import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.logging.Logger; import org.keycloak.models.RealmModel; import org.keycloak.representations.idm.PublishedRealmRepresentation; +import org.keycloak.services.resources.admin.AdminService; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -19,7 +20,6 @@ import javax.ws.rs.core.UriInfo; */ public class PublicRealmResource { protected static final Logger logger = Logger.getLogger(PublicRealmResource.class); - public static final String ADMIN_ROLE = "$REALM-ADMIN$"; @Context protected UriInfo uriInfo; @@ -30,12 +30,6 @@ public class PublicRealmResource { this.realm = realm; } - public static UriBuilder realmUrl(UriInfo uriInfo) { - UriBuilder base = uriInfo.getBaseUriBuilder() - .path(RealmsResource.class).path(RealmsResource.class, "getRealmResource"); - return base; - } - @GET @NoCache @Produces("application/json") @@ -43,38 +37,13 @@ public class PublicRealmResource { 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("

    Realm: ").append(realm.getName()).append("

    "); - html.append("

    auth: ").append(authUri).append("

    "); - html.append("

    code: ").append(codeUri).append("

    "); - html.append("

    grant: ").append(grantUrl).append("

    "); - html.append("

    public key: ").append(realm.getPublicKeyPem()).append("

    "); - html.append(""); - - return html.toString(); - } - - public static PublishedRealmRepresentation realmRep(RealmModel realm, UriInfo uriInfo) { PublishedRealmRepresentation rep = new PublishedRealmRepresentation(); 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.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; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminService.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminService.java index 0b1aa128b6..afe2317b41 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminService.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminService.java @@ -17,6 +17,7 @@ import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.Auth; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.TokenManager; +import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.TokenService; import org.keycloak.services.resources.flows.Flows; @@ -75,6 +76,12 @@ public class AdminService { 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 { protected String userId; protected String displayName;