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 b22f991861..ba0864fc34 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -17,6 +17,7 @@ package org.keycloak.protocol.oidc; +import java.util.HashMap; import org.jboss.logging.Logger; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.OAuth2Constants; @@ -657,6 +658,31 @@ public class TokenManager { return finalToken.get(); } + public Map generateUserInfoClaims(AccessToken userInfo, UserModel userModel) { + Map claims = new HashMap<>(); + claims.put("sub", userModel.getId()); + claims.putAll(userInfo.getOtherClaims()); + + if (userInfo.getRealmAccess() != null) { + Map> realmAccess = new HashMap<>(); + realmAccess.put("roles", userInfo.getRealmAccess().getRoles()); + claims.put("realm_access", realmAccess); + } + + if (userInfo.getResourceAccess() != null && !userInfo.getResourceAccess().isEmpty()) { + Map>> resourceAccessMap = new HashMap<>(); + + for (Map.Entry resourceAccessMapEntry : userInfo.getResourceAccess() + .entrySet()) { + Map> resourceAccess = new HashMap<>(); + resourceAccess.put("roles", resourceAccessMapEntry.getValue().getRoles()); + resourceAccessMap.put(resourceAccessMapEntry.getKey(), resourceAccess); + } + claims.put("resource_access", resourceAccessMap); + } + return claims; + } + public void transformIDToken(KeycloakSession session, IDToken token, UserSessionModel userSession, ClientSessionContext clientSessionCtx) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java index bde802b5d9..4d349e945b 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java @@ -69,9 +69,7 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import java.util.Collections; -import java.util.HashMap; import java.util.Map; -import java.util.Set; /** * @author pedroigor @@ -229,28 +227,7 @@ public class UserInfoEndpoint { AccessToken userInfo = new AccessToken(); tokenManager.transformUserInfoAccessToken(session, userInfo, userSession, clientSessionCtx); - - Map claims = new HashMap<>(); - claims.put("sub", userModel.getId()); - claims.putAll(userInfo.getOtherClaims()); - - if (userInfo.getRealmAccess() != null) { - Map> realmAccess = new HashMap<>(); - realmAccess.put("roles", userInfo.getRealmAccess().getRoles()); - claims.put("realm_access", realmAccess); - } - - if (userInfo.getResourceAccess() != null && !userInfo.getResourceAccess().isEmpty()) { - Map>> resourceAccessMap = new HashMap<>(); - - for (Map.Entry resourceAccessMapEntry : userInfo.getResourceAccess() - .entrySet()) { - Map> resourceAccess = new HashMap<>(); - resourceAccess.put("roles", resourceAccessMapEntry.getValue().getRoles()); - resourceAccessMap.put(resourceAccessMapEntry.getKey(), resourceAccess); - } - claims.put("resource_access", resourceAccessMap); - } + Map claims = tokenManager.generateUserInfoClaims(userInfo, userModel); Response.ResponseBuilder responseBuilder; OIDCAdvancedConfigWrapper cfg = OIDCAdvancedConfigWrapper.fromClientModel(clientModel); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateResource.java index c007f73e52..0fed4cdbf8 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientScopeEvaluateResource.java @@ -19,7 +19,9 @@ package org.keycloak.services.resources.admin; import static org.keycloak.protocol.ProtocolMapperUtils.isEnabled; +import java.util.Map; import java.util.Objects; +import java.util.function.BiFunction; import java.util.stream.Stream; import javax.ws.rs.GET; @@ -47,6 +49,7 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.representations.AccessToken; +import org.keycloak.representations.IDToken; import org.keycloak.services.Urls; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationSessionManager; @@ -144,6 +147,55 @@ public class ClientScopeEvaluateResource { return rep; } + /** + * Create JSON with payload of example user info + * + * @return + */ + @GET + @Path("generate-example-userinfo") + @NoCache + @Produces(MediaType.APPLICATION_JSON) + public Map generateExampleUserinfo(@QueryParam("scope") String scopeParam, @QueryParam("userId") String userId) { + auth.clients().requireView(client); + + UserModel user = getUserModel(userId); + + logger.debugf("generateExampleUserinfo invoked. User: %s", user.getUsername()); + + return sessionAware(user, scopeParam, (userSession, clientSessionCtx) -> { + AccessToken userInfo = new AccessToken(); + TokenManager tokenManager = new TokenManager(); + + tokenManager.transformUserInfoAccessToken(session, userInfo, userSession, clientSessionCtx); + return tokenManager.generateUserInfoClaims(userInfo, user); + }); + } + + /** + * Create JSON with payload of example id token + * + * @return + */ + @GET + @Path("generate-example-id-token") + @NoCache + @Produces(MediaType.APPLICATION_JSON) + public IDToken generateExampleIdToken(@QueryParam("scope") String scopeParam, @QueryParam("userId") String userId) { + auth.clients().requireView(client); + + UserModel user = getUserModel(userId); + + logger.debugf("generateExampleIdToken invoked. User: %s, Scope param: %s", user.getUsername(), scopeParam); + + return sessionAware(user, scopeParam, (userSession, clientSessionCtx) -> + { + TokenManager tokenManager = new TokenManager(); + return tokenManager.responseBuilder(realm, client, null, session, userSession, clientSessionCtx) + .generateAccessToken().generateIDToken().getIdToken(); + }); + } + /** * Create JSON with payload of example access token * @@ -156,25 +208,20 @@ public class ClientScopeEvaluateResource { public AccessToken generateExampleAccessToken(@QueryParam("scope") String scopeParam, @QueryParam("userId") String userId) { auth.clients().requireView(client); - if (userId == null) { - throw new NotFoundException("No userId provided"); - } - - UserModel user = session.users().getUserById(realm, userId); - if (user == null) { - throw new NotFoundException("No user found"); - } + UserModel user = getUserModel(userId); logger.debugf("generateExampleAccessToken invoked. User: %s, Scope param: %s", user.getUsername(), scopeParam); - AccessToken token = generateToken(user, scopeParam); - return token; + return sessionAware(user, scopeParam, (userSession, clientSessionCtx) -> + { + TokenManager tokenManager = new TokenManager(); + return tokenManager.responseBuilder(realm, client, null, session, userSession, clientSessionCtx) + .generateAccessToken().getAccessToken(); + }); } - - private AccessToken generateToken(UserModel user, String scopeParam) { + private R sessionAware(UserModel user, String scopeParam, BiFunction function) { AuthenticationSessionModel authSession = null; - UserSessionModel userSession = null; AuthenticationSessionManager authSessionManager = new AuthenticationSessionManager(session); try { @@ -186,18 +233,13 @@ public class ClientScopeEvaluateResource { authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName())); authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scopeParam); - userSession = session.sessions().createUserSession(authSession.getParentSession().getId(), realm, user, user.getUsername(), + UserSessionModel userSession = session.sessions().createUserSession(authSession.getParentSession().getId(), realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "example-auth", false, null, null, UserSessionModel.SessionPersistenceState.TRANSIENT); AuthenticationManager.setClientScopesInSession(authSession); ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(session, userSession, authSession); - TokenManager tokenManager = new TokenManager(); - - TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, client, null, session, userSession, clientSessionCtx) - .generateAccessToken(); - - return responseBuilder.getAccessToken(); + return function.apply(userSession, clientSessionCtx); } finally { if (authSession != null) { @@ -206,6 +248,17 @@ public class ClientScopeEvaluateResource { } } + private UserModel getUserModel(String userId) { + if (userId == null) { + throw new NotFoundException("No userId provided"); + } + + UserModel user = session.users().getUserById(realm, userId); + if (user == null) { + throw new NotFoundException("No user found"); + } + return user; + } public static class ProtocolMapperEvaluationRepresentation { diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index ccae25041a..0b03ae6b06 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -1044,7 +1044,11 @@ client-scopes.evaluate.granted-realm-effective-roles=Granted Effective Realm Rol client-scopes.evaluate.granted-realm-effective-roles.tooltip=Client has scope mappings for these roles. Those roles will be in the access token issued to this client if the authenticated user is a member of them client-scopes.evaluate.granted-client-effective-roles=Granted Effective Client Roles generated-access-token=Generated Access Token -generated-access-token.tooltip=See the example token, which will be generated and sent to the client when selected user is authenticated. You can see claims and roles that the token will contain based on the effective protocol mappers and role scope mappings and also based on the claims/roles assigned to user himself +generated-access-token.tooltip=See the example access token, which will be generated and sent to the client when selected user is authenticated. You can see claims and roles that the token will contain based on the effective protocol mappers and role scope mappings and also based on the claims/roles assigned to user himself +generated-id-token=Generated ID Token +generated-id-token.tooltip=See the example ID Token, which will be generated and sent to the client when selected user is authenticated. You can see claims and roles that the token will contain based on the effective protocol mappers and role scope mappings and also based on the claims/roles assigned to user himself +generated-user-info=Generated User Info +generated-user-info.tooltip=See the example User Info, which will be provided by the User Info Endpoint manage=Manage authentication=Authentication diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index 16e779163a..04f68ea461 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -2573,8 +2573,9 @@ module.controller('ClientClientScopesSetupCtrl', function($scope, realm, Realm, }); module.controller('ClientClientScopesEvaluateCtrl', function($scope, Realm, User, ClientEvaluateProtocolMappers, ClientEvaluateGrantedRoles, - ClientEvaluateNotGrantedRoles, ClientEvaluateGenerateExampleToken, realm, client, clients, clientScopes, serverInfo, - ComponentUtils, clientOptionalClientScopes, clientDefaultClientScopes, $route, $routeParams, $http, Notifications, $location, + ClientEvaluateNotGrantedRoles, ClientEvaluateGenerateExampleAccessToken, ClientEvaluateGenerateExampleIDToken, + ClientEvaluateGenerateExampleUserInfo, realm, client, clients, clientScopes, serverInfo, ComponentUtils, + clientOptionalClientScopes, clientDefaultClientScopes, $route, $routeParams, $http, Notifications, $location, Client) { console.log('ClientClientScopesEvaluateCtrl'); @@ -2610,6 +2611,8 @@ module.controller('ClientClientScopesEvaluateCtrl', function($scope, Realm, User $scope.notGrantedClientRoles = null; $scope.targetClient = null; $scope.oidcAccessToken = null; + $scope.oidcIDToken = null; + $scope.oidcUserInfo = null; $scope.selectedTab = 0; } @@ -2743,49 +2746,75 @@ module.controller('ClientClientScopesEvaluateCtrl', function($scope, Realm, User // Send request for retrieve accessToken (in case user was selected) if (client.protocol === 'openid-connect' && $scope.userId != null && $scope.userId !== '') { - var url = ClientEvaluateGenerateExampleToken.url({ + var exampleRequestParams = { realm: realm.realm, client: client.id, userId: $scope.userId, scopeParam: $scope.scopeParam + }; + + var accessTokenUrl = ClientEvaluateGenerateExampleAccessToken.url(exampleRequestParams); + getPrettyJsonResponse(accessTokenUrl).then(function (result) { + $scope.oidcAccessToken = result; }); - $http.get(url).then(function (response) { - if (response.data) { - var oidcAccessToken = angular.fromJson(response.data); - oidcAccessToken = angular.toJson(oidcAccessToken, true); - $scope.oidcAccessToken = oidcAccessToken; - } else { - $scope.oidcAccessToken = null; - } + var idTokenUrl = ClientEvaluateGenerateExampleIDToken.url(exampleRequestParams); + getPrettyJsonResponse(idTokenUrl).then(function (result) { + $scope.oidcIDToken = result; + }); + + var userInfoUrl = ClientEvaluateGenerateExampleUserInfo.url(exampleRequestParams); + getPrettyJsonResponse(userInfoUrl).then(function (result) { + $scope.oidcUserInfo = result; }); } $scope.showTab(1); }; + function getPrettyJsonResponse(url) { + return $http.get(url).then(function (response) { + if (response.data) { + var responseJson = angular.fromJson(response.data); + return angular.toJson(responseJson, true); + } else { + return null; + } + }); + } $scope.isResponseAvailable = function () { return $scope.protocolMappers != null; } - $scope.isTokenAvailable = function () { + $scope.isAccessTokenAvailable = function () { return $scope.oidcAccessToken != null; } + $scope.isIDTokenAvailable = function () { + return $scope.oidcIDToken != null; + } + + $scope.isUserInfoAvailable = function () { + return $scope.oidcUserInfo != null; + } + $scope.showTab = function (tab) { $scope.selectedTab = tab; - // Check if there is more clever way to do it... :/ - if (tab === 1) { - $scope.tabCss = { tab1: 'active', tab2: '', tab3: '' } - } else if (tab === 2) { - $scope.tabCss = { tab1: '', tab2: 'active', tab3: '' } - } else if (tab === 3) { - $scope.tabCss = { tab1: '', tab2: '', tab3: 'active' } + $scope.tabCss = { + tab1: getTabCssClass(1, tab), + tab2: getTabCssClass(2, tab), + tab3: getTabCssClass(3, tab), + tab4: getTabCssClass(4, tab), + tab5: getTabCssClass(5, tab) } } + function getTabCssClass(tabNo, selectedTab) { + return (tabNo === selectedTab) ? 'active' : ''; + } + $scope.protocolMappersShown = function () { return $scope.selectedTab === 1; } @@ -2794,8 +2823,17 @@ module.controller('ClientClientScopesEvaluateCtrl', function($scope, Realm, User return $scope.selectedTab === 2; } - $scope.tokenShown = function () { - return $scope.selectedTab === 3; + $scope.exampleTabInfo = function() { + switch ($scope.selectedTab) { + case 3: + return { isShown: true, value: $scope.oidcAccessToken} + case 4: + return { isShown: true, value: $scope.oidcIDToken} + case 5: + return { isShown: true, value: $scope.oidcUserInfo} + default: + return { isShown: false, value: null} + } } $scope.sortMappersByPriority = function(mapper) { diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js index 08bd07e9e1..87a5b5098e 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -1239,19 +1239,30 @@ module.factory('ClientEvaluateNotGrantedRoles', function($resource) { }); }); -module.factory('ClientEvaluateGenerateExampleToken', function($resource) { - var url = authUrl + '/admin/realms/:realm/clients/:client/evaluate-scopes/generate-example-access-token?scope=:scopeParam&userId=:userId'; +module.factory('ClientEvaluateGenerateExampleAccessToken', function($resource) { + return buildClientEvaluateGenerateExampleUrl('generate-example-access-token'); +}); + +module.factory('ClientEvaluateGenerateExampleIDToken', function($resource) { + return buildClientEvaluateGenerateExampleUrl('generate-example-id-token'); +}); + +module.factory('ClientEvaluateGenerateExampleUserInfo', function($resource) { + return buildClientEvaluateGenerateExampleUrl('generate-example-userinfo'); +}); + +function buildClientEvaluateGenerateExampleUrl(subPath) { + var urlTemplate = authUrl + '/admin/realms/:realm/clients/:client/evaluate-scopes/' + subPath + '?scope=:scopeParam&userId=:userId'; return { - url : function(parameters) - { - return url + url: function (parameters) { + return urlTemplate .replace(':realm', parameters.realm) .replace(':client', parameters.client) .replace(':scopeParam', parameters.scopeParam) .replace(':userId', parameters.userId); } } -}); +} module.factory('ClientProtocolMappersByProtocol', function($resource) { return $resource(authUrl + '/admin/realms/:realm/clients/:client/protocol-mappers/protocol/:protocol', { diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-scopes-evaluate.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-scopes-evaluate.html index cd8785ebb4..e8702ad2a7 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-scopes-evaluate.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-scopes-evaluate.html @@ -126,10 +126,18 @@ {{:: 'evaluated-roles' | translate}} {{:: 'evaluated-roles.tooltip' | translate}} -
  • +
  • {{:: 'generated-access-token' | translate}} {{:: 'generated-access-token.tooltip' | translate}}
  • +
  • + {{:: 'generated-id-token' | translate}} + {{:: 'generated-id-token.tooltip' | translate}} +
  • +
  • + {{:: 'generated-user-info' | translate}} + {{:: 'generated-user-info.tooltip' | translate}} +
  • @@ -246,11 +254,11 @@ - -
    + +
    -
    - +
    +