protocol mapper ui

This commit is contained in:
Bill Burke 2015-02-27 17:21:02 -05:00
parent bccffadc7c
commit 1c6e90c4ef
30 changed files with 763 additions and 118 deletions

View file

@ -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;
}
}

View file

@ -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>

View file

@ -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'
})

View file

@ -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');
});
};
});

View file

@ -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();
};
});

View file

@ -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 {

View file

@ -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',

View file

@ -1,19 +0,0 @@
<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">Claims</li>
</ol>
<h2 data-ng-hide="create"><span>{{application.name}}</span> Allowed Claims <span tooltip-placement="right" tooltip="Allows you to restrict which claim information is stored in the access token generated for the application." class="fa fa-info-circle"></span></h2>
<form class="form-horizontal" name="claimForm">
<div data-ng-include data-src="resourceUrl + '/partials/claims.html'"></div>
<div class="pull-right form-actions" data-ng-show="access.manageApplications">
<button kc-reset data-ng-show="changed">Clear changes</button>
<button kc-save data-ng-show="changed">Save</button>
</div>
</form>
</div>
</div>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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>

View file

@ -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);
}

View file

@ -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.");

View file

@ -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.");

View file

@ -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.");

View file

@ -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.");

View file

@ -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) {
@ -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<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()) {

View file

@ -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()) {

View file

@ -278,7 +278,6 @@ public class RealmAdminResource {
*
*/
@Path("protocol-mappers")
@POST
public ProtocolMappersResource protocolMappers() {
ProtocolMappersResource mappers = new ProtocolMappersResource(realm, auth);
ResteasyProviderFactory.getInstance().injectProperties(mappers);

View file

@ -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;
}
}
}

View file

@ -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() {