Merge pull request #1441 from mposolda/master

KEYCLOAK-1233 Admin console support for add/remove federated identity
This commit is contained in:
Marek Posolda 2015-07-11 16:48:29 +02:00
commit af922920ab
12 changed files with 194 additions and 54 deletions

View file

@ -216,7 +216,7 @@ ktadd -k /tmp/http.keytab HTTP/www.mydomain.org@MYDOMAIN.ORG
GSS credential will need to be used by your application. So you need to enable built-in <literal>gss delegation credential</literal> protocol mapper
in admin console for your application. This will cause that Keycloak will deserialize GSS credential and transmit it to the application
in access token. Application will need to deserialize it and use it for further GSS calls against other services. We have an example, which is showing it in details. It's in <literal>examples/kerberos</literal>
in the Keycloak appliance distribution or WAR distribution download. You can also check the example sources directly <ulink url="https://github.com/keycloak/keycloak/blob/master/examples/kerberos">here</ulink> .
in the Keycloak example distribution or demo distribution download. You can also check the example sources directly <ulink url="https://github.com/keycloak/keycloak/blob/master/examples/kerberos">here</ulink> .
</para>
<para>
Once you deserialize the credential from the access token to the GSSCredential object, then GSSContext will need to

View file

@ -208,6 +208,10 @@
more attribute mappings (For example to street, postalCode etc), delete firstName/lastname mapper and put fullName mapper instead, add role mappers etc.
Admin console provides tooltips, which should help on how to configure corresponding mappers.
</para>
<para>
We have an example, which is showing LDAP integration and set of base mappers and sample mappers (mappers for street and postalCode) . It's in <literal>examples/ldap</literal>
in the Keycloak example distribution or demo distribution download. You can also check the example sources directly <ulink url="https://github.com/keycloak/keycloak/blob/master/examples/ldap">here</ulink> .
</para>
</section>
<section>
<title>Writing your own User Federation Provider</title>

View file

@ -388,7 +388,7 @@ module.config([ '$routeProvider', function($routeProvider) {
controller : 'UserSessionsCtrl'
})
.when('/realms/:realm/users/:user/federated-identity', {
templateUrl : resourceUrl + '/partials/user-federated-identity.html',
templateUrl : resourceUrl + '/partials/user-federated-identity-list.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
@ -402,6 +402,21 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'UserFederatedIdentityCtrl'
})
.when('/create/federated-identity/:realm/:user', {
templateUrl : resourceUrl + '/partials/user-federated-identity-detail.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
user : function(UserLoader) {
return UserLoader();
},
federatedIdentities : function(UserFederatedIdentityLoader) {
return UserFederatedIdentityLoader();
}
},
controller : 'UserFederatedIdentityAddCtrl'
})
.when('/realms/:realm/users/:user/consents', {
templateUrl : resourceUrl + '/partials/user-consents.html',
resolve : {

View file

@ -129,10 +129,66 @@ module.controller('UserSessionsCtrl', function($scope, realm, user, sessions, Us
}
});
module.controller('UserFederatedIdentityCtrl', function($scope, realm, user, federatedIdentities) {
module.controller('UserFederatedIdentityCtrl', function($scope, $location, realm, user, federatedIdentities, UserFederatedIdentity, Notifications, Dialog) {
$scope.realm = realm;
$scope.user = user;
$scope.federatedIdentities = federatedIdentities;
$scope.hasAnyProvidersToCreate = function() {
return realm.identityProviders.length - $scope.federatedIdentities.length > 0;
}
$scope.removeProviderLink = function(providerLink) {
console.log("Removing provider link: " + providerLink.identityProvider);
Dialog.confirmDelete(providerLink.identityProvider, 'Identity Provider Link', function() {
UserFederatedIdentity.remove({ realm: realm.realm, user: user.id, provider: providerLink.identityProvider }, function() {
Notifications.success("The provider link has been deleted.");
var indexToRemove = $scope.federatedIdentities.indexOf(providerLink);
$scope.federatedIdentities.splice(indexToRemove, 1);
});
});
}
});
module.controller('UserFederatedIdentityAddCtrl', function($scope, $location, realm, user, federatedIdentities, UserFederatedIdentity, Notifications) {
$scope.realm = realm;
$scope.user = user;
$scope.federatedIdentity = {};
var getAvailableProvidersToCreate = function() {
var realmProviders = [];
for (var i=0 ; i<realm.identityProviders.length ; i++) {
var providerAlias = realm.identityProviders[i].alias;
realmProviders.push(providerAlias);
};
for (var i=0 ; i<federatedIdentities.length ; i++) {
var providerAlias = federatedIdentities[i].identityProvider;
var index = realmProviders.indexOf(providerAlias);
realmProviders.splice(index, 1);
}
return realmProviders;
}
$scope.availableProvidersToCreate = getAvailableProvidersToCreate();
$scope.save = function() {
UserFederatedIdentity.save({
realm : realm.realm,
user: user.id,
provider: $scope.federatedIdentity.identityProvider
}, $scope.federatedIdentity, function(data, headers) {
$location.url("/realms/" + realm.realm + '/users/' + $scope.user.id + '/federated-identity');
Notifications.success("Provider link has been created.");
});
};
$scope.cancel = function() {
$location.url("/realms/" + realm.realm + '/users/' + $scope.user.id + '/federated-identity');
};
});
module.controller('UserConsentsCtrl', function($scope, realm, user, userConsents, UserConsents, Notifications) {

View file

@ -171,8 +171,8 @@ module.factory('UserSessionsLoader', function(Loader, UserSessions, $route, $q)
});
});
module.factory('UserFederatedIdentityLoader', function(Loader, UserFederatedIdentity, $route, $q) {
return Loader.query(UserFederatedIdentity, function() {
module.factory('UserFederatedIdentityLoader', function(Loader, UserFederatedIdentities, $route, $q) {
return Loader.query(UserFederatedIdentities, function() {
return {
realm : $route.current.params.realm,
user : $route.current.params.user

View file

@ -305,12 +305,21 @@ module.factory('UserLogout', function($resource) {
user : '@user'
});
});
module.factory('UserFederatedIdentity', function($resource) {
module.factory('UserFederatedIdentities', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/users/:user/federated-identity', {
realm : '@realm',
user : '@user'
});
});
module.factory('UserFederatedIdentity', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/users/:user/federated-identity/:provider', {
realm : '@realm',
user : '@user',
provider : '@provider'
});
});
module.factory('UserConsents', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/users/:user/consents/:client', {
realm : '@realm',

View file

@ -0,0 +1,50 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
<li>{{user.username}}</li>
</ol>
<h1 data-ng-show="create">Add Identity Provider Link</h1>
<kc-tabs-user></kc-tabs-user>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset>
<div class="form-group">
<label class="col-md-2 control-label" for="identityProvider">Identity Provier <span class="required">*</span></label>
<div class="col-sm-6">
<div>
<select class="form-control" id="identityProvider"
ng-model="federatedIdentity.identityProvider"
ng-options="providerAlias for providerAlias in availableProvidersToCreate"
required>
</select>
</div>
</div>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="userId">Identity Provider User ID <span class="required">*</span></label>
<div class="col-md-6">
<input class="form-control" id="userId" type="text" ng-model="federatedIdentity.userId" required>
</div>
<kc-tooltip>Unique ID of the user on the Identity Provider side</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="userName">Identity Provider Username <span class="required">*</span></label>
<div class="col-md-6">
<input class="form-control" id="userName" type="text" ng-model="federatedIdentity.userName" required>
</div>
<kc-tooltip>Username on the Identity Provider side</kc-tooltip>
</div>
</fieldset>
<div class="pull-right form-actions" data-ng-show="access.manageRealm">
<button kc-cancel data-ng-click="cancel()">Cancel</button>
<button kc-save>Save</button>
</div>
</form>
</div>
<kc-menu></kc-menu>

View file

@ -0,0 +1,45 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
<li>{{user.username}}</li>
</ol>
<h1>{{user.username|capitalize}}</h1>
<kc-tabs-user></kc-tabs-user>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="kc-table-actions" colspan="4">
<div class="form-inline">
<div class="pull-right" data-ng-show="hasAnyProvidersToCreate()">
<a class="btn btn-primary" href="#/create/federated-identity/{{realm.realm}}/{{user.id}}">Create</a>
</div>
</div>
</th>
</tr>
<tr data-ng-hide="federatedIdentities.length == 0">
<th>Identity Provider Alias</th>
<th>Provider user ID</th>
<th>Provider username</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="identity in federatedIdentities">
<td>{{identity.identityProvider}}</td>
<td>{{identity.userId}}</td>
<td>{{identity.userName}}</td>
<td class="actions">
<div class="action-div"><i class="pficon pficon-delete" ng-click="removeProviderLink(identity)" tooltip-placement="right" tooltip="Remove Provider Link"></i></div>
</td>
</tr>
<tr data-ng-show="federatedIdentities.length == 0">
<td>No identity provider links available</td>
</tr>
</tbody>
</table>
</div>
<kc-menu></kc-menu>

View file

@ -1,27 +0,0 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
<li>{{user.username}}</li>
</ol>
<h1>{{user.username|capitalize}}</h1>
<kc-tabs-user></kc-tabs-user>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Identity Provider Alias</th>
<th>Username</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="identity in federatedIdentities">
<td>{{identity.identityProvider}}</td>
<td>{{identity.userName}}</td>
</tr>
</tbody>
</table>
</div>
<kc-menu></kc-menu>

View file

@ -1,8 +1,8 @@
<ul class="nav nav-tabs" data-ng-show="!create">
<li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}">Attributes</a></li>
<li ng-class="{active: !path[4] && path[0] != 'create'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}">Attributes</a></li>
<li ng-class="{active: path[4] == 'user-credentials'}" data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-credentials">Credentials</a></li>
<li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/role-mappings">Role Mappings</a></li>
<li ng-class="{active: path[4] == 'consents'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/consents">Consents</a></li>
<li ng-class="{active: path[4] == 'sessions'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/sessions">Sessions</a></li>
<li ng-class="{active: path[4] == 'federated-identity'}" data-ng-show="user.federatedIdentities && user.federatedIdentities.length > 0"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/federated-identity">Identity Provider Links</a></li>
<li ng-class="{active: path[4] == 'federated-identity' || path[1] == 'federated-identity'}" data-ng-show="user.federatedIdentities != null"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/federated-identity">Identity Provider Links</a></li>
</ul>

View file

@ -259,14 +259,8 @@ public class UsersResource {
UserRepresentation rep = ModelToRepresentation.toRepresentation(user);
if (realm.isIdentityFederationEnabled()) {
Set<FederatedIdentityModel> identities = session.users().getFederatedIdentities(user, realm);
if (!identities.isEmpty()) {
List<FederatedIdentityRepresentation> reps = new LinkedList<>();
for (FederatedIdentityModel m : identities) {
reps.add(ModelToRepresentation.toRepresentation(m));
}
rep.setFederatedIdentities(reps);
}
List<FederatedIdentityRepresentation> reps = getFederatedIdentities(user);
rep.setFederatedIdentities(reps);
}
if ((protector != null) && protector.isTemporarilyDisabled(session, realm, rep.getUsername())) {
@ -318,6 +312,10 @@ public class UsersResource {
throw new NotFoundException("User not found");
}
return getFederatedIdentities(user);
}
private List<FederatedIdentityRepresentation> getFederatedIdentities(UserModel user) {
Set<FederatedIdentityModel> identities = session.users().getFederatedIdentities(user, realm);
List<FederatedIdentityRepresentation> result = new ArrayList<FederatedIdentityRepresentation>();

View file

@ -36,17 +36,7 @@ public class LDAPExampleServlet extends HttpServlet {
out.println();
for (Map.Entry<String, Object> claim : idToken.getOtherClaims().entrySet()) {
Object value = claim.getValue();
if (value instanceof List) {
List<String> asList = (List<String>) value;
StringBuilder result = new StringBuilder();
for (String item : asList) {
result.append(item + "<br>");
}
value = result.toString();
}
String value = claim.getValue().toString();
out.printf("<tr><td>%s</td><td>%s</td></tr>", claim.getKey(), value);
out.println();
}