Merge pull request #1846 from patriot1burke/master

default groups UI
This commit is contained in:
Bill Burke 2015-11-20 19:18:21 -05:00
commit 32e069eadc
10 changed files with 224 additions and 7 deletions

View file

@ -27,6 +27,7 @@
<!ENTITY Migration SYSTEM "modules/MigrationFromOlderVersions.xml">
<!ENTITY Email SYSTEM "modules/email.xml">
<!ENTITY Roles SYSTEM "modules/roles.xml">
<!ENTITY Groups SYSTEM "modules/groups.xml">
<!ENTITY DirectAccess SYSTEM "modules/direct-access.xml">
<!ENTITY ServiceAccounts SYSTEM "modules/service-accounts.xml">
<!ENTITY CORS SYSTEM "modules/cors.xml">
@ -133,6 +134,7 @@ This one is short
</chapter>
&AccessTypes;
&Roles;
&Groups;
&DirectAccess;
&ServiceAccounts;
&CORS;

View file

@ -0,0 +1,31 @@
<chapter id="groups">
<title>Groups</title>
<para>
Groups in Keycloak allow you to manage a common set of attributes and role mappings for a large set of users.
Users can be members of zero or more groups. Users inherit the attributes and role mappings assign to each group.
As an admin this makes it easy for you to manage permissions for a user in one place.
</para>
<para>
Groups are hierarchical. A group can have many subgroups, but a group can only have one parent. Subgroups inherit
the attributes and role mappings from the parent. This applies to user as well. So, if you have a parent group and a child group
and a user that only belongs to the child group, the user inherits the attributes and role mappings of both the
parent and child.
</para>
<section>
<title>Groups vs. Roles</title>
<para>
In the IT world the concepts of Group and Role are often blurred and interchangeable. In Keycloak, Groups are just
a collection of users that you can apply roles and attributes to in one place. Roles are used to assign permissions
and access control.
</para>
<para>
Keycloak Roles have the concept of a Composite Role. A role can be associated with one or more additional roles.
This is called a Composite Role. If a user has a role mapping to the Composite Role, they inherit all the roles associated
with the composite. So what's the difference from a Keycloak Group and a Composite Role? Logically they could be
used for the same exact thing. The difference is conceptual. Composite roles should be used to compose the
permission model of your set of services and applications. So, roles become a set of permissions. Groups on the
other hand, would be a set of users that have a set of permissions. Use Groups to manage users, composite roles to
manage applications and services.
</para>
</section>
</chapter>

View file

@ -1,7 +1,7 @@
<chapter id="roles">
<title>Roles</title>
<para>
In Keycloak, roles (or permissions) can be defined globally at the realm level, or individually per application.
In Keycloak, roles can be defined globally at the realm level, or individually per application.
Each role has a name which must be unique at the level it is defined in, i.e. you can have only one "admin" role at
the realm level. You may have that a role named "admin" within an Application too, but "admin" must be unique
for that application.

View file

@ -697,6 +697,18 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'GroupRoleMappingCtrl'
})
.when('/realms/:realm/default-groups', {
templateUrl : resourceUrl + '/partials/default-groups.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
groups : function(GroupListLoader) {
return GroupListLoader();
}
},
controller : 'DefaultGroupsCtrl'
})
.when('/create/role/:realm/clients/:client', {
@ -1995,6 +2007,15 @@ module.directive('kcTabsGroup', function () {
}
});
module.directive('kcTabsGroupList', function () {
return {
scope: true,
restrict: 'E',
replace: true,
templateUrl: resourceUrl + '/templates/kc-tabs-group-list.html'
}
});
module.directive('kcTabsClient', function () {
return {
scope: true,

View file

@ -366,3 +366,63 @@ module.controller('GroupMembersCtrl', function($scope, realm, group, GroupMember
});
module.controller('DefaultGroupsCtrl', function($scope, $route, realm, groups, DefaultGroups, Notifications, $location, Dialog) {
$scope.realm = realm;
$scope.groupList = groups;
$scope.selectedGroup = null;
$scope.tree = [];
DefaultGroups.query({realm: realm.realm}, function(data) {
$scope.defaultGroups = data;
});
$scope.addDefaultGroup = function() {
if (!$scope.tree.currentNode) {
Notifications.error('Please select a group to add');
return;
};
DefaultGroups.update({realm: realm.realm, groupId: $scope.tree.currentNode.id}, function() {
Notifications.success('Added default group');
$route.reload();
});
};
$scope.removeDefaultGroup = function() {
DefaultGroups.remove({realm: realm.realm, groupId: $scope.selectedGroup.id}, function() {
Notifications.success('Removed default group');
$route.reload();
});
};
var isLeaf = function(node) {
return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0);
};
$scope.getGroupClass = function(node) {
if (node.id == "realm") {
return 'pficon pficon-users';
}
if (isLeaf(node)) {
return 'normal';
}
if (node.subGroups.length && node.collapsed) return 'collapsed';
if (node.subGroups.length && !node.collapsed) return 'expanded';
return 'collapsed';
}
$scope.getSelectedClass = function(node) {
if (node.selected) {
return 'selected';
} else if ($scope.cutNode && $scope.cutNode.id == node.id) {
return 'cut';
}
return undefined;
}
});

View file

@ -1559,4 +1559,15 @@ module.factory('UserGroupMapping', function($resource) {
method : 'PUT'
}
});
});
});
module.factory('DefaultGroups', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/default-groups/:groupId', {
realm : '@realm',
groupId : '@groupId'
}, {
update : {
method : 'PUT'
}
});
});

View file

@ -0,0 +1,80 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<kc-tabs-group-list></kc-tabs-group-list>
<form class="form-horizontal" name="realmForm" novalidate>
<div class="form-group" kc-read-only="!access.manageRealm">
<label class="col-md-1 control-label" class="control-label"></label>
<div class="col-md-8" >
<div class="row">
<div class="col-md-5">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="kc-table-actions" colspan="5">
<div class="form-inline">
<label class="control-label">Default Groups</label>
<kc-tooltip>Newly created or registered users will automatically be added to these groups</kc-tooltip>
<div class="pull-right" data-ng-show="access.manageRealm">
<button id="removeDefaultGroup" class="btn btn-default" ng-click="removeDefaultGroup()">Remove</button>
</div>
</div>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<select id="defaultGroups" class="form-control" size=5
ng-model="selectedGroup"
ng-options="r.path for r in defaultGroups">
<option style="display:none" value="">select a type</option>
</select>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-5">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="kc-table-actions" colspan="5">
<div class="form-inline">
<label class="control-label">Available Groups</label>
<kc-tooltip>Select a group you want to add as a default.</kc-tooltip>
<div class="pull-right" data-ng-show="access.manageRealm">
<button id="addDefaultGroup" class="btn btn-default" ng-click="addDefaultGroup()">Add</button>
</div>
</div>
</th>
</tr>
</thead>
<tbody>
<tr>
<td> <div
tree-id="tree"
angular-treeview="true"
tree-model="groupList"
node-id="id"
node-label="name"
node-children="subGroups" >
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</form>
</div>
<kc-menu></kc-menu>

View file

@ -1,8 +1,5 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1>
<span>User Groups</span>
<kc-tooltip>User groups</kc-tooltip>
</h1>
<kc-tabs-group-list></kc-tabs-group-list>
<table class="table table-striped table-bordered">
<thead>

View file

@ -0,0 +1,11 @@
<div data-ng-controller="GroupTabCtrl">
<h1>
<span>User Groups</span>
</h1>
<ul class="nav nav-tabs">
<li ng-class="{active: path[2] == 'groups'}"><a href="#/realms/{{realm.realm}}/groups">Groups</a></li>
<li ng-class="{active: path[2] == 'default-groups'}"><a href="#/realms/{{realm.realm}}/default-groups">Default Groups</a><kc-tooltip>Set of groups that new users will automatically join.</kc-tooltip>
</li>
</ul>
</div>

View file

@ -22,6 +22,7 @@ import org.keycloak.saml.SAML2LogoutResponseBuilder;
import org.keycloak.saml.SAMLRequestParser;
import org.keycloak.saml.SignatureAlgorithm;
import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.Base64;
import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
@ -33,6 +34,7 @@ import org.keycloak.common.util.MultivaluedHashMap;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import java.net.URI;
import java.security.PublicKey;
import java.security.Signature;
import java.util.HashSet;
@ -312,7 +314,9 @@ public abstract class SamlAuthenticator {
}
final SamlPrincipal principal = new SamlPrincipal(principalName, principalName, subjectNameID.getFormat().toString(), attributes, friendlyAttributes);
URI nameFormat = subjectNameID.getFormat();
String nameFormatString = nameFormat == null ? JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString();
final SamlPrincipal principal = new SamlPrincipal(principalName, principalName, nameFormatString, attributes, friendlyAttributes);
String index = authn == null ? null : authn.getSessionIndex();
final String sessionIndex = index;
SamlSession account = new SamlSession(principal, roles, sessionIndex);