diff --git a/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java new file mode 100755 index 0000000000..b20845333f --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ProtocolMapperTypeRepresentation.java @@ -0,0 +1,68 @@ +package org.keycloak.representations.idm; + +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ProtocolMapperTypeRepresentation { + protected String id; + protected String name; + + public static class ConfigProperty { + protected String name; + protected String label; + protected String helpText; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getHelpText() { + return helpText; + } + + public void setHelpText(String helpText) { + this.helpText = helpText; + } + } + + protected List properties; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } +} diff --git a/forms/common-themes/src/main/resources/theme/admin/base/index.ftl b/forms/common-themes/src/main/resources/theme/admin/base/index.ftl index e4a8a19ea4..9884c9b6e0 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/index.ftl +++ b/forms/common-themes/src/main/resources/theme/admin/base/index.ftl @@ -33,6 +33,7 @@ + diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js index b398b83c41..47c3fcac07 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js @@ -427,8 +427,8 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'ApplicationRoleDetailCtrl' }) - .when('/realms/:realm/applications/:application/claims', { - templateUrl : resourceUrl + '/partials/application-claims.html', + .when('/realms/:realm/applications/:application/mappers', { + templateUrl : resourceUrl + '/partials/application-mappers.html', resolve : { realm : function(RealmLoader) { return RealmLoader(); @@ -436,11 +436,26 @@ module.config([ '$routeProvider', function($routeProvider) { application : function(ApplicationLoader) { return ApplicationLoader(); }, - claims : function(ApplicationClaimsLoader) { - return ApplicationClaimsLoader(); + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); } }, - controller : 'ApplicationClaimsCtrl' + controller : 'ApplicationProtocolMapperCtrl' + }) + .when('/realms/:realm/applications/:application/add-mappers', { + templateUrl : resourceUrl + '/partials/application-mappers-add.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + application : function(ApplicationLoader) { + return ApplicationLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'AddApplicationProtocolMapperCtrl' }) .when('/realms/:realm/applications/:application/sessions', { templateUrl : resourceUrl + '/partials/application-sessions.html', @@ -946,6 +961,73 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'RealmBruteForceCtrl' }) + .when('/realms/:realm/protocols', { + templateUrl : resourceUrl + '/partials/protocol-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + + }, + controller : 'ProtocolListCtrl' + }) + .when('/realms/:realm/protocols/:protocol/mappers', { + templateUrl : resourceUrl + '/partials/protocol-mapper-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + protocol : function($route) { + return $route.current.params.protocol; + }, + mappers : function(RealmProtocolMappersByProtocolLoader) { + return RealmProtocolMappersByProtocolLoader(); + } + + }, + controller : 'ProtocolMapperListCtrl' + }) + .when('/realms/:realm/protocols/:protocol/mappers/:id', { + templateUrl : resourceUrl + '/partials/protocol-mapper-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + protocol : function($route) { + return $route.current.params.protocol; + }, + mapper : function(RealmProtocolMapperLoader) { + return RealmProtocolMapperLoader(); + } + + }, + controller : 'ProtocolMapperCtrl' + }) + .when('/create/protocols/:protocol/realms/:realm/mappers', { + templateUrl : resourceUrl + '/partials/protocol-mapper-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + protocol : function($route) { + return $route.current.params.protocol; + } + }, + controller : 'ProtocolMapperCreateCtrl' + }) + .when('/server-info', { templateUrl : resourceUrl + '/partials/server-info.html' }) diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js index 617d5abbd1..569c8662b8 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js @@ -1,3 +1,9 @@ +Array.prototype.remove = function(from, to) { + var rest = this.slice((to || from) + 1 || this.length); + this.length = from < 0 ? this.length + from : from; + return this.push.apply(this, rest); +}; + module.controller('ApplicationRoleListCtrl', function($scope, $location, realm, application, roles) { $scope.realm = realm; $scope.roles = roles; @@ -349,41 +355,6 @@ module.controller('ApplicationSessionsCtrl', function($scope, realm, sessionCoun }; }); -module.controller('ApplicationClaimsCtrl', function($scope, realm, application, claims, - ApplicationClaims, - $http, $location, Dialog, Notifications) { - $scope.realm = realm; - $scope.application = application; - $scope.claims = angular.copy(claims); - - $scope.changed = false; - - $scope.$watch('claims', function () { - if (!angular.equals($scope.claims, claims)) { - $scope.changed = true; - } - }, true); - - - $scope.save = function () { - ApplicationClaims.update({ - realm: realm.realm, - application: application.id - }, $scope.claims, function () { - $scope.changed = false; - claims = angular.copy($scope.claims); - - Notifications.success("Your claim changes have been saved."); - }); - }; - - $scope.reset = function () { - $location.url("/realms/" + realm.realm + "/applications/" + application.id + "/claims"); - }; - -}); - - module.controller('ApplicationRoleDetailCtrl', function($scope, realm, application, role, roles, applications, Role, ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites, $http, $location, Dialog, Notifications) { @@ -1095,3 +1066,107 @@ module.controller('ApplicationClusteringNodeCtrl', function($scope, application, } }); +module.controller('ApplicationProtocolMapperCtrl', function($scope, realm, application, serverInfo, + ApplicationProtocolMappersByProtocol, + $http, $location, Dialog, Notifications) { + $scope.realm = realm; + $scope.application = application; + + var protocolMappers = serverInfo.protocolMapperTypes[application.protocol]; + var mapperTypes = {}; + for (var i = 0; i < protocolMappers.length; i++) { + mapperTypes[protocolMappers[i].id] = protocolMappers[i].name; + } + $scope.mapperTypes = mapperTypes; + + + var updateMappers = function() { + $scope.mappers = ApplicationProtocolMappersByProtocol.query({realm : realm.realm, application : application.id, protocol : application.protocol}); + + for (var i = 0; i < $scope.mappers.length; i++) { + $scope.mappers[i].isChecked = false; + } + }; + + updateMappers(); + + $scope.remove = function() { + var toDelete = []; + for (var i = 0; i < $scope.mappers.length; i++) { + if ($scope.mappers[i].isChecked) { + toDelete.push($scope.mappers[i].id); + } + } + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/applications-by-id/' + application.id + '/protocol-mappers/models', + {data : toDelete, headers : {"content-type" : "application/json"}}).success(function() { + Notifications.success("Mappers removed"); + updateMappers(); + }).error(function() { + updateMappers(); + Notifications.error("Error removing mappers"); + }); + }; + +}); + +module.controller('AddApplicationProtocolMapperCtrl', function($scope, realm, application, serverInfo, + RealmProtocolMappersByProtocol, + ApplicationProtocolMappersByProtocol, + $http, $location, Dialog, Notifications) { + $scope.realm = realm; + $scope.application = application; + + var protocolMapperTypes = serverInfo.protocolMapperTypes[application.protocol]; + var mapperTypes = {}; + for (var i = 0; i < protocolMapperTypes.length; i++) { + mapperTypes[protocolMapperTypes[i].id] = protocolMapperTypes[i].name; + } + $scope.mapperTypes = mapperTypes; + + + var updateMappers = function() { + var mappers = RealmProtocolMappersByProtocol.query({realm : realm.realm, protocol : application.protocol}, function() { + var appMappers = ApplicationProtocolMappersByProtocol.query({realm : realm.realm, application : application.id, protocol : application.protocol}, function() { + for (var i = 0; i < appMappers.length; i++) { + for (var j = 0; j < mappers.length; j++) { + if (mappers[j].id == appMappers[i].id) { + mappers.remove(j); + break; + } + } + } + $scope.mappers = mappers; + for (var i = 0; i < $scope.mappers.length; i++) { + $scope.mappers[i].isChecked = false; + } + + + }) + + }) + + }; + + updateMappers(); + + $scope.add = function() { + var toAdd = []; + for (var i = 0; i < $scope.mappers.length; i++) { + if ($scope.mappers[i].isChecked) { + toAdd.push($scope.mappers[i].id); + } + } + $http.post(authUrl + '/admin/realms/' + realm.realm + '/applications-by-id/' + application.id + '/protocol-mappers/models', + toAdd).success(function() { + Notifications.success("Mappers added"); + $location.url('/realms/' + realm.realm + '/applications/' + application.id + '/mappers'); + }).error(function() { + Notifications.error("Error adding mappers"); + $location.url('/realms/' + realm.realm + '/applications/' + application.id + '/mappers'); + }); + }; + +}); + + + diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/protocols.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/protocols.js new file mode 100755 index 0000000000..9eaa5f9448 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/protocols.js @@ -0,0 +1,125 @@ +module.controller('ProtocolListCtrl', function($scope, realm, serverInfo, $location) { + $scope.realm = realm; + $scope.protocols = serverInfo.protocols; + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); +}); + +module.controller('ProtocolMapperListCtrl', function($scope, realm, serverInfo, protocol, mappers, $location) { + $scope.realm = realm; + $scope.protocol = protocol; + var protocolMappers = serverInfo.protocolMapperTypes[protocol]; + var mapperTypes = {}; + if (protocolMappers) { + for (var i = 0; i < protocolMappers.length; i++) { + mapperTypes[protocolMappers[i].id] = protocolMappers[i].name; + } + } + $scope.mapperTypes = mapperTypes; + + $scope.mappers = mappers; + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); +}); + +module.controller('ProtocolMapperCtrl', function($scope, realm, serverInfo, protocol, mapper, RealmProtocolMapper, Notifications, Dialog, $location) { + $scope.realm = realm; + $scope.create = false; + $scope.protocol = protocol; + $scope.mapper = angular.copy(mapper); + var oldCopy = angular.copy($scope.realm); + $scope.changed = false; + + console.log('protocol: ' + protocol); + var protocolMappers = serverInfo.protocolMapperTypes[protocol]; + for (var i = 0; i < protocolMappers.length; i++) { + if (protocolMappers[i].id == mapper.protocolMapper) { + $scope.mapperType = protocolMappers[i]; + } + } + $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() { + RealmProtocolMapper.update({ + realm : realm.realm, + id : mapper.id + }, $scope.mapper, function() { + $scope.changed = false; + mapper = angular.copy($scope.mapper); + $location.url("/realms/" + realm.realm + "/protocols/" + protocol + "/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() { + RealmProtocolMapper.remove({ realm: realm.realm, id : $scope.mapper.id }, function() { + Notifications.success("The mapper has been deleted."); + $location.url("/realms/" + realm.realm + "/protocols/" + protocol + "/mappers"); + }); + }); + }; + +}); + +module.controller('ProtocolMapperCreateCtrl', function($scope, realm, serverInfo, protocol, RealmProtocolMapper, Notifications, Dialog, $location) { + $scope.realm = realm; + $scope.create = true; + $scope.protocol = protocol; + $scope.mapper = { protocol : protocol, config: {}}; + $scope.mapperTypes = serverInfo.protocolMapperTypes[protocol]; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.save = function() { + $scope.mapper.protocolMapper = $scope.mapperType.id; + RealmProtocolMapper.save({ + realm : realm.realm + }, $scope.mapper, function(data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + $location.url("/realms/" + realm.realm + "/protocols/" + protocol + "/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/admin/base/resources/js/loaders.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/loaders.js index dda0e815c5..9793cb0412 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/loaders.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/loaders.js @@ -79,6 +79,25 @@ module.factory('RealmApplicationSessionStatsLoader', function(Loader, RealmAppli }); }); +module.factory('RealmProtocolMappersByProtocolLoader', function(Loader, RealmProtocolMappersByProtocol, $route, $q) { + return Loader.query(RealmProtocolMappersByProtocol, function() { + return { + realm : $route.current.params.realm, + protocol: $route.current.params.protocol + } + }); +}); + +module.factory('RealmProtocolMapperLoader', function(Loader, RealmProtocolMapper, $route, $q) { + return Loader.get(RealmProtocolMapper, function() { + return { + realm : $route.current.params.realm, + id: $route.current.params.id + } + }); +}); + + module.factory('UserLoader', function(Loader, User, $route, $q) { return Loader.get(User, function() { return { diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js index d131b50146..a0c538325d 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js @@ -188,6 +188,24 @@ module.factory('ServerInfo', function($resource) { return $resource(authUrl + '/admin/serverinfo'); }); +module.factory('RealmProtocolMappersByProtocol', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/protocol-mappers/protocol/:protocol', { + realm : '@realm', + protocol : "@protocol" + }); +}); +module.factory('RealmProtocolMapper', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/protocol-mappers/models/:id', { + realm : '@realm', + id : "@id" + }, { + update : { + method : 'PUT' + } + }); +}); + + module.factory('User', function($resource) { return $resource(authUrl + '/admin/realms/:realm/users/:userId', { realm : '@realm', @@ -639,6 +657,14 @@ module.factory('ApplicationClaims', function($resource) { }); }); +module.factory('ApplicationProtocolMappersByProtocol', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/applications-by-id/:application/protocol-mappers/protocol/:protocol', { + realm : '@realm', + application : "@application", + protocol : "@protocol" + }); +}); + module.factory('ApplicationSessionStats', function($resource) { return $resource(authUrl + '/admin/realms/:realm/applications-by-id/:application/session-stats', { realm : '@realm', diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-claims.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-claims.html deleted file mode 100755 index d23887cae5..0000000000 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-claims.html +++ /dev/null @@ -1,19 +0,0 @@ -
-
- -
- -

{{application.name}} Allowed Claims

-
-
-
- - -
-
-
-
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-mappers-add.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-mappers-add.html new file mode 100755 index 0000000000..36d7a6cacc --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-mappers-add.html @@ -0,0 +1,47 @@ +
+
+ +
+ +

{{realm.realm}} Add {{application.name}} {{application.protocol}} Protocol Mappers

+ + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ +
+
NameTypeAdd
{{mapper.name}}{{mapperTypes[mapper.protocolMapper]}}
No mappers available
+
+
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-mappers.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-mappers.html new file mode 100755 index 0000000000..a06d07cef2 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-mappers.html @@ -0,0 +1,48 @@ +
+
+ +
+ +

{{realm.realm}} {{application.name}} {{application.protocol}} Protocol Mappers

+ + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ Create + Add Builtin + +
+
NameTypeRemove
{{mapper.name}}{{mapperTypes[mapper.protocolMapper]}}
No mappers available
+
+
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-list.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-list.html new file mode 100755 index 0000000000..383815aa1e --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-list.html @@ -0,0 +1,22 @@ +
+
+

+
+

{{realm.realm}} Client Protocols

+ + + + + + + + + + + + + + +
Protocol Name
{{protocol}}
No protocols available
+
+
\ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-detail.html new file mode 100755 index 0000000000..7b8c8ad96e --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-detail.html @@ -0,0 +1,98 @@ +
+
+ +
+ + +

Protocol Mapper Settings

+

Add Protocol Mapper

+

* Required fields

+
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ + +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ + +
+ +
+ +
+ +
+
+ + +
+ +
+ + + +
+
+
+
\ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-list.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-list.html new file mode 100755 index 0000000000..bc27c660a6 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-list.html @@ -0,0 +1,39 @@ +
+
+

+
+

{{realm.realm}} {{protocol}} Protocol Mappers

+ + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ Create +
+
NameType
{{mapper.name}}{{mapperTypes[mapper.protocolMapper]}}
No mappers available
+
+
\ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-menu.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-menu.html index 8415ba23ee..a8d3aab9c5 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-menu.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-menu.html @@ -14,6 +14,7 @@
  • Applications
  • OAuth Clients
  • Sessions and Tokens
  • +
  • Protocol Settings
  • Security Defenses
  • Events
  • \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/role-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/role-detail.html index 299b6989d4..d5be190d09 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/role-detail.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/role-detail.html @@ -34,7 +34,8 @@
    - + +
    diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html index e84e1499bc..40133f6c69 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html @@ -3,7 +3,7 @@
  • Credentials
  • SAML Keys
  • Roles
  • -
  • Claims
  • +
  • Mappers
  • Scope
  • Revocation
  • Identity Provider
  • diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java index debd784a91..588f9d369e 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java @@ -888,7 +888,7 @@ public class RealmAdapter implements RealmModel { @Override public Set getProtocolMappers() { if (updated != null) return updated.getProtocolMappers(); - return cached.getClaimMappings(); + return cached.getProtocolMappers(); } @Override @@ -913,7 +913,7 @@ public class RealmAdapter implements RealmModel { @Override public ProtocolMapperModel getProtocolMapperById(String id) { - for (ProtocolMapperModel mapping : cached.getClaimMappings()) { + for (ProtocolMapperModel mapping : cached.getProtocolMappers()) { if (mapping.getId().equals(id)) return mapping; } return null; @@ -921,7 +921,7 @@ public class RealmAdapter implements RealmModel { @Override public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) { - for (ProtocolMapperModel mapping : cached.getClaimMappings()) { + for (ProtocolMapperModel mapping : cached.getProtocolMappers()) { if (mapping.getProtocol().equals(protocol) && mapping.getName().equals(name)) return mapping; } return null; diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java index 81ddc57cd9..a3f2406c98 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java @@ -71,7 +71,7 @@ public class CachedRealm { private List userFederationProviders = new ArrayList(); private List identityProviders = new ArrayList(); private Set claimTypes = new HashSet(); - private Set claimMappings = new HashSet(); + private Set protocolMappers = new HashSet(); private Map browserSecurityHeaders = new HashMap(); private Map smtpConfig = new HashMap(); @@ -138,6 +138,9 @@ public class CachedRealm { for (ClaimTypeModel claimType : model.getClaimTypes()) { this.claimTypes.add(new ClaimTypeModel(claimType)); } + for (ProtocolMapperModel mapper : model.getProtocolMappers()) { + this.protocolMappers.add(mapper); + } smtpConfig.putAll(model.getSmtpConfig()); browserSecurityHeaders.putAll(model.getBrowserSecurityHeaders()); @@ -353,7 +356,7 @@ public class CachedRealm { return claimTypes; } - public Set getClaimMappings() { - return claimMappings; + public Set getProtocolMappers() { + return protocolMappers; } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index 00a3fbb79f..d581d62862 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -1340,6 +1340,14 @@ public class RealmAdapter implements RealmModel { ProtocolMapperEntity toDelete = getProtocolMapperEntity(mapping.getId()); if (toDelete != null) { realm.getProtocolMappers().remove(toDelete); + Set removeId = new HashSet(); + removeId.add(mapping.getId()); + for (ApplicationModel app : getApplications()) { + app.removeProtocolMappers(removeId); + } + for (OAuthClientModel app : getOAuthClients()) { + app.removeProtocolMappers(removeId); + } em.remove(toDelete); } diff --git a/services/pom.xml b/services/pom.xml index f02030c344..a5f0e936cb 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -200,31 +200,12 @@ ${maven.compiler.target} + generate-service-docs generate-resources @@ -252,6 +233,7 @@ + --> diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java index 0de9bf8b50..cf98f8c871 100755 --- a/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java +++ b/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java @@ -13,6 +13,5 @@ import java.util.List; * @version $Revision: 1 $ */ public interface LoginProtocolFactory extends ProviderFactory { - //List getDefaultProtocolMappers(); Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java index 216c0fe972..1d39d88427 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCClientSessionNoteMapper.java @@ -28,6 +28,7 @@ public class OIDCClientSessionNoteMapper extends AbstractOIDCProtocolMapper impl property.setLabel(CLIENT_SESSION_NOTE); property.setHelpText("Name of the note to map in the UserSessionModel"); configProperties.add(property); + property = new ConfigProperty(); property.setName(AttributeMapperHelper.TOKEN_CLAIM_NAME); property.setLabel(AttributeMapperHelper.TOKEN_CLAIM_NAME); property.setHelpText("Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created."); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java index c5435588b9..a63a238163 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserAttributeMapper.java @@ -30,6 +30,7 @@ public class OIDCUserAttributeMapper extends AbstractOIDCProtocolMapper implemen property.setLabel(USER_MODEL_ATTRIBUTE_NAME); property.setHelpText("Name of stored user attribute which is the name of an attribute within the UserModel.attribute map."); configProperties.add(property); + property = new ConfigProperty(); property.setName(AttributeMapperHelper.TOKEN_CLAIM_NAME); property.setLabel(AttributeMapperHelper.TOKEN_CLAIM_NAME); property.setHelpText("Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created."); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java index 6c30080321..1ae2a65db6 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserModelMapper.java @@ -30,6 +30,7 @@ public class OIDCUserModelMapper extends AbstractOIDCProtocolMapper implements O property.setLabel(USER_MODEL_PROPERTY); property.setHelpText("Name of the property method in the UserModel interface. For example, a value of 'email' would reference the UserModel.getEmail() method."); configProperties.add(property); + property = new ConfigProperty(); property.setName(AttributeMapperHelper.TOKEN_CLAIM_NAME); property.setLabel(AttributeMapperHelper.TOKEN_CLAIM_NAME); property.setHelpText("Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created."); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java index e430f4d528..a011adfd29 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/OIDCUserSessionNoteMapper.java @@ -28,6 +28,7 @@ public class OIDCUserSessionNoteMapper extends AbstractOIDCProtocolMapper implem property.setLabel("UserSession Note"); property.setHelpText("Name of the note to map in the UserSessionModel"); configProperties.add(property); + property = new ConfigProperty(); property.setName(AttributeMapperHelper.TOKEN_CLAIM_NAME); property.setLabel(AttributeMapperHelper.TOKEN_CLAIM_NAME); property.setHelpText("Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created."); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientProtocolMappersResource.java index b4d9456945..e9e482fd11 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientProtocolMappersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientProtocolMappersResource.java @@ -64,11 +64,11 @@ public class ClientProtocolMappersResource { @NoCache @Path("protocol/{protocol}") @Produces("application/json") - public Map getMappersPerProtocol(@PathParam("protocol") String protocol) { + public List getMappersPerProtocol(@PathParam("protocol") String protocol) { auth.requireView(); - Map mappers = new HashMap(); + List mappers = new LinkedList(); for (ProtocolMapperModel mapper : client.getProtocolMappers()) { - mappers.put(mapper.getName(), ModelToRepresentation.toRepresentation(mapper)); + mappers.add(ModelToRepresentation.toRepresentation(mapper)); } return mappers; } @@ -78,8 +78,8 @@ public class ClientProtocolMappersResource { * * @param mapperIds List of mapper ids */ - @Path("models/add") - @PUT + @Path("models") + @POST @NoCache @Consumes("application/json") public void addMappers(Set mapperIds) { @@ -87,27 +87,13 @@ public class ClientProtocolMappersResource { client.addProtocolMappers(mapperIds); } - /** - * replace sets of client mappers. - * - * @param mapperIds List of mapper ids - */ - @Path("models/set") - @PUT - @NoCache - @Consumes("application/json") - public void setMappers(Set mapperIds) { - auth.requireManage(); - client.setProtocolMappers(mapperIds); - } - /** * remove client mappers. * * @param mapperIds List of mapper ids */ - @Path("models/remove") - @PUT + @Path("models") + @DELETE @NoCache @Consumes("application/json") public void removeMappers(Set mapperIds) { @@ -119,7 +105,7 @@ public class ClientProtocolMappersResource { @NoCache @Path("models") @Produces("application/json") - public List getMappersPerProtocol() { + public List getMappers() { auth.requireView(); List mappers = new LinkedList(); for (ProtocolMapperModel mapper : realm.getProtocolMappers()) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java index 2a7c82a6a1..49f8053262 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java @@ -74,11 +74,11 @@ public class ProtocolMappersResource { @NoCache @Path("protocol/{protocol}") @Produces("application/json") - public Map getMappersPerProtocol(@PathParam("protocol") String protocol) { + public List getMappersPerProtocol(@PathParam("protocol") String protocol) { auth.requireView(); - Map mappers = new HashMap(); + List mappers = new LinkedList(); for (ProtocolMapperModel mapper : realm.getProtocolMappers()) { - mappers.put(mapper.getName(), ModelToRepresentation.toRepresentation(mapper)); + if (mapper.getProtocol().equals(protocol)) mappers.add(ModelToRepresentation.toRepresentation(mapper)); } return mappers; } @@ -95,7 +95,7 @@ public class ProtocolMappersResource { public Response createMapper(ProtocolMapperRepresentation rep) { auth.requireManage(); ProtocolMapperModel model = RepresentationToModel.toModel(rep); - realm.addProtocolMapper(model); + model = realm.addProtocolMapper(model); return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build(); } @@ -103,7 +103,7 @@ public class ProtocolMappersResource { @NoCache @Path("models") @Produces("application/json") - public List getMappersPerProtocol() { + public List getMappers() { auth.requireView(); List mappers = new LinkedList(); for (ProtocolMapperModel mapper : realm.getProtocolMappers()) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 592fa6f098..4eefb1fdaa 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -278,7 +278,6 @@ public class RealmAdminResource { * */ @Path("protocol-mappers") - @POST public ProtocolMappersResource protocolMappers() { ProtocolMappersResource mappers = new ProtocolMappersResource(realm, auth); ResteasyProviderFactory.getInstance().injectProperties(mappers); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java index f1d3e355e0..d844721f55 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java @@ -11,9 +11,11 @@ import org.keycloak.freemarker.Theme; import org.keycloak.freemarker.ThemeProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.LoginProtocol; +import org.keycloak.protocol.ProtocolMapper; import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.Spi; import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation; import org.keycloak.social.SocialIdentityProvider; import javax.ws.rs.GET; @@ -53,6 +55,7 @@ public class ServerInfoAdminResource { setProtocols(info); setApplicationImporters(info); setProviders(info); + setProtocolMappers(info); return info; } @@ -128,6 +131,30 @@ public class ServerInfoAdminResource { Collections.sort(info.protocols); } + private void setProtocolMappers(ServerInfoRepresentation info) { + info.protocolMapperTypes = new HashMap>(); + for (ProviderFactory p : session.getKeycloakSessionFactory().getProviderFactories(ProtocolMapper.class)) { + ProtocolMapper mapper = (ProtocolMapper)p; + List types = info.protocolMapperTypes.get(mapper.getProtocol()); + if (types == null) { + types = new LinkedList(); + info.protocolMapperTypes.put(mapper.getProtocol(), types); + } + ProtocolMapperTypeRepresentation rep = new ProtocolMapperTypeRepresentation(); + rep.setId(mapper.getId()); + rep.setName(mapper.getDisplayType()); + rep.setProperties(new LinkedList()); + for (ProtocolMapper.ConfigProperty prop : mapper.getConfigProperties()) { + ProtocolMapperTypeRepresentation.ConfigProperty propRep = new ProtocolMapperTypeRepresentation.ConfigProperty(); + propRep.setName(prop.getName()); + propRep.setLabel(prop.getLabel()); + propRep.setHelpText(prop.getHelpText()); + rep.getProperties().add(propRep); + } + types.add(rep); + } + } + private void setApplicationImporters(ServerInfoRepresentation info) { info.applicationImporters = new LinkedList>(); for (ProviderFactory p : session.getKeycloakSessionFactory().getProviderFactories(ApplicationImporter.class)) { @@ -155,6 +182,7 @@ public class ServerInfoAdminResource { private Map> providers; private List eventListeners; + private Map> protocolMapperTypes; public ServerInfoRepresentation() { } @@ -194,6 +222,10 @@ public class ServerInfoAdminResource { public Map> getProviders() { return providers; } + + public Map> getProtocolMapperTypes() { + return protocolMapperTypes; + } } } 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 29c6aab66c..a69389d090 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 @@ -25,6 +25,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.keycloak.events.Details; @@ -156,13 +157,11 @@ public class AccountTest { }); } -/* @Test @Ignore public void runit() throws Exception { Thread.sleep(10000000); } - */ @Test public void returnToAppFromQueryParam() {