From a7c563b0eb32cb8506af33db9133f5a37b71c672 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 17 Apr 2015 16:41:04 -0400 Subject: [PATCH] oidc broker role mapper --- .../AbstractIdentityProviderMapper.java | 31 +++ .../oidc/KeycloakOIDCIdentityProvider.java | 11 + .../broker/oidc/OIDCIdentityProvider.java | 47 ++-- .../broker/oidc/mappers/RoleMapper.java | 230 ++++++++++++++++++ ...oak.broker.provider.IdentityProviderMapper | 1 + ...ntityProviderMapperTypeRepresentation.java | 3 +- .../java/org/keycloak/JsonParserTest.java | 28 +++ .../theme/base/admin/resources/js/app.js | 52 ++++ .../admin/resources/js/controllers/clients.js | 48 ++-- .../admin/resources/js/controllers/realm.js | 97 +++++++- .../theme/base/admin/resources/js/loaders.js | 31 ++- .../theme/base/admin/resources/js/services.js | 25 +- .../identity-provider-mapper-detail.html | 86 +++++++ .../partials/identity-provider-mappers.html | 46 ++++ .../realm-identity-provider-oidc.html | 1 + .../admin/IdentityProviderResource.java | 8 +- .../testsuite/account/AccountTest.java | 6 + 17 files changed, 701 insertions(+), 50 deletions(-) create mode 100755 broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java create mode 100755 broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/RoleMapper.java create mode 100755 broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper create mode 100755 forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mapper-detail.html create mode 100755 forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mappers.html diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java new file mode 100755 index 0000000000..15b83e8643 --- /dev/null +++ b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java @@ -0,0 +1,31 @@ +package org.keycloak.broker.provider; + +import org.keycloak.broker.provider.IdentityProviderMapper; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public abstract class AbstractIdentityProviderMapper implements IdentityProviderMapper { + @Override + public void close() { + + } + + @Override + public IdentityProviderMapper create(KeycloakSession session) { + return null; + } + + @Override + public void init(org.keycloak.Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } +} diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java index 14626c5759..479422eb08 100755 --- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java +++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java @@ -1,11 +1,14 @@ package org.keycloak.broker.oidc; import org.keycloak.broker.oidc.util.SimpleHttp; +import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.constants.AdapterConstants; import org.keycloak.events.EventBuilder; import org.keycloak.jose.jws.JWSInput; import org.keycloak.models.RealmModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.adapters.action.AdminAction; import org.keycloak.representations.adapters.action.LogoutAction; import org.keycloak.services.managers.AuthenticationManager; @@ -23,6 +26,8 @@ import java.security.PublicKey; */ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider { + public static final String VALIDATED_ACCESS_TOKEN = "VALIDATED_ACCESS_TOKEN"; + public KeycloakOIDCIdentityProvider(OIDCIdentityProviderConfig config) { super(config); } @@ -32,6 +37,12 @@ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider { return new KeycloakEndpoint(callback, realm, event); } + @Override + protected void processAccessTokenResponse(BrokeredIdentityContext context, PublicKey idpKey, AccessTokenResponse response) { + JsonWebToken access = validateToken(idpKey, response.getToken()); + context.getContextData().put(VALIDATED_ACCESS_TOKEN, access); + } + protected class KeycloakEndpoint extends OIDCEndpoint { public KeycloakEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) { super(callback, realm, event); diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java index 2d0c16b13c..745e16dad1 100755 --- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java +++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java @@ -95,13 +95,6 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProviderBill Burke + * @version $Revision: 1 $ + */ +public class RoleMapper extends AbstractIdentityProviderMapper { + + public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID}; + + private static final List configProperties = new ArrayList(); + + public static final String ROLE = "role"; + public static final String CLAIM = "claim"; + + public static final String ID_TOKEN_CLAIM = "id.token.claim"; + + public static final String ACCESS_TOKEN_CLAIM = "access.token.claim"; + + public static final String CLAIM_VALUE = "claim.value"; + + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(); + property.setName(CLAIM); + property.setLabel("Claim"); + property.setHelpText("Name of claim to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'."); + property.setType(ProviderConfigProperty.STRING_TYPE); + configProperties.add(property); + property = new ProviderConfigProperty(); + property.setName(CLAIM_VALUE); + property.setLabel("Claim Value"); + property.setHelpText("Value the claim must have. If the claim is an array, then the value must be contained in the array."); + property.setType(ProviderConfigProperty.STRING_TYPE); + configProperties.add(property); + property = new ProviderConfigProperty(); + property.setName(ID_TOKEN_CLAIM); + property.setLabel("ID Token Claim"); + property.setType(ProviderConfigProperty.BOOLEAN_TYPE); + property.setDefaultValue("true"); + property.setHelpText("If this claim is in ID Token, apply role."); + configProperties.add(property); + property = new ProviderConfigProperty(); + property.setName(ACCESS_TOKEN_CLAIM); + property.setLabel("Access Token Claim"); + property.setType(ProviderConfigProperty.BOOLEAN_TYPE); + property.setDefaultValue("true"); + property.setHelpText("If this claim is in Access Token, apply role."); + configProperties.add(property); + property = new ProviderConfigProperty(); + property.setName(ROLE); + property.setLabel("Role"); + property.setHelpText("Role to grant to user. To reference an application role the syntax is appname.approle, i.e. myapp.myrole"); + property.setType(ProviderConfigProperty.STRING_TYPE); + configProperties.add(property); + } + + public static final String PROVIDER_ID = "oidc-role-idp-mapper"; + + public static String[] parseRole(String role) { + int scopeIndex = role.indexOf('.'); + if (scopeIndex > -1) { + String appName = role.substring(0, scopeIndex); + role = role.substring(scopeIndex + 1); + String[] rtn = {appName, role}; + return rtn; + } else { + String[] rtn = {null, role}; + return rtn; + + } + } + + public static Object getClaimValue(JsonWebToken token, String claim) { + String[] split = claim.split("\\."); + Map jsonObject = token.getOtherClaims(); + for (int i = 0; i < split.length; i++) { + if (i == split.length - 1) { + return jsonObject.get(split[i]); + } else { + Object val = jsonObject.get(split[i]); + if (!(val instanceof Map)) return null; + jsonObject = (Map)val; + } + } + return null; + } + + + + + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String[] getCompatibleProviders() { + return COMPATIBLE_PROVIDERS; + } + + @Override + public String getDisplayCategory() { + return "Role Mapper"; + } + + @Override + public String getDisplayType() { + return "Role Mapper"; + } + + @Override + public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + String roleName = mapperModel.getConfig().get(ROLE); + if (isClaimPresent(mapperModel, context)) { + RoleModel role = getRoleFromString(realm, roleName); + if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName); + user.grantRole(role); + } + } + + protected RoleModel getRoleFromString(RealmModel realm, String roleName) { + String[] parsedRole = parseRole(roleName); + RoleModel role = null; + if (parsedRole[0] == null) { + role = realm.getRole(parsedRole[1]); + } else { + ClientModel client = realm.getClientByClientId(parsedRole[0]); + role = client.getRole(parsedRole[1]); + } + return role; + } + + protected boolean isClaimPresent(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + boolean searchAccess = Boolean.valueOf(mapperModel.getConfig().get(ACCESS_TOKEN_CLAIM)); + boolean searchId = Boolean.valueOf(mapperModel.getConfig().get(ID_TOKEN_CLAIM)); + String claim = mapperModel.getConfig().get(CLAIM); + String desiredValue = mapperModel.getConfig().get(CLAIM_VALUE); + + if (searchAccess) { + JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN); + if (token != null) { + Object value = getClaimValue(token, claim); + if (valueEquals(desiredValue, value)) return true; + } + + } + if (searchId) { + JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ID_TOKEN); + if (token != null) { + Object value = getClaimValue(token, claim); + if (valueEquals(desiredValue, value)) return true; + } + + } + return false; + } + + public boolean valueEquals(String desiredValue, Object value) { + if (value instanceof String) { + if (desiredValue.equals(value)) return true; + } else if (value instanceof Double) { + try { + if (Double.valueOf(desiredValue).equals(value)) return true; + } catch (Exception e) { + + } + } else if (value instanceof Integer) { + try { + if (Integer.valueOf(desiredValue).equals(value)) return true; + } catch (Exception e) { + + } + } else if (value instanceof Boolean) { + try { + if (Boolean.valueOf(desiredValue).equals(value)) return true; + } catch (Exception e) { + + } + } else if (value instanceof List) { + List list = (List)value; + for (Object val : list) { + return valueEquals(desiredValue, val); + } + } + return false; + } + + @Override + public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + String roleName = mapperModel.getConfig().get(ROLE); + if (!isClaimPresent(mapperModel, context)) { + RoleModel role = getRoleFromString(realm, roleName); + if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName); + user.deleteRoleMapping(role); + } + + } + + @Override + public String getHelpText() { + return "If a claim exists, grant the user the specified realm or application role."; + } + +} diff --git a/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper b/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper new file mode 100755 index 0000000000..98ce5f7c9b --- /dev/null +++ b/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper @@ -0,0 +1 @@ +org.keycloak.broker.oidc.mappers.RoleMapper \ No newline at end of file diff --git a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderMapperTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderMapperTypeRepresentation.java index c76f6eed0d..81fdf0f179 100755 --- a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderMapperTypeRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderMapperTypeRepresentation.java @@ -1,5 +1,6 @@ package org.keycloak.representations.idm; +import java.util.LinkedList; import java.util.List; /** @@ -12,7 +13,7 @@ public class IdentityProviderMapperTypeRepresentation { protected String category; protected String helpText; - protected List properties; + protected List properties = new LinkedList<>(); public String getId() { return id; diff --git a/core/src/test/java/org/keycloak/JsonParserTest.java b/core/src/test/java/org/keycloak/JsonParserTest.java index 1f51c2154f..010eae4fad 100755 --- a/core/src/test/java/org/keycloak/JsonParserTest.java +++ b/core/src/test/java/org/keycloak/JsonParserTest.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; +import java.util.regex.Pattern; import org.codehaus.jackson.annotate.JsonAnyGetter; import org.codehaus.jackson.annotate.JsonAnySetter; @@ -12,6 +13,7 @@ import org.codehaus.jackson.annotate.JsonUnwrapped; import org.junit.Assert; import org.junit.Test; import org.keycloak.representations.IDToken; +import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.util.JsonSerialization; @@ -20,6 +22,32 @@ import org.keycloak.util.JsonSerialization; */ public class JsonParserTest { + @Test + public void regex() throws Exception { + Pattern p = Pattern.compile(".*(?!\\.pdf)"); + if (p.matcher("foo.pdf").matches()) { + System.out.println(".pdf no match"); + } + if (p.matcher("foo.txt").matches()) { + System.out.println("foo.txt matches"); + + } + + } + + @Test + public void testOtherClaims() throws Exception { + String json = "{ \"floatData\" : 555.5," + + "\"boolData\": true, " + + "\"intData\": 1234," + + "\"array\": [ \"val\", \"val2\"] }"; + JsonWebToken token = JsonSerialization.readValue(json, JsonWebToken.class); + System.out.println(token.getOtherClaims().get("floatData").getClass().getName()); + System.out.println(token.getOtherClaims().get("boolData").getClass().getName()); + System.out.println(token.getOtherClaims().get("intData").getClass().getName()); + System.out.println(token.getOtherClaims().get("array").getClass().getName()); + } + @Test public void testUnwrap() throws Exception { // just experimenting with unwrapped and any properties diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js index f45453c744..cffa8e08bd 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -206,6 +206,58 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'RealmIdentityProviderExportCtrl' }) + .when('/realms/:realm/identity-provider-mappers/:alias/mappers', { + templateUrl : function(params){ return resourceUrl + '/partials/identity-provider-mappers.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + identityProvider : function(IdentityProviderLoader) { + return IdentityProviderLoader(); + }, + mapperTypes : function(IdentityProviderMapperTypesLoader) { + return IdentityProviderMapperTypesLoader(); + }, + mappers : function(IdentityProviderMappersLoader) { + return IdentityProviderMappersLoader(); + } + }, + controller : 'IdentityProviderMapperListCtrl' + }) + .when('/realms/:realm/identity-provider-mappers/:alias/mappers/:mapperId', { + templateUrl : function(params){ return resourceUrl + '/partials/identity-provider-mapper-detail.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + identityProvider : function(IdentityProviderLoader) { + return IdentityProviderLoader(); + }, + mapperTypes : function(IdentityProviderMapperTypesLoader) { + return IdentityProviderMapperTypesLoader(); + }, + mapper : function(IdentityProviderMapperLoader) { + return IdentityProviderMapperLoader(); + } + }, + controller : 'IdentityProviderMapperCtrl' + }) + .when('/create/identity-provider-mappers/:realm/:alias', { + templateUrl : function(params){ return resourceUrl + '/partials/identity-provider-mapper-detail.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + identityProvider : function(IdentityProviderLoader) { + return IdentityProviderLoader(); + }, + mapperTypes : function(IdentityProviderMapperTypesLoader) { + return IdentityProviderMapperTypesLoader(); + } + }, + controller : 'IdentityProviderMapperCreateCtrl' + }) + .when('/realms/:realm/default-roles', { templateUrl : resourceUrl + '/partials/realm-default-roles.html', resolve : { diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index 2324d4876b..13941c3d52 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -1066,30 +1066,6 @@ module.controller('ClientClusteringNodeCtrl', function($scope, client, Client, C } }); -module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client, serverInfo, - ClientProtocolMappersByProtocol, - $http, $location, Dialog, Notifications) { - $scope.realm = realm; - $scope.client = client; - if (client.protocol == null) { - client.protocol = 'openid-connect'; - } - - var protocolMappers = serverInfo.protocolMapperTypes[client.protocol]; - var mapperTypes = {}; - for (var i = 0; i < protocolMappers.length; i++) { - mapperTypes[protocolMappers[i].id] = protocolMappers[i]; - } - $scope.mapperTypes = mapperTypes; - - - var updateMappers = function() { - $scope.mappers = ClientProtocolMappersByProtocol.query({realm : realm.realm, client : client.id, protocol : client.protocol}); - }; - - updateMappers(); -}); - module.controller('AddBuiltinProtocolMapperCtrl', function($scope, realm, client, serverInfo, ClientProtocolMappersByProtocol, $http, $location, Dialog, Notifications) { @@ -1152,6 +1128,30 @@ module.controller('AddBuiltinProtocolMapperCtrl', function($scope, realm, client }); +module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client, serverInfo, + ClientProtocolMappersByProtocol, + $http, $location, Dialog, Notifications) { + $scope.realm = realm; + $scope.client = client; + if (client.protocol == null) { + client.protocol = 'openid-connect'; + } + + var protocolMappers = serverInfo.protocolMapperTypes[client.protocol]; + var mapperTypes = {}; + for (var i = 0; i < protocolMappers.length; i++) { + mapperTypes[protocolMappers[i].id] = protocolMappers[i]; + } + $scope.mapperTypes = mapperTypes; + + + var updateMappers = function() { + $scope.mappers = ClientProtocolMappersByProtocol.query({realm : realm.realm, client : client.id, protocol : client.protocol}); + }; + + updateMappers(); +}); + module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo, client, mapper, ClientProtocolMapper, Notifications, Dialog, $location) { $scope.realm = realm; $scope.client = client; diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index 97c1bdaa7a..c2599a777a 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -807,7 +807,6 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload }); }; $scope.$watch('fromUrl.data', function(newVal, oldVal){ - console.log('watch fromUrl: ' + newVal + " " + oldVal); if ($scope.fromUrl.data && $scope.fromUrl.data.length > 0) { $scope.importUrl = true; } else{ @@ -1412,3 +1411,99 @@ module.controller('RealmBruteForceCtrl', function($scope, Realm, realm, $http, $ }); +module.controller('IdentityProviderMapperListCtrl', function($scope, realm, identityProvider, mapperTypes, mappers) { + $scope.realm = realm; + $scope.identityProvider = identityProvider; + $scope.mapperTypes = mapperTypes; + $scope.mappers = mappers; +}); + +module.controller('IdentityProviderMapperCtrl', function($scope, realm, identityProvider, mapperTypes, mapper, IdentityProviderMapper, Notifications, Dialog, $location) { + $scope.realm = realm; + $scope.identityProvider = identityProvider; + $scope.create = false; + $scope.mapper = angular.copy(mapper); + $scope.changed = false; + $scope.mapperType = mapperTypes[mapper.identityProviderMapper]; + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.$watch('mapper', function() { + if (!angular.equals($scope.mapper, mapper)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + IdentityProviderMapper.update({ + realm : realm.realm, + client: client.id, + mapperId : mapper.id + }, $scope.mapper, function() { + $scope.changed = false; + mapper = angular.copy($scope.mapper); + $location.url("/realms/" + realm.realm + '/identity-provider-mappers/' + identityProvider.alias + "/mappers/" + mapper.id); + Notifications.success("Your changes have been saved."); + }); + }; + + $scope.reset = function() { + $scope.mapper = angular.copy(mapper); + $scope.changed = false; + }; + + $scope.cancel = function() { + //$location.url("/realms"); + window.history.back(); + }; + + $scope.remove = function() { + Dialog.confirmDelete($scope.mapper.name, 'mapper', function() { + IdentityProviderMapper.remove({ realm: realm.realm, alias: mapper.identityProviderAlias, mapperId : $scope.mapper.id }, function() { + Notifications.success("The mapper has been deleted."); + $location.url("/realms/" + realm.realm + '/identity-provider-mappers/' + identityProvider.alias + "/mappers"); + }); + }); + }; + +}); + +module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, identityProvider, mapperTypes, IdentityProviderMapper, Notifications, Dialog, $location) { + $scope.realm = realm; + $scope.identityProvider = identityProvider; + $scope.create = true; + $scope.mapper = { identityProviderAlias: identityProvider.alias, config: {}}; + $scope.mapperTypes = mapperTypes; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.save = function() { + $scope.mapper.identityProviderMapper = $scope.mapperType.id; + IdentityProviderMapper.save({ + realm : realm.realm, alias: identityProvider.alias + }, $scope.mapper, function(data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + $location.url("/realms/" + realm.realm + '/identity-provider-mappers/' + identityProvider.alias + "/mappers/" + id); + Notifications.success("Mapper has been created."); + }); + }; + + $scope.cancel = function() { + //$location.url("/realms"); + window.history.back(); + }; + + +}); + + + + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js index b6e0541999..bf36307301 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js @@ -266,4 +266,33 @@ module.factory('IdentityProviderFactoryLoader', function(Loader, IdentityProvide provider_id: $route.current.params.provider_id } }); -}); \ No newline at end of file +}); + +module.factory('IdentityProviderMapperTypesLoader', function(Loader, IdentityProviderMapperTypes, $route, $q) { + return Loader.get(IdentityProviderMapperTypes, function () { + return { + realm: $route.current.params.realm, + alias: $route.current.params.alias + } + }); +}); + +module.factory('IdentityProviderMappersLoader', function(Loader, IdentityProviderMappers, $route, $q) { + return Loader.query(IdentityProviderMappers, function () { + return { + realm: $route.current.params.realm, + alias: $route.current.params.alias + } + }); +}); + +module.factory('IdentityProviderMapperLoader', function(Loader, IdentityProviderMapper, $route, $q) { + return Loader.get(IdentityProviderMapper, function () { + return { + realm: $route.current.params.realm, + alias: $route.current.params.alias, + mapperId: $route.current.params.mapperId + } + }); +}); + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js index df05a13ffc..2bb49369c7 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -1002,4 +1002,27 @@ module.factory('IdentityProviderFactory', function($resource) { realm : '@realm', provider_id : '@provider_id' }); -}); \ No newline at end of file +}); + +module.factory('IdentityProviderMapperTypes', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:alias/mapper-types', { + realm : '@realm', + alias : '@alias' + }); +}); + +module.factory('IdentityProviderMappers', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:alias/mappers', { + realm : '@realm', + alias : '@alias' + }); +}); + +module.factory('IdentityProviderMapper', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:alias/mappers/:mapperId', { + realm : '@realm', + alias : '@alias', + mapperId: '@mapperId' + }); +}); + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mapper-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mapper-detail.html new file mode 100755 index 0000000000..4e1fdc934c --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mapper-detail.html @@ -0,0 +1,86 @@ +
+
+ +
+ + + +

{{mapper.name}} Identity Provider Mapper

+

Create Identity Provider Mapper

+

* Required fields

+
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ + +
+ +
+
+ +
+
+ +
+ +
+ +
+
+ + +
+ +
+ + + +
+
+
+
\ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mappers.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mappers.html new file mode 100755 index 0000000000..2449609712 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/identity-provider-mappers.html @@ -0,0 +1,46 @@ +
+
+ +
+ +

{{realm.realm}} {{identityProvider.alias}} Identity Provider Mappers

+ + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ Create +
+
NameCategoryType
{{mapper.name}}{{mapperTypes[mapper.identityProviderMapper].category}}{{mapperTypes[mapper.identityProviderMapper].name}}
No mappers available
+
+
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html index 67b0a68fe1..0d1d027932 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html @@ -185,6 +185,7 @@
+ Mappers diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java index fcc1d639b0..35cce009a7 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java @@ -43,8 +43,10 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; /** * @author Pedro Igor @@ -194,10 +196,10 @@ public class IdentityProviderResource { @GET @Path("mapper-types") @NoCache - public List getMapperTypes() { + public Map getMapperTypes() { this.auth.requireView(); KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); - List types = new LinkedList<>(); + Map types = new HashMap<>(); List factories = sessionFactory.getProviderFactories(IdentityProviderMapper.class); for (ProviderFactory factory : factories) { IdentityProviderMapper mapper = (IdentityProviderMapper)factory; @@ -218,7 +220,7 @@ public class IdentityProviderResource { propRep.setHelpText(prop.getHelpText()); rep.getProperties().add(propRep); } - types.add(rep); + types.put(rep.getId(), rep); } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java index 91ced7f49a..af202b35f8 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java @@ -24,6 +24,7 @@ package org.keycloak.testsuite.account; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; @@ -156,6 +157,11 @@ public class AccountTest { }); } + @Test + public void ideTesting() throws Exception { + Thread.sleep(100000000); + } + @Test public void returnToAppFromQueryParam() { driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app");