keycloak-aplcache

Merge pull request #1005 from patriot1burke/master initial

2/27/2015 8:03:22 PM

Changes

forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-claims.html 19(+0 -19)

services/pom.xml 22(+2 -20)

Details

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 0000000..b208453
--- /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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @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<ConfigProperty> 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<ConfigProperty> getProperties() {
+        return properties;
+    }
+
+    public void setProperties(List<ConfigProperty> 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 e4a8a19..9884c9b 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 @@
     <script src="${resourceUrl}/js/controllers/applications.js" type="text/javascript"></script>
     <script src="${resourceUrl}/js/controllers/oauth-clients.js" type="text/javascript"></script>
     <script src="${resourceUrl}/js/controllers/users.js" type="text/javascript"></script>
+    <script src="${resourceUrl}/js/controllers/protocols.js" type="text/javascript"></script>
     <script src="${resourceUrl}/js/loaders.js" type="text/javascript"></script>
     <script src="${resourceUrl}/js/services.js" type="text/javascript"></script>
 
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 d6880dc..59f169f 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 3e758c2..b412288 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;
@@ -369,41 +375,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) {
@@ -1115,3 +1086,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 0000000..9eaa5f9
--- /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 dda0e81..9793cb0 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 d131b50..a0c5383 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-mappers.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-mappers.html
new file mode 100755
index 0000000..a06d07c
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-mappers.html
@@ -0,0 +1,48 @@
+<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
+<div id="content-area" class="col-md-9" role="main">
+    <kc-navigation-application></kc-navigation-application>
+    <div id="content">
+        <ol class="breadcrumb" data-ng-hide="create">
+            <li><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
+            <li><a href="#/realms/{{realm.realm}}/applications/{{application.id}}">{{application.name}}</a></li>
+            <li class="active">Protocol Mappers</li>
+        </ol>
+        <h2><span>{{realm.realm}} </span> {{application.name}} {{application.protocol}} Protocol Mappers  <span tooltip-placement="right" tooltip="Protocol mappers perform transformation on tokens and documents.  They an do things like map user data into protocol claims, or just transform any requests going between the application and auth server." class="fa fa-info-circle"></span></h2>
+        <table class="table table-striped table-bordered">
+            <thead>
+            <tr>
+                <th class="kc-table-actions" colspan="3">
+                    <div class="search-comp clearfix">
+                        <input type="text" placeholder="Search..." class="form-control search" data-ng-model="search.name"
+                               onkeyup="if(event.keyCode == 13){$(this).next('button').click();}">
+                        <button type="submit" class="kc-icon-search" tooltip-placement="right"
+                                tooltip="Search by mapper name.">
+                            Icon: search
+                        </button>
+                    </div>
+                    <div class="pull-right">
+                        <a class="btn btn-primary" href="#/create/protocols/{{application.protocol}}/realms/{{realm.realm}}/mappers">Create</a>
+                        <a class="btn btn-primary" href="#/realms/{{realm.realm}}/applications/{{application.id}}/add-mappers">Add Builtin</a>
+                        <button class="btn btn-primary" data-ng-click="remove()">Remove Selected</button>
+                    </div>
+                </th>
+            </tr>
+            <tr data-ng-hide="mappers.length == 0">
+                <th>Name</th>
+                <th>Type</th>
+                <th>Remove</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr ng-repeat="mapper in mappers | filter:search">
+                <td><a href="#/realms/{{realm.realm}}/protocols/{{application.protocol}}/mappers/{{mapper.id}}">{{mapper.name}}</a></td>
+                <td>{{mapperTypes[mapper.protocolMapper]}}</td>
+                <td><input type="checkbox" ng-model="mapper.isChecked"></td>
+            </tr>
+            <tr data-ng-show="mappers.length == 0">
+                <td>No mappers available</td>
+            </tr>
+            </tbody>
+        </table>
+    </div>
+</div>
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 0000000..36d7a6c
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-mappers-add.html
@@ -0,0 +1,47 @@
+<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
+<div id="content-area" class="col-md-9" role="main">
+    <kc-navigation-application></kc-navigation-application>
+    <div id="content">
+        <ol class="breadcrumb" data-ng-hide="create">
+            <li><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
+            <li><a href="#/realms/{{realm.realm}}/applications/{{application.id}}">{{application.name}}</a></li>
+            <li><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/mappers">{{application.name}} Mappers</a></li>
+            <li class="active">Add Protocol Mappers</li>
+        </ol>
+        <h2><span>{{realm.realm}} </span>Add {{application.name}} {{application.protocol}} Protocol Mappers  <span tooltip-placement="right" tooltip="Protocol mappers perform transformation on tokens and documents.  They an do things like map user data into protocol claims, or just transform any requests going between the application and auth server." class="fa fa-info-circle"></span></h2>
+        <table class="table table-striped table-bordered">
+            <thead>
+            <tr>
+                <th class="kc-table-actions" colspan="3">
+                    <div class="search-comp clearfix">
+                        <input type="text" placeholder="Search..." class="form-control search" data-ng-model="search.name"
+                               onkeyup="if(event.keyCode == 13){$(this).next('button').click();}">
+                        <button type="submit" class="kc-icon-search" tooltip-placement="right"
+                                tooltip="Search by mapper name.">
+                            Icon: search
+                        </button>
+                    </div>
+                    <div class="pull-right">
+                        <button class="btn btn-primary" data-ng-click="add()">Add Selected</button>
+                    </div>
+                </th>
+            </tr>
+            <tr data-ng-hide="mappers.length == 0">
+                <th>Name</th>
+                <th>Type</th>
+                <th>Add</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr ng-repeat="mapper in mappers | filter:search">
+                <td><a href="#/realms/{{realm.realm}}/protocols/{{application.protocol}}/mappers/{{mapper.id}}">{{mapper.name}}</a></td>
+                <td>{{mapperTypes[mapper.protocolMapper]}}</td>
+                <td><input type="checkbox" ng-model="mapper.isChecked"></td>
+            </tr>
+            <tr data-ng-show="mappers.length == 0">
+                <td>No mappers available</td>
+            </tr>
+            </tbody>
+        </table>
+    </div>
+</div>
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 0000000..383815a
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-list.html
@@ -0,0 +1,22 @@
+<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
+<div id="content-area" class="col-md-9" role="main">
+    <h2></h2>
+    <div id="content">
+        <h2><span>{{realm.realm}}</span> Client Protocols  <span tooltip-placement="right" tooltip="This section allows you to manage settings for the protocols clients and applications use to login and interact with the auth server.." class="fa fa-info-circle"></span></h2>
+        <table class="table table-striped table-bordered">
+            <thead>
+            <tr data-ng-hide="applications.length == 0">
+                <th>Protocol Name</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr ng-repeat="protocol in protocols">
+                <td><a href="#/realms/{{realm.realm}}/protocols/{{protocol}}/mappers">{{protocol}}</a></td>
+            </tr>
+            <tr data-ng-show="applications.length == 0">
+                <td>No protocols available</td>
+            </tr>
+            </tbody>
+        </table>
+    </div>
+</div>
\ 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 0000000..7b8c8ad
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-detail.html
@@ -0,0 +1,98 @@
+<div class="bs-sidebar col-sm-3 " data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
+<div id="content-area" class="col-sm-9" role="main">
+    <ul class="nav nav-tabs nav-tabs-pf">
+        <li><a href="#/realms/{{realm.realm}}/protocols/{{protocol}}/mappers">Mappers</a></li>
+    </ul>
+    <div id="content">
+        <ol class="breadcrumb" data-ng-hide="create">
+            <li><a href="#/realms/{{realm.realm}}/protocols/{{protocol}}/mappers">Protocol Mappers</a></li>
+            <li><a href="#/realms/{{realm.realm}}/protocols/{{protocol}}/mappers/{{mapper.id}}">{{mapper.name}}</a></li>
+            <li class="active">Protocol Mapper</li>
+        </ol>
+        <ol class="breadcrumb" data-ng-show="create">
+            <li><a href="#/realms/{{realm.realm}}/protocols/{{protocol}}/mappers">Protocol Mappers</a></li>
+            <li class="active">Add Protocol Mapper</li>
+        </ol>
+        <h2 class="pull-left" data-ng-hide="create">Protocol Mapper Settings</h2>
+        <h2 class="pull-left" data-ng-show="create">Add Protocol Mapper</h2>
+        <p class="subtitle"><span class="required">*</span> Required fields</p>
+        <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
+
+            <fieldset>
+                <div class="form-group clearfix">
+                    <label class="col-sm-2 control-label" for="protocol">Protocol</label>
+                    <div class="col-sm-4">
+                        <input class="form-control" id="protocol" type="text" ng-model="protocol" readonly>
+                    </div>
+                    <span tooltip-placement="right" tooltip="Protocol." class="fa fa-info-circle"></span>
+                </div>
+                <div class="form-group clearfix" data-ng-show="!create">
+                    <label class="col-sm-2 control-label" for="mapperId">ID </label>
+                    <div class="col-sm-4">
+                        <input class="form-control" id="mapperId" type="text" ng-model="mapper.id" readonly>
+                    </div>
+                </div>
+                <div class="form-group clearfix">
+                    <label class="col-sm-2 control-label" for="name">Name</label>
+                    <div class="col-sm-4">
+                        <input class="form-control" id="name" type="text" ng-model="mapper.name" data-ng-readonly="!create">
+                    </div>
+                    <span tooltip-placement="right" tooltip="Name of the mapper." class="fa fa-info-circle"></span>
+                </div>
+                <div class="form-group">
+                    <label for="consentRequired" class="col-sm-2 control-label">Consent required</label>
+                    <div class="col-sm-4">
+                        <input ng-model="mapper.consentRequired" name="consentRequired" id="consentRequired" onoffswitch />
+                    </div>
+                    <span tooltip-placement="right" tooltip="When granting temporary access, must the user consent to providing this data to the client?" class="fa fa-info-circle"></span>
+                </div>
+                <div class="form-group" data-ng-show="mapper.consentRequired">
+                    <label class="col-sm-2 control-label" for="consentText">Consent Text </label>
+
+                    <div class="col-sm-4">
+                        <textarea class="form-control" rows="5" cols="50" id="consentText" name="consentText" data-ng-model="mapper.consentText"></textarea>
+                    </div>
+                    <span tooltip-placement="right" tooltip="Text to display on consent page" class="fa fa-info-circle"></span>
+                </div>
+                <div class="form-group" data-ng-show="create">
+                    <label class="col-sm-2 control-label" for="mapperTypeCreate">Mapper Type</label>
+                    <div class="col-sm-6">
+                        <div class="select-kc">
+                            <select id="mapperTypeCreate"
+                                    ng-model="mapperType"
+                                    ng-options="mapperType.name for mapperType in mapperTypes">
+                            </select>
+                        </div>
+                    </div>
+                    <span tooltip-placement="right" tooltip="'Confidential' applications require a secret to initiate login protocol.  'Public' clients do not require a secret.  'Bearer-only' applications are web services that never initiate a login." class="fa fa-info-circle"></span>
+                </div>
+                <div class="form-group clearfix" data-ng-hide="create">
+                    <label class="col-sm-2 control-label" for="mapperType">Mapper Type</label>
+                    <div class="col-sm-4">
+                        <input class="form-control" id="mapperType" type="text" ng-model="mapperType.name" data-ng-readonly="true">
+                    </div>
+                    <span tooltip-placement="right" tooltip="Name of the mapper." class="fa fa-info-circle"></span>
+                </div>
+                <div data-ng-repeat="option in mapperType.properties" class="form-group">
+                    <label class="col-sm-2 control-label">{{option.label}} </label>
+
+                    <div class="col-sm-4">
+                        <input class="form-control" type="text" data-ng-model="mapper.config[ option.name ]" >
+                    </div>
+                    <span tooltip-placement="right" tooltip="{{option.helpText}}" class="fa fa-info-circle"></span>
+                </div>
+
+            </fieldset>
+            <div class="pull-right form-actions" data-ng-show="create && access.manageRealm">
+                <button kc-cancel data-ng-click="cancel()">Cancel</button>
+                <button kc-save>Save</button>
+            </div>
+
+            <div class="pull-right form-actions" data-ng-show="!create && access.manageRealm">
+                <button kc-reset data-ng-show="changed">Clear changes</button>
+                <button kc-save  data-ng-show="changed">Save</button>
+                <button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button>
+            </div>
+        </form>
+    </div>
+</div>
\ 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 0000000..bc27c66
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/protocol-mapper-list.html
@@ -0,0 +1,39 @@
+<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
+<div id="content-area" class="col-md-9" role="main">
+    <h2></h2>
+    <div id="content">
+        <h2><span>{{realm.realm}} </span> {{protocol}} Protocol Mappers  <span tooltip-placement="right" tooltip="Protocol mappers perform transformation on tokens and documents.  They an do things like map user data into protocol claims, or just transform any requests going between the application and auth server." class="fa fa-info-circle"></span></h2>
+        <table class="table table-striped table-bordered">
+            <thead>
+            <tr>
+                <th class="kc-table-actions" colspan="3">
+                    <div class="search-comp clearfix">
+                        <input type="text" placeholder="Search..." class="form-control search" data-ng-model="search.name"
+                               onkeyup="if(event.keyCode == 13){$(this).next('button').click();}">
+                        <button type="submit" class="kc-icon-search" tooltip-placement="right"
+                                tooltip="Search by mapper name.">
+                            Icon: search
+                        </button>
+                    </div>
+                    <div class="pull-right">
+                        <a class="btn btn-primary" href="#/create/protocols/{{protocol}}/realms/{{realm.realm}}/mappers">Create</a>
+                    </div>
+                </th>
+            </tr>
+            <tr data-ng-hide="mappers.length == 0">
+                <th>Name</th>
+                <th>Type</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr ng-repeat="mapper in mappers | filter:search">
+                <td><a href="#/realms/{{realm.realm}}/protocols/{{protocol}}/mappers/{{mapper.id}}">{{mapper.name}}</a></td>
+                <td>{{mapperTypes[mapper.protocolMapper]}}</td>
+            </tr>
+            <tr data-ng-show="mappers.length == 0">
+                <td>No mappers available</td>
+            </tr>
+            </tbody>
+        </table>
+    </div>
+</div>
\ 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 8415ba2..a8d3aab 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 @@
     <li data-ng-show="access.viewApplications" data-ng-class="(path[2] == 'applications' || path[1] == 'application' || path[3] == 'applications') && 'active'"><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
     <li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'oauth-clients' || path[1] == 'oauth-client') && 'active'"><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
     <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'sessions' || path[2] == 'token-settings') && 'active'"><a href="#/realms/{{realm.realm}}/sessions/realm">Sessions and Tokens</a></li>
+    <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'protocols') && 'active'"><a href="#/realms/{{realm.realm}}/protocols">Protocol Settings</a></li>
     <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'defense') && 'active'"><a href="#/realms/{{realm.realm}}/defense/headers">Security Defenses</a></li>
     <li data-ng-show="access.viewEvents" data-ng-class="(path[2] == 'events' || path[2] == 'events-settings') && 'active'"><a href="#/realms/{{realm.realm}}/events">Events</a></li>
 </ul>
\ 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 299b698..d5be190 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 @@
                     <div class="col-sm-4">
                         <input ng-model="compositeSwitch" name="compositeSwitch" id="compositeSwitch" ng-disabled="compositeSwitchDisabled" onoffswitch />
                     </div>
-                    <span tooltip-placement="right" tooltip="When this role is (un)assigned to a user any role associated with it will be (un)assigned implicitly." class="fa fa-info-circle"></span>                </div>
+                    <span tooltip-placement="right" tooltip="When this role is (un)assigned to a user any role associated with it will be (un)assigned implicitly." class="fa fa-info-circle"></span>
+                </div>
             </fieldset>
             <div class="pull-right form-actions" data-ng-show="create">
                 <button kc-cancel data-ng-click="cancel()" data-ng-show="changed">Cancel</button>
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 e84e149..40133f6 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 @@
     <li ng-class="{active: path[4] == 'credentials'}" data-ng-show="!application.publicClient && application.protocol != 'saml'"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/credentials">Credentials</a></li>
     <li ng-class="{active: path[4] == 'saml'}" data-ng-show="application.protocol == 'saml' && (application.attributes['saml.client.signature'] == 'true' || application.attributes['saml.encrypt'] == 'true')"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/saml/keys">SAML Keys</a></li>
     <li ng-class="{active: path[4] == 'roles'}"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/roles">Roles</a></li>
-    <li ng-class="{active: path[4] == 'claims'}" data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/claims">Claims</a></li>
+    <li ng-class="{active: path[4] == 'mappers'}" data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/mappers">Mappers</a></li>
     <li ng-class="{active: path[4] == 'scope-mappings'}" data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/scope-mappings">Scope</a></li>
     <li ng-class="{active: path[4] == 'revocation'}"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/revocation">Revocation</a></li>
     <li ng-class="{active: path[4] == 'identity-provider'}" data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/identity-provider">Identity Provider</a></li>
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 81ddc57..a3f2406 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<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
     private List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
     private Set<ClaimTypeModel> claimTypes = new HashSet<ClaimTypeModel>();
-    private Set<ProtocolMapperModel> claimMappings = new HashSet<ProtocolMapperModel>();
+    private Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
 
     private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
     private Map<String, String> smtpConfig = new HashMap<String, String>();
@@ -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<ProtocolMapperModel> getClaimMappings() {
-        return claimMappings;
+    public Set<ProtocolMapperModel> getProtocolMappers() {
+        return protocolMappers;
     }
 }
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 debd784..588f9d3 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<ProtocolMapperModel> 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/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 00a3fbb..d581d62 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<String> removeId = new HashSet<String>();
+            removeId.add(mapping.getId());
+            for (ApplicationModel app : getApplications()) {
+                app.removeProtocolMappers(removeId);
+            }
+            for (OAuthClientModel app : getOAuthClients()) {
+                app.removeProtocolMappers(removeId);
+            }
             em.remove(toDelete);
         }
 

services/pom.xml 22(+2 -20)

diff --git a/services/pom.xml b/services/pom.xml
index f02030c..a5f0e93 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -200,31 +200,12 @@
                     <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
+            <!--
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-javadoc-plugin</artifactId>
                 <version>2.9.1</version>
                 <executions>
-                    <!--
-                    <execution>
-                        <id>generate-service-docs</id>
-                        <phase>generate-resources</phase>
-                        <configuration>
-                            <doclet>com.hypnoticocelot.jaxrs.doclet.ServiceDoclet</doclet>
-                            <docletArtifact>
-                                <groupId>com.hypnoticocelot</groupId>
-                                <artifactId>jaxrs-doclet</artifactId>
-                                <version>0.0.4-SNAPSHOT</version>
-                            </docletArtifact>
-                            <reportOutputDirectory>swagger</reportOutputDirectory>
-                            <useStandardDocletOptions>false</useStandardDocletOptions>
-                            <additionalparam>-apiVersion 1 -docBasePath /apidocs -apiBasePath /</additionalparam>
-                        </configuration>
-                        <goals>
-                            <goal>javadoc</goal>
-                        </goals>
-                    </execution>
-                    -->
                     <execution>
                         <id>generate-service-docs</id>
                         <phase>generate-resources</phase>
@@ -252,6 +233,7 @@
                     </execution>
                 </executions>
             </plugin>
+    -->
         </plugins>
     </build>
 </project>
diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
index 0de9bf8..cf98f8c 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<LoginProtocol> {
-    //List<ProtocolMapperModel> 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 216c0fe..1d39d88 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 c543558..a63a238 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 6c30080..1ae2a65 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 e430f4d..a011adf 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 b4d9456..e9e482f 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<String, ProtocolMapperRepresentation> getMappersPerProtocol(@PathParam("protocol") String protocol) {
+    public List<ProtocolMapperRepresentation> getMappersPerProtocol(@PathParam("protocol") String protocol) {
         auth.requireView();
-        Map<String, ProtocolMapperRepresentation> mappers = new HashMap<String, ProtocolMapperRepresentation>();
+        List<ProtocolMapperRepresentation> mappers = new LinkedList<ProtocolMapperRepresentation>();
         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<String> mapperIds) {
@@ -88,26 +88,12 @@ public class ClientProtocolMappersResource {
     }
 
     /**
-     * replace sets of client mappers.
-     *
-     * @param mapperIds  List of mapper ids
-     */
-    @Path("models/set")
-    @PUT
-    @NoCache
-    @Consumes("application/json")
-    public void setMappers(Set<String> 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<String> mapperIds) {
@@ -119,7 +105,7 @@ public class ClientProtocolMappersResource {
     @NoCache
     @Path("models")
     @Produces("application/json")
-    public List<ProtocolMapperRepresentation> getMappersPerProtocol() {
+    public List<ProtocolMapperRepresentation> getMappers() {
         auth.requireView();
         List<ProtocolMapperRepresentation> mappers = new LinkedList<ProtocolMapperRepresentation>();
         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 2a7c82a..49f8053 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<String, ProtocolMapperRepresentation> getMappersPerProtocol(@PathParam("protocol") String protocol) {
+    public List<ProtocolMapperRepresentation> getMappersPerProtocol(@PathParam("protocol") String protocol) {
         auth.requireView();
-        Map<String, ProtocolMapperRepresentation> mappers = new HashMap<String, ProtocolMapperRepresentation>();
+        List<ProtocolMapperRepresentation> mappers = new LinkedList<ProtocolMapperRepresentation>();
         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<ProtocolMapperRepresentation> getMappersPerProtocol() {
+    public List<ProtocolMapperRepresentation> getMappers() {
         auth.requireView();
         List<ProtocolMapperRepresentation> mappers = new LinkedList<ProtocolMapperRepresentation>();
         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 592fa6f..4eefb1f 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 f1d3e35..d844721 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<String, List<ProtocolMapperTypeRepresentation>>();
+        for (ProviderFactory p : session.getKeycloakSessionFactory().getProviderFactories(ProtocolMapper.class)) {
+            ProtocolMapper mapper = (ProtocolMapper)p;
+            List<ProtocolMapperTypeRepresentation> types = info.protocolMapperTypes.get(mapper.getProtocol());
+            if (types == null) {
+                types = new LinkedList<ProtocolMapperTypeRepresentation>();
+                info.protocolMapperTypes.put(mapper.getProtocol(), types);
+            }
+            ProtocolMapperTypeRepresentation rep = new ProtocolMapperTypeRepresentation();
+            rep.setId(mapper.getId());
+            rep.setName(mapper.getDisplayType());
+            rep.setProperties(new LinkedList<ProtocolMapperTypeRepresentation.ConfigProperty>());
+            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<Map<String, String>>();
         for (ProviderFactory p : session.getKeycloakSessionFactory().getProviderFactories(ApplicationImporter.class)) {
@@ -155,6 +182,7 @@ public class ServerInfoAdminResource {
         private Map<String, Set<String>> providers;
 
         private List<String> eventListeners;
+        private Map<String, List<ProtocolMapperTypeRepresentation>> protocolMapperTypes;
 
         public ServerInfoRepresentation() {
         }
@@ -194,6 +222,10 @@ public class ServerInfoAdminResource {
         public Map<String, Set<String>> getProviders() {
             return providers;
         }
+
+        public Map<String, List<ProtocolMapperTypeRepresentation>> 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 29c6aab..a69389d 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() {