commit
6cc76b2e3d
88 changed files with 4314 additions and 268 deletions
52
connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
Normal file → Executable file
52
connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
Normal file → Executable file
|
@ -1,6 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
<changeSet author="mposolda@redhat.com" id="1.7.0">
|
||||
<changeSet author="bburke@redhat.com" id="1.7.0">
|
||||
<createTable tableName="KEYCLOAK_GROUP">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="NAME" type="VARCHAR(255)"/>
|
||||
<column name="PARENT_GROUP" type="VARCHAR(36)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="GROUP_ROLE_MAPPING">
|
||||
<column name="ROLE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="GROUP_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="GROUP_ATTRIBUTE">
|
||||
<column name="ID" type="VARCHAR(36)" defaultValue="sybase-needs-something-here">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="GROUP_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="USER_GROUP_MEMBERSHIP">
|
||||
<column name="GROUP_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<addColumn tableName="IDENTITY_PROVIDER">
|
||||
<column name="FIRST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
|
||||
|
@ -9,5 +45,19 @@
|
|||
</addColumn>
|
||||
<dropColumn tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD"/>
|
||||
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP" tableName="KEYCLOAK_GROUP"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="KEYCLOAK_GROUP" constraintName="FK_GROUP_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP_ATTRIBUTE_PK" tableName="GROUP_ATTRIBUTE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="GROUP_ATTRIBUTE" constraintName="FK_GROUP_ATTRIBUTE_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
|
||||
|
||||
<addPrimaryKey columnNames="GROUP_ID, USER_ID" constraintName="CONSTRAINT_USER_GROUP" tableName="USER_GROUP_MEMBERSHIP"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_GROUP_MEMBERSHIP" constraintName="FK_USER_GROUP_USER" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
|
||||
<addPrimaryKey columnNames="ROLE_ID, GROUP_ID" constraintName="CONSTRAINT_GROUP_ROLE" tableName="GROUP_ROLE_MAPPING"/>
|
||||
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
|
||||
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
|
||||
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -31,6 +31,10 @@
|
|||
<class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class>
|
||||
<class>org.keycloak.models.jpa.session.PersistentUserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.session.PersistentClientSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.GroupEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.GroupAttributeEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.GroupRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserGroupMembershipEntity</class>
|
||||
|
||||
<!-- JpaAuditProviders -->
|
||||
<class>org.keycloak.events.jpa.EventEntity</class>
|
||||
|
|
76
core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java
Executable file
76
core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java
Executable file
|
@ -0,0 +1,76 @@
|
|||
package org.keycloak.representations.idm;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnore;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class GroupRepresentation {
|
||||
protected String id;
|
||||
protected String name;
|
||||
protected Map<String, List<String>> attributes;
|
||||
protected List<String> realmRoles;
|
||||
protected Map<String, List<String>> clientRoles;
|
||||
protected List<GroupRepresentation> subGroups;
|
||||
|
||||
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<String> getRealmRoles() {
|
||||
return realmRoles;
|
||||
}
|
||||
|
||||
public void setRealmRoles(List<String> realmRoles) {
|
||||
this.realmRoles = realmRoles;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getClientRoles() {
|
||||
return clientRoles;
|
||||
}
|
||||
|
||||
public void setClientRoles(Map<String, List<String>> clientRoles) {
|
||||
this.clientRoles = clientRoles;
|
||||
}
|
||||
|
||||
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(Map<String, List<String>> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public GroupRepresentation singleAttribute(String name, String value) {
|
||||
if (this.attributes == null) attributes = new HashMap<>();
|
||||
attributes.put(name, Arrays.asList(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<GroupRepresentation> getSubGroups() {
|
||||
return subGroups;
|
||||
}
|
||||
|
||||
public void setSubGroups(List<GroupRepresentation> subGroups) {
|
||||
this.subGroups = subGroups;
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ public class RealmRepresentation {
|
|||
protected String certificate;
|
||||
protected String codeSecret;
|
||||
protected RolesRepresentation roles;
|
||||
protected List<GroupRepresentation> groups;
|
||||
protected List<String> defaultRoles;
|
||||
@Deprecated
|
||||
protected Set<String> requiredCredentials;
|
||||
|
@ -775,4 +776,12 @@ public class RealmRepresentation {
|
|||
public void setClientAuthenticationFlow(String clientAuthenticationFlow) {
|
||||
this.clientAuthenticationFlow = clientAuthenticationFlow;
|
||||
}
|
||||
|
||||
public List<GroupRepresentation> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public void setGroups(List<GroupRepresentation> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UserRoleMappingRepresentation {
|
||||
protected String self; // link
|
||||
protected String username;
|
||||
protected Set<String> roles;
|
||||
|
||||
public String getSelf() {
|
||||
return self;
|
||||
}
|
||||
|
||||
public void setSelf(String self) {
|
||||
this.self = self;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public Set<String> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(Set<String> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
public UserRoleMappingRepresentation role(String role) {
|
||||
if (this.roles == null) this.roles = new HashSet<String>();
|
||||
this.roles.add(role);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.examples.federation.properties;
|
||||
|
||||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
|
@ -106,6 +107,12 @@ public abstract class BasePropertiesFederationProvider implements UserFederation
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, GroupModel group) {
|
||||
// complete we dont'care if a role is removed
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* See if the user is still in the properties file
|
||||
*
|
||||
|
|
|
@ -63,4 +63,6 @@ public class ClasspathPropertiesFederationProvider extends BasePropertiesFederat
|
|||
throw new IllegalStateException("Remove not supported");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
6
federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
Normal file → Executable file
6
federation/kerberos/src/main/java/org/keycloak/federation/kerberos/KerberosFederationProvider.java
Normal file → Executable file
|
@ -12,6 +12,7 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
||||
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
||||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
|
@ -105,6 +106,11 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, GroupModel group) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(RealmModel realm, UserModel local) {
|
||||
// KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
|||
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
|
||||
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
|
||||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
|
@ -319,6 +320,11 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
// TODO: Maybe mappers callback to ensure role deletion propagated to LDAP by RoleLDAPFederationMapper?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, GroupModel group) {
|
||||
|
||||
}
|
||||
|
||||
public boolean validPassword(RealmModel realm, UserModel user, String password) {
|
||||
if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) {
|
||||
// Use Kerberos JAAS (Krb5LoginModule)
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
<script src="${resourceUrl}/lib/angular/angular-sanitize.js"></script>
|
||||
<script src="${resourceUrl}/lib/angular/angular-translate.js"></script>
|
||||
<script src="${resourceUrl}/lib/angular/angular-translate-loader-url.js"></script>
|
||||
<script src="${resourceUrl}/lib/angular/treeview/angular.treeview.js"></script>
|
||||
<script src="${resourceUrl}/lib/angular/ui-bootstrap-tpls-0.11.0.js"></script>
|
||||
|
||||
<script src="${resourceUrl}/lib/angular/select2.js" type="text/javascript"></script>
|
||||
|
@ -37,6 +38,7 @@
|
|||
<script src="${resourceUrl}/js/controllers/realm.js" type="text/javascript"></script>
|
||||
<script src="${resourceUrl}/js/controllers/clients.js" type="text/javascript"></script>
|
||||
<script src="${resourceUrl}/js/controllers/users.js" type="text/javascript"></script>
|
||||
<script src="${resourceUrl}/js/controllers/groups.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>
|
||||
</head>
|
||||
|
|
|
@ -7,7 +7,7 @@ var configUrl = consoleBaseUrl + "/config";
|
|||
|
||||
var auth = {};
|
||||
|
||||
var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'ui.bootstrap', 'ui.select2', 'angularFileUpload', 'pascalprecht.translate', 'ngCookies', 'ngSanitize']);
|
||||
var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'ui.bootstrap', 'ui.select2', 'angularFileUpload', 'angularTreeview', 'pascalprecht.translate', 'ngCookies', 'ngSanitize']);
|
||||
var resourceRequests = 0;
|
||||
var loadingTimer = -1;
|
||||
|
||||
|
@ -583,6 +583,73 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
},
|
||||
controller : 'RoleListCtrl'
|
||||
})
|
||||
.when('/realms/:realm/groups', {
|
||||
templateUrl : resourceUrl + '/partials/group-list.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
groups : function(GroupListLoader) {
|
||||
return GroupListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'GroupListCtrl'
|
||||
})
|
||||
.when('/create/group/:realm/parent/:parentId', {
|
||||
templateUrl : resourceUrl + '/partials/create-group.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
parentId : function($route) {
|
||||
return $route.current.params.parentId;
|
||||
}
|
||||
},
|
||||
controller : 'GroupCreateCtrl'
|
||||
})
|
||||
.when('/realms/:realm/groups/:group', {
|
||||
templateUrl : resourceUrl + '/partials/group-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
group : function(GroupLoader) {
|
||||
return GroupLoader();
|
||||
}
|
||||
},
|
||||
controller : 'GroupDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/groups/:group/attributes', {
|
||||
templateUrl : resourceUrl + '/partials/group-attributes.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
group : function(GroupLoader) {
|
||||
return GroupLoader();
|
||||
}
|
||||
},
|
||||
controller : 'GroupDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/groups/:group/role-mappings', {
|
||||
templateUrl : resourceUrl + '/partials/group-role-mappings.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
group : function(GroupLoader) {
|
||||
return GroupLoader();
|
||||
},
|
||||
clients : function(ClientListLoader) {
|
||||
return ClientListLoader();
|
||||
},
|
||||
client : function() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
controller : 'GroupRoleMappingCtrl'
|
||||
})
|
||||
|
||||
|
||||
.when('/create/role/:realm/clients/:client', {
|
||||
templateUrl : resourceUrl + '/partials/client-role-detail.html',
|
||||
|
@ -1871,6 +1938,15 @@ module.directive('kcTabsUser', function () {
|
|||
}
|
||||
});
|
||||
|
||||
module.directive('kcTabsGroup', function () {
|
||||
return {
|
||||
scope: true,
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: resourceUrl + '/templates/kc-tabs-group.html'
|
||||
}
|
||||
});
|
||||
|
||||
module.directive('kcTabsClient', function () {
|
||||
return {
|
||||
scope: true,
|
||||
|
|
|
@ -0,0 +1,321 @@
|
|||
module.controller('GroupListCtrl', function($scope, $route, realm, groups, Groups, Group, GroupChildren, Notifications, $location, Dialog) {
|
||||
$scope.realm = realm;
|
||||
$scope.groupList = [
|
||||
{"id" : "realm", "name": "Groups",
|
||||
"subGroups" : groups}
|
||||
];
|
||||
|
||||
$scope.tree = [];
|
||||
|
||||
$scope.edit = function(selected) {
|
||||
$location.url("/realms/" + realm.realm + "/groups/" + selected.id);
|
||||
}
|
||||
|
||||
$scope.cut = function(selected) {
|
||||
$scope.cutNode = selected;
|
||||
}
|
||||
|
||||
$scope.isDisabled = function() {
|
||||
if (!$scope.tree.currentNode) return true;
|
||||
return $scope.tree.currentNode.id == 'realm';
|
||||
}
|
||||
|
||||
$scope.paste = function(selected) {
|
||||
if (selected == null) return;
|
||||
if ($scope.cutNode == null) return;
|
||||
if (selected.id == 'realm') {
|
||||
Groups.save({realm: realm.realm}, {id:$scope.cutNode.id}, function() {
|
||||
$route.reload();
|
||||
Notifications.success("Group moved.");
|
||||
|
||||
});
|
||||
|
||||
} else {
|
||||
GroupChildren.save({realm: realm.realm, groupId: selected.id}, {id:$scope.cutNode.id}, function() {
|
||||
$route.reload();
|
||||
Notifications.success("Group moved.");
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$scope.remove = function(selected) {
|
||||
if (selected == null) return;
|
||||
Dialog.confirmDelete(selected.name, 'group', function() {
|
||||
Group.remove({ realm: realm.realm, groupId : selected.id }, function() {
|
||||
$route.reload();
|
||||
Notifications.success("The group has been deleted.");
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
$scope.createGroup = function(selected) {
|
||||
var parent = 'realm';
|
||||
if (selected) {
|
||||
parent = selected.id;
|
||||
}
|
||||
$location.url("/create/group/" + realm.realm + '/parent/' + parent);
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.controller('GroupCreateCtrl', function($scope, $route, realm, parentId, Groups, Group, GroupChildren, Notifications, $location) {
|
||||
$scope.realm = realm;
|
||||
$scope.group = {};
|
||||
$scope.save = function() {
|
||||
console.log('save!!!');
|
||||
if (parentId == 'realm') {
|
||||
console.log('realm')
|
||||
Groups.save({realm: realm.realm, groupId: parentId}, $scope.group, function(data, headers) {
|
||||
var l = headers().location;
|
||||
|
||||
|
||||
var id = l.substring(l.lastIndexOf("/") + 1);
|
||||
|
||||
$location.url("/realms/" + realm.realm + "/groups/" + id);
|
||||
Notifications.success("Group Created.");
|
||||
})
|
||||
|
||||
} else {
|
||||
GroupChildren.save({realm: realm.realm, groupId: parentId}, $scope.group, function(data, headers) {
|
||||
var l = headers().location;
|
||||
|
||||
|
||||
var id = l.substring(l.lastIndexOf("/") + 1);
|
||||
|
||||
$location.url("/realms/" + realm.realm + "/groups/" + id);
|
||||
Notifications.success("Group Created.");
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
$scope.cancel = function() {
|
||||
$location.url("/realms/" + realm.realm + "/groups");
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('GroupTabCtrl', function(Dialog, $scope, Current, Group, Notifications, $location) {
|
||||
$scope.removeGroup = function() {
|
||||
Dialog.confirmDelete($scope.group.name, 'group', function() {
|
||||
Group.remove({
|
||||
realm : Current.realm.realm,
|
||||
groupId : $scope.group.id
|
||||
}, function() {
|
||||
$location.url("/realms/" + Current.realm.realm + "/groups");
|
||||
Notifications.success("The group has been deleted.");
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('GroupDetailCtrl', function(Dialog, $scope, realm, group, Group, Notifications, $location) {
|
||||
$scope.realm = realm;
|
||||
|
||||
if (!group.attributes) {
|
||||
group.attributes = {}
|
||||
}
|
||||
convertAttributeValuesToString(group);
|
||||
|
||||
|
||||
$scope.group = angular.copy(group);
|
||||
|
||||
$scope.changed = false; // $scope.create;
|
||||
$scope.$watch('group', function() {
|
||||
if (!angular.equals($scope.group, group)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
convertAttributeValuesToLists();
|
||||
|
||||
Group.update({
|
||||
realm: realm.realm,
|
||||
groupId: $scope.group.id
|
||||
}, $scope.group, function () {
|
||||
$scope.changed = false;
|
||||
convertAttributeValuesToString($scope.group);
|
||||
group = angular.copy($scope.group);
|
||||
Notifications.success("Your changes have been saved to the group.");
|
||||
});
|
||||
};
|
||||
|
||||
function convertAttributeValuesToLists() {
|
||||
var attrs = $scope.group.attributes;
|
||||
for (var attribute in attrs) {
|
||||
if (typeof attrs[attribute] === "string") {
|
||||
var attrVals = attrs[attribute].split("##");
|
||||
attrs[attribute] = attrVals;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertAttributeValuesToString(group) {
|
||||
var attrs = group.attributes;
|
||||
for (var attribute in attrs) {
|
||||
if (typeof attrs[attribute] === "object") {
|
||||
var attrVals = attrs[attribute].join("##");
|
||||
attrs[attribute] = attrVals;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.group = angular.copy(group);
|
||||
$scope.changed = false;
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
$location.url("/realms/" + realm.realm + "/groups");
|
||||
};
|
||||
|
||||
$scope.addAttribute = function() {
|
||||
$scope.group.attributes[$scope.newAttribute.key] = $scope.newAttribute.value;
|
||||
delete $scope.newAttribute;
|
||||
}
|
||||
|
||||
$scope.removeAttribute = function(key) {
|
||||
delete $scope.group.attributes[key];
|
||||
}
|
||||
});
|
||||
|
||||
module.controller('GroupRoleMappingCtrl', function($scope, $http, realm, group, clients, client, Notifications, GroupRealmRoleMapping,
|
||||
GroupClientRoleMapping, GroupAvailableRealmRoleMapping, GroupAvailableClientRoleMapping,
|
||||
GroupCompositeRealmRoleMapping, GroupCompositeClientRoleMapping) {
|
||||
$scope.realm = realm;
|
||||
$scope.group = group;
|
||||
$scope.selectedRealmRoles = [];
|
||||
$scope.selectedRealmMappings = [];
|
||||
$scope.realmMappings = [];
|
||||
$scope.clients = clients;
|
||||
$scope.client = client;
|
||||
$scope.clientRoles = [];
|
||||
$scope.clientComposite = [];
|
||||
$scope.selectedClientRoles = [];
|
||||
$scope.selectedClientMappings = [];
|
||||
$scope.clientMappings = [];
|
||||
$scope.dummymodel = [];
|
||||
|
||||
$scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
$scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
$scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
|
||||
$scope.addRealmRole = function() {
|
||||
var roles = $scope.selectedRealmRoles;
|
||||
$scope.selectedRealmRoles = [];
|
||||
$http.post(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/realm',
|
||||
roles).success(function() {
|
||||
$scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
$scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
$scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
$scope.selectedRealmMappings = [];
|
||||
$scope.selectRealmRoles = [];
|
||||
if ($scope.targetClient) {
|
||||
console.log('load available');
|
||||
$scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.selectedClientRoles = [];
|
||||
$scope.selectedClientMappings = [];
|
||||
}
|
||||
Notifications.success("Role mappings updated.");
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteRealmRole = function() {
|
||||
$http.delete(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/realm',
|
||||
{data : $scope.selectedRealmMappings, headers : {"content-type" : "application/json"}}).success(function() {
|
||||
$scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
$scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
$scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
$scope.selectedRealmMappings = [];
|
||||
$scope.selectRealmRoles = [];
|
||||
if ($scope.targetClient) {
|
||||
console.log('load available');
|
||||
$scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.selectedClientRoles = [];
|
||||
$scope.selectedClientMappings = [];
|
||||
}
|
||||
Notifications.success("Role mappings updated.");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addClientRole = function() {
|
||||
$http.post(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/clients/' + $scope.targetClient.id,
|
||||
$scope.selectedClientRoles).success(function() {
|
||||
$scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.selectedClientRoles = [];
|
||||
$scope.selectedClientMappings = [];
|
||||
$scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
$scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
Notifications.success("Role mappings updated.");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteClientRole = function() {
|
||||
$http.delete(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/clients/' + $scope.targetClient.id,
|
||||
{data : $scope.selectedClientMappings, headers : {"content-type" : "application/json"}}).success(function() {
|
||||
$scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.selectedClientRoles = [];
|
||||
$scope.selectedClientMappings = [];
|
||||
$scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
$scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id});
|
||||
Notifications.success("Role mappings updated.");
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
$scope.changeClient = function() {
|
||||
if ($scope.targetClient) {
|
||||
$scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
$scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id});
|
||||
} else {
|
||||
$scope.clientRoles = null;
|
||||
$scope.clientMappings = null;
|
||||
$scope.clientComposite = null;
|
||||
}
|
||||
$scope.selectedClientRoles = [];
|
||||
$scope.selectedClientMappings = [];
|
||||
};
|
||||
|
||||
|
||||
|
||||
});
|
|
@ -1993,3 +1993,7 @@ module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, flow
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -458,6 +458,24 @@ module.factory('AuthenticationConfigLoader', function(Loader, AuthenticationConf
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('GroupListLoader', function(Loader, Groups, $route, $q) {
|
||||
return Loader.query(Groups, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('GroupLoader', function(Loader, Group, $route, $q) {
|
||||
return Loader.get(Group, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
groupId : $route.current.params.group
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1442,6 +1442,78 @@ module.service('SelectRoleDialog', function($modal) {
|
|||
return dialog
|
||||
});
|
||||
|
||||
module.factory('Group', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/groups/:groupId', {
|
||||
realm : '@realm',
|
||||
userId : '@groupId'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('GroupChildren', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/children', {
|
||||
realm : '@realm',
|
||||
groupId : '@groupId'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('Groups', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/groups', {
|
||||
realm : '@realm'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('GroupRealmRoleMapping', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/realm', {
|
||||
realm : '@realm',
|
||||
groupId : '@groupId'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('GroupCompositeRealmRoleMapping', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/realm/composite', {
|
||||
realm : '@realm',
|
||||
groupId : '@groupId'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('GroupAvailableRealmRoleMapping', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/realm/available', {
|
||||
realm : '@realm',
|
||||
groupId : '@groupId'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.factory('GroupClientRoleMapping', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/clients/:client', {
|
||||
realm : '@realm',
|
||||
groupId : '@groupId',
|
||||
client : "@client"
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('GroupAvailableClientRoleMapping', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/clients/:client/available', {
|
||||
realm : '@realm',
|
||||
groupId : '@groupId',
|
||||
client : "@client"
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('GroupCompositeClientRoleMapping', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/clients/:client/composite', {
|
||||
realm : '@realm',
|
||||
groupId : '@groupId',
|
||||
client : "@client"
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<div>
|
||||
<h1>Create Group</h1>
|
||||
</div>
|
||||
<form class="form-horizontal" name="clientForm" novalidate>
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label"for="name">Name <span class="required">*</span></label>
|
||||
<div class="col-md-6">
|
||||
<input class="form-control" type="text" id="name" name="name" data-ng-model="group.name" autofocus
|
||||
required >
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save>Save</button>
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -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}}/groups">Groups</a></li>
|
||||
<li>{{group.name}}</li>
|
||||
</ol>
|
||||
|
||||
<kc-tabs-group></kc-tabs-group>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageUsers">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="(key, value) in group.attributes">
|
||||
<td>{{key}}</td>
|
||||
<td><input ng-model="group.attributes[key]" class="form-control" type="text" name="{{key}}" id="attribute-{{key}}" /></td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" data-ng-click="removeAttribute(key)">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input ng-model="newAttribute.key" class="form-control" type="text" id="newAttributeKey" /></td>
|
||||
<td><input ng-model="newAttribute.value" class="form-control" type="text" id="newAttributeValue" /></td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" data-ng-click="addAttribute()">Add</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="form-group" data-ng-show="access.manageUsers">
|
||||
<div class="col-md-12">
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -0,0 +1,28 @@
|
|||
<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}}/groups">Groups</a></li>
|
||||
<li>{{group.name}}</li>
|
||||
</ol>
|
||||
|
||||
<kc-tabs-group></kc-tabs-group>
|
||||
<form class="form-horizontal" name="clientForm" novalidate>
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label"for="name">Name <span class="required">*</span></label>
|
||||
<div class="col-md-6">
|
||||
<input class="form-control" type="text" id="name" name="name" data-ng-model="group.name" autofocus
|
||||
required >
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-group" data-ng-show="access.manageUsers">
|
||||
<div class="col-md-10 col-md-offset-2">
|
||||
<button kc-save data-ng-disabled="!changed">Save</button>
|
||||
<button kc-reset data-ng-disabled="!changed" data-ng-click="cancel()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -0,0 +1,40 @@
|
|||
<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>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="kc-table-actions" colspan="5">
|
||||
<div class="form-inline">
|
||||
<div class="pull-right" data-ng-show="access.manageUsers">
|
||||
<button id="createGroup" class="btn btn-default" ng-click="createGroup(tree.currentNode)">New</button>
|
||||
<button id="editGroup" ng-disabled="isDisabled()" class="btn btn-default" ng-click="edit(tree.currentNode)">Edit</button>
|
||||
<button id="cutGroup" ng-disabled="isDisabled()" class="btn btn-default" ng-click="cut(tree.currentNode)">Cut</button>
|
||||
<button id="pasteGroup" ng-disabled="!cutNode" class="btn btn-default" ng-click="paste(tree.currentNode)">Paste</button>
|
||||
<button id="removeGroup" ng-disabled="isDisabled()" class="btn btn-default" ng-click="remove(tree.currentNode)">Delete</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>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -0,0 +1,101 @@
|
|||
<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}}/groups">Groups</a></li>
|
||||
<li>{{group.name}}</li>
|
||||
</ol>
|
||||
|
||||
<kc-tabs-group></kc-tabs-group>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<div class="form-group" kc-read-only="!access.manageUsers">
|
||||
<label class="col-md-2 control-label" class="control-label">Realm Roles</label>
|
||||
|
||||
<div class="col-md-10">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="available">Available Roles</label>
|
||||
<select id="available" class="form-control" multiple size="5"
|
||||
ng-multiple="true"
|
||||
ng-model="selectedRealmRoles"
|
||||
ng-options="r.name for r in realmRoles">
|
||||
</select>
|
||||
<button ng-disabled="selectedRealmRoles.length == 0" ng-disabled="c.length == 0" class="btn btn-default" type="submit" ng-click="addRealmRole()">
|
||||
Add selected <i class="fa fa-angle-right"></i>
|
||||
</button>
|
||||
<kc-tooltip>Realm roles that can be assigned to the group.</kc-tooltip>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="assigned">Assigned Roles</label>
|
||||
<kc-tooltip>Realm roles mapped to the group</kc-tooltip>
|
||||
<select id="assigned" class="form-control" multiple size=5
|
||||
ng-multiple="true"
|
||||
ng-model="selectedRealmMappings"
|
||||
ng-options="r.name for r in realmMappings">
|
||||
</select>
|
||||
<button ng-disabled="selectedRealmMappings.length == 0" class="btn btn-default" type="submit" ng-click="deleteRealmRole()">
|
||||
<i class="fa fa-angle-double-left"></i> Remove selected
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="realm-composite">Effective Roles</label>
|
||||
<kc-tooltip>All realm role mappings. Some roles here might be inherited from a mapped composite role.</kc-tooltip>
|
||||
<select id="realm-composite" class="form-control" multiple size=5
|
||||
disabled="true"
|
||||
ng-model="dummymodel"
|
||||
ng-options="r.name for r in realmComposite">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" class="control-label">
|
||||
<span>Client Roles</span>
|
||||
<select class="form-control" id="clients" name="clients" ng-change="changeClient()" ng-model="targetClient" ng-options="a.clientId for a in clients" ng-disabled="false"></select>
|
||||
</label>
|
||||
<div class="col-md-10" kc-read-only="!access.manageUsers">
|
||||
<div class="row" data-ng-hide="targetClient">
|
||||
<div class="col-md-4"><span class="text-muted">Select client to view roles for client</span></div>
|
||||
</div>
|
||||
<div class="row" data-ng-show="targetClient">
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="available-client">Available Roles</label>
|
||||
<kc-tooltip>Assignable roles from this client.</kc-tooltip>
|
||||
<select id="available-client" class="form-control" multiple size="5"
|
||||
ng-multiple="true"
|
||||
ng-model="selectedClientRoles"
|
||||
ng-options="r.name for r in clientRoles">
|
||||
</select>
|
||||
<button ng-disabled="selectedClientRoles.length == 0" class="btn btn-default" type="submit" ng-click="addClientRole()">
|
||||
Add selected <i class="fa fa-angle-double-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="assigned-client">Assigned Roles</label>
|
||||
<kc-tooltip>Role mappings for this client.</kc-tooltip>
|
||||
<select id="assigned-client" class="form-control" multiple size=5
|
||||
ng-multiple="true"
|
||||
ng-model="selectedClientMappings"
|
||||
ng-options="r.name for r in clientMappings">
|
||||
</select>
|
||||
<button ng-disabled="selectedClientMappings.length == 0" class="btn btn-default" type="submit" ng-click="deleteClientRole()">
|
||||
<i class="fa fa-angle-double-left"></i> Remove selected
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="client-composite">Effective Roles <span tooltip-placement="right" tooltip-trigger="mouseover mouseout" tooltip="Role mappings for this client. Some roles here might be inherited from a mapped composite role." class="fa fa-info-circle"></span></label>
|
||||
<select id="client-composite" class="form-control" multiple size=5
|
||||
disabled="true"
|
||||
ng-model="dummymodel"
|
||||
ng-options="r.name for r in clientComposite">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -40,7 +40,8 @@
|
|||
<div class="nav-category" data-ng-show="current.realm">
|
||||
<h2>Manage</h2>
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users"><span class="pficon pficon-users"></span> Users</a></li>
|
||||
<li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'groups' || path[1] == 'group') && 'active'"><a href="#/realms/{{realm.realm}}/groups"><span class="pficon pficon-users"></span> Groups</a></li>
|
||||
<li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users"><span class="pficon pficon-user"></span> Users</a></li>
|
||||
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'sessions') && 'active'"><a href="#/realms/{{realm.realm}}/sessions/realm"><i class="fa fa-clock-o"></i> Sessions</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"><i class="fa fa-calendar"></i> Events</a></li>
|
||||
</ul>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<div data-ng-controller="GroupTabCtrl">
|
||||
<h1>
|
||||
{{group.name|capitalize}}
|
||||
<i id="removeGroup" class="pficon pficon-delete clickable" data-ng-show="access.manageUsers" data-ng-click="removeGroup()"></i>
|
||||
</h1>
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}">Settings</a></li>
|
||||
<li ng-class="{active: path[4] == 'attributes'}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/attributes">Attributes</a></li>
|
||||
<li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/role-mappings">Role Mappings</a></li>
|
||||
<li ng-class="{active: path[4] == 'members'}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/members">Members</a></li>
|
||||
</ul>
|
||||
</div>
|
2
forms/common-themes/src/main/resources/theme/keycloak/admin/theme.properties
Normal file → Executable file
2
forms/common-themes/src/main/resources/theme/keycloak/admin/theme.properties
Normal file → Executable file
|
@ -1,3 +1,3 @@
|
|||
parent=base
|
||||
import=common/keycloak
|
||||
styles=lib/patternfly/css/patternfly.css lib/select2-3.4.1/select2.css css/styles.css
|
||||
styles=lib/patternfly/css/patternfly.css lib/select2-3.4.1/select2.css css/styles.css lib/angular/treeview/css/angular.treeview.css
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Steve
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,122 @@
|
|||
Angular Treeview
|
||||
================
|
||||
|
||||
Pure [AngularJS](http://www.angularjs.org) based tree menu directive.
|
||||
|
||||
[![ScreenShot](https://github.com/eu81273/angular.treeview/raw/master/img/preview.png)](http://jsfiddle.net/eu81273/8LWUc/)
|
||||
|
||||
## Installation
|
||||
|
||||
Copy the script and css into your project and add a script and link tag to your page.
|
||||
|
||||
```html
|
||||
<script type="text/javascript" src="/angular.treeview.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="css/angular.treeview.css">
|
||||
```
|
||||
|
||||
Add a dependency to your application module.
|
||||
|
||||
```javascript
|
||||
angular.module('myApp', ['angularTreeview']);
|
||||
```
|
||||
|
||||
Add a tree to your application. See [Usage](#usage).
|
||||
|
||||
## Usage
|
||||
|
||||
Attributes of angular treeview are below.
|
||||
|
||||
- angular-treeview: the treeview directive.
|
||||
- tree-id : each tree's unique id.
|
||||
- tree-model : the tree model on $scope.
|
||||
- node-id : each node's id.
|
||||
- node-label : each node's label.
|
||||
- node-children: each node's children.
|
||||
|
||||
Here is a simple example.
|
||||
|
||||
|
||||
```html
|
||||
<div
|
||||
data-angular-treeview="true"
|
||||
data-tree-id="abc"
|
||||
data-tree-model="treedata"
|
||||
data-node-id="id"
|
||||
data-node-label="label"
|
||||
data-node-children="children" >
|
||||
</div>
|
||||
```
|
||||
|
||||
Example model:
|
||||
|
||||
```javascript
|
||||
$scope.treedata =
|
||||
[
|
||||
{ "label" : "User", "id" : "role1", "children" : [
|
||||
{ "label" : "subUser1", "id" : "role11", "children" : [] },
|
||||
{ "label" : "subUser2", "id" : "role12", "children" : [
|
||||
{ "label" : "subUser2-1", "id" : "role121", "children" : [
|
||||
{ "label" : "subUser2-1-1", "id" : "role1211", "children" : [] },
|
||||
{ "label" : "subUser2-1-2", "id" : "role1212", "children" : [] }
|
||||
]}
|
||||
]}
|
||||
]},
|
||||
{ "label" : "Admin", "id" : "role2", "children" : [] },
|
||||
{ "label" : "Guest", "id" : "role3", "children" : [] }
|
||||
];
|
||||
```
|
||||
|
||||
## Selection
|
||||
|
||||
If tree node is selected, then that selected tree node is saved to $scope."TREE ID".currentNode. By using $watch, the controller can recognize the tree selection.
|
||||
|
||||
|
||||
```javascript
|
||||
$scope.$watch( 'abc.currentNode', function( newObj, oldObj ) {
|
||||
if( $scope.abc && angular.isObject($scope.abc.currentNode) ) {
|
||||
console.log( 'Node Selected!!' );
|
||||
console.log( $scope.abc.currentNode );
|
||||
}
|
||||
}, false);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
#### Basic example
|
||||
[![ScreenShot](https://github.com/eu81273/angular.treeview/raw/master/img/jsfiddle01.png)](http://jsfiddle.net/eu81273/8LWUc/)
|
||||
|
||||
[jsFiddle - http://jsfiddle.net/eu81273/8LWUc/](http://jsfiddle.net/eu81273/8LWUc/)
|
||||
|
||||
#### Multiple treeview example
|
||||
[![ScreenShot](https://github.com/eu81273/angular.treeview/raw/master/img/jsfiddle02.png)](http://jsfiddle.net/eu81273/b9Pnw/)
|
||||
|
||||
[jsFiddle - http://jsfiddle.net/eu81273/b9Pnw/](http://jsfiddle.net/eu81273/b9Pnw/)
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
Same with AngularJS. Safari, Chrome, Firefox, Opera, IE8, IE9 and mobile browsers (Android, Chrome Mobile, iOS Safari).
|
||||
|
||||
## Changelogs
|
||||
|
||||
#### version 0.1.6
|
||||
- Fixed the bug that 'null' string appears before each 'div' generated. (Thanks to Iaac)
|
||||
|
||||
#### version 0.1.5
|
||||
- support multiple treeview. (Thanks to Axel Pesme)
|
||||
|
||||
#### version 0.1.4
|
||||
- prevented memory leaks.
|
||||
|
||||
#### version 0.1.3
|
||||
- removed unnecessary codes.
|
||||
|
||||
#### version 0.1.2
|
||||
- removed some jQuery dependency. (Issue #2)
|
||||
|
||||
## License
|
||||
|
||||
The MIT License.
|
||||
|
||||
Copyright ⓒ 2013 AHN JAE-HA.
|
||||
|
||||
See [LICENSE](https://github.com/eu81273/angular.treeview/blob/master/LICENSE)
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
@license Angular Treeview version 0.1.6
|
||||
ⓒ 2013 AHN JAE-HA http://github.com/eu81273/angular.treeview
|
||||
License: MIT
|
||||
|
||||
|
||||
[TREE attribute]
|
||||
angular-treeview: the treeview directive
|
||||
tree-id : each tree's unique id.
|
||||
tree-model : the tree model on $scope.
|
||||
node-id : each node's id
|
||||
node-label : each node's label
|
||||
node-children: each node's children
|
||||
|
||||
<div
|
||||
data-angular-treeview="true"
|
||||
data-tree-id="tree"
|
||||
data-tree-model="roleList"
|
||||
data-node-id="roleId"
|
||||
data-node-label="roleName"
|
||||
data-node-children="children" >
|
||||
</div>
|
||||
*/
|
||||
|
||||
(function ( angular ) {
|
||||
'use strict';
|
||||
|
||||
angular.module( 'angularTreeview', [] ).directive( 'treeModel', ['$compile', function( $compile ) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function ( scope, element, attrs ) {
|
||||
//tree id
|
||||
var treeId = attrs.treeId;
|
||||
|
||||
//tree model
|
||||
var treeModel = attrs.treeModel;
|
||||
|
||||
//node id
|
||||
var nodeId = attrs.nodeId || 'id';
|
||||
|
||||
//node label
|
||||
var nodeLabel = attrs.nodeLabel || 'label';
|
||||
|
||||
//children
|
||||
var nodeChildren = attrs.nodeChildren || 'children';
|
||||
|
||||
//tree template
|
||||
|
||||
var template =
|
||||
'<ul>' +
|
||||
'<li data-ng-repeat="node in ' + treeModel + '">' +
|
||||
'<i ng-class="getGroupClass(node)" data-ng-click="' + treeId + '.selectNodeHead(node)"></i>' +
|
||||
'<span data-ng-class="getSelectedClass(node)" ng-dblclick="edit(node)" data-ng-click="' + treeId + '.selectNodeLabel(node)">{{node.' + nodeLabel + '}}</span>' +
|
||||
'<div data-ng-hide="node.collapsed" data-tree-id="' + treeId + '" data-tree-model="node.' + nodeChildren + '" data-node-id=' + nodeId + ' data-node-label=' + nodeLabel + ' data-node-children=' + nodeChildren + '></div>' +
|
||||
'</li>' +
|
||||
'</ul>';
|
||||
|
||||
|
||||
//check tree id, tree model
|
||||
if( treeId && treeModel ) {
|
||||
//root node
|
||||
if( attrs.angularTreeview ) {
|
||||
|
||||
//create tree object if not exists
|
||||
scope[treeId] = scope[treeId] || {};
|
||||
|
||||
//if node head clicks,
|
||||
scope[treeId].selectNodeHead = scope[treeId].selectNodeHead || function( selectedNode ){
|
||||
|
||||
//Collapse or Expand
|
||||
selectedNode.collapsed = !selectedNode.collapsed;
|
||||
scope[treeId].selectNodeLabel(selectedNode);
|
||||
};
|
||||
|
||||
//if node label clicks,
|
||||
scope[treeId].selectNodeLabel = scope[treeId].selectNodeLabel || function( selectedNode ){
|
||||
|
||||
//remove highlight from previous node
|
||||
if( scope[treeId].currentNode && scope[treeId].currentNode.selected ) {
|
||||
scope[treeId].currentNode.selected = undefined;
|
||||
}
|
||||
|
||||
//set highlight to selected node
|
||||
selectedNode.selected = 'selected';
|
||||
|
||||
//set currentNode
|
||||
scope[treeId].currentNode = selectedNode;
|
||||
};
|
||||
}
|
||||
|
||||
//Rendering template.
|
||||
element.html('').append( $compile( template )( scope ) );
|
||||
}
|
||||
}
|
||||
};
|
||||
}]);
|
||||
})( angular );
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
@license Angular Treeview version 0.1.6
|
||||
ⓒ 2013 AHN JAE-HA http://github.com/eu81273/angular.treeview
|
||||
License: MIT
|
||||
*/
|
||||
|
||||
(function(f){f.module("angularTreeview",[]).directive("treeModel",function($compile){return{restrict:"A",link:function(b,h,c){var a=c.treeId,g=c.treeModel,e=c.nodeLabel||"label",d=c.nodeChildren||"children",e='<ul><li data-ng-repeat="node in '+g+'"><i class="collapsed" data-ng-show="node.'+d+'.length && node.collapsed" data-ng-click="'+a+'.selectNodeHead(node)"></i><i class="expanded" data-ng-show="node.'+d+'.length && !node.collapsed" data-ng-click="'+a+'.selectNodeHead(node)"></i><i class="normal" data-ng-hide="node.'+
|
||||
d+'.length"></i> <span data-ng-class="node.selected" data-ng-click="'+a+'.selectNodeLabel(node)">{{node.'+e+'}}</span><div data-ng-hide="node.collapsed" data-tree-id="'+a+'" data-tree-model="node.'+d+'" data-node-id='+(c.nodeId||"id")+" data-node-label="+e+" data-node-children="+d+"></div></li></ul>";a&&g&&(c.angularTreeview&&(b[a]=b[a]||{},b[a].selectNodeHead=b[a].selectNodeHead||function(a){a.collapsed=!a.collapsed},b[a].selectNodeLabel=b[a].selectNodeLabel||function(c){b[a].currentNode&&b[a].currentNode.selected&&
|
||||
(b[a].currentNode.selected=void 0);c.selected="selected";b[a].currentNode=c}),h.html('').append($compile(e)(b)))}}})})(angular);
|
|
@ -0,0 +1,99 @@
|
|||
div[angular-treeview] {
|
||||
/* prevent user selection */
|
||||
-moz-user-select: -moz-none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
/* default */
|
||||
font-family: Tahoma;
|
||||
font-size:13px;
|
||||
color: #555;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div[tree-model] ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div[tree-model] li {
|
||||
position: relative;
|
||||
padding: 0 0 0 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
div[tree-model] li .expanded {
|
||||
padding: 1px 10px;
|
||||
background-image: url("../img/folder.png");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
div[tree-model] li .collapsed {
|
||||
padding: 1px 10px;
|
||||
background-image: url("../img/folder-closed.png");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
div[tree-model] li .normal {
|
||||
padding: 1px 10px;
|
||||
background-image: url("../img/file.png");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
div[tree-model] li i, div[tree-model] li span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div[tree-model] li .selected {
|
||||
background-color: #aaddff;
|
||||
font-weight: bold;
|
||||
padding: 1px 5px;
|
||||
}
|
||||
|
||||
div[tree-model] li .cut {
|
||||
font-weight: bold;
|
||||
color: gray
|
||||
}
|
||||
|
||||
/*
|
||||
.angular-ui-tree-handle {
|
||||
cursor: grab;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
min-height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
*/
|
||||
|
||||
.angular-ui-tree-handle {
|
||||
/* background: #f8faff; */
|
||||
/*
|
||||
color: #7c9eb2; */
|
||||
border: 1px solid #dae2ea;
|
||||
padding: 10px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.expanded-folder {
|
||||
padding: 1px 10px;
|
||||
background-image: url("../img/folder.png");
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.collapsed-folder {
|
||||
padding: 1px 10px;
|
||||
background-image: url("../img/folder-closed.png");
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 263 B |
Binary file not shown.
After Width: | Height: | Size: 281 B |
Binary file not shown.
After Width: | Height: | Size: 289 B |
|
@ -57,7 +57,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore();
|
||||
return token != null && this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore();
|
||||
}
|
||||
|
||||
public KeycloakDeployment getDeployment() {
|
||||
|
@ -111,6 +111,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||
log.debug("Token Verification succeeded!");
|
||||
} catch (VerificationException e) {
|
||||
log.error("failed verification of token");
|
||||
return false;
|
||||
}
|
||||
if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
|
||||
deployment.setNotBefore(response.getNotBeforePolicy());
|
||||
|
|
|
@ -11,7 +11,7 @@ public interface MigrationModel {
|
|||
/**
|
||||
* Must have the form of major.minor.micro as the version is parsed and numbers are compared
|
||||
*/
|
||||
public static final String LATEST_VERSION = "1.6.0";
|
||||
public static final String LATEST_VERSION = "1.7.0";
|
||||
|
||||
String getStoredVersion();
|
||||
void setStoredVersion(String version);
|
||||
|
|
68
model/api/src/main/java/org/keycloak/models/GroupModel.java
Executable file
68
model/api/src/main/java/org/keycloak/models/GroupModel.java
Executable file
|
@ -0,0 +1,68 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface GroupModel extends RoleMapperModel {
|
||||
String getId();
|
||||
|
||||
String getName();
|
||||
|
||||
void setName(String name);
|
||||
|
||||
/**
|
||||
* Set single value of specified attribute. Remove all other existing values
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
*/
|
||||
void setSingleAttribute(String name, String value);
|
||||
|
||||
void setAttribute(String name, List<String> values);
|
||||
|
||||
void removeAttribute(String name);
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @return null if there is not any value of specified attribute or first value otherwise. Don't throw exception if there are more values of the attribute
|
||||
*/
|
||||
String getFirstAttribute(String name);
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @return list of all attribute values or empty list if there are not any values. Never return null
|
||||
*/
|
||||
List<String> getAttribute(String name);
|
||||
|
||||
Map<String, List<String>> getAttributes();
|
||||
|
||||
GroupModel getParent();
|
||||
String getParentId();
|
||||
Set<GroupModel> getSubGroups();
|
||||
|
||||
/**
|
||||
* You must also call addChild on the parent group, addChild on RealmModel if there is no parent group
|
||||
*
|
||||
* @param group
|
||||
*/
|
||||
void setParent(GroupModel group);
|
||||
|
||||
/**
|
||||
* Automatically calls setParent() on the subGroup
|
||||
*
|
||||
* @param subGroup
|
||||
*/
|
||||
void addChild(GroupModel subGroup);
|
||||
|
||||
/**
|
||||
* Automatically calls setParent() on the subGroup
|
||||
*
|
||||
* @param subGroup
|
||||
*/
|
||||
void removeChild(GroupModel subGroup);
|
||||
}
|
|
@ -328,4 +328,19 @@ public interface RealmModel extends RoleContainerModel {
|
|||
void setSupportedLocales(Set<String> locales);
|
||||
String getDefaultLocale();
|
||||
void setDefaultLocale(String locale);
|
||||
|
||||
GroupModel createGroup(String name);
|
||||
|
||||
/**
|
||||
* Move Group to top realm level. Basically just sets group parent to null. You need to call this though
|
||||
* to make sure caches are set properly
|
||||
*
|
||||
* @param subGroup
|
||||
*/
|
||||
void addTopLevelGroup(GroupModel subGroup);
|
||||
GroupModel getGroupById(String id);
|
||||
List<GroupModel> getGroups();
|
||||
List<GroupModel> getTopLevelGroups();
|
||||
boolean removeGroup(GroupModel group);
|
||||
void moveGroup(GroupModel group, GroupModel toParent);
|
||||
}
|
||||
|
|
|
@ -20,8 +20,11 @@ public interface RealmProvider extends Provider {
|
|||
|
||||
RoleModel getRoleById(String id, RealmModel realm);
|
||||
ClientModel getClientById(String id, RealmModel realm);
|
||||
GroupModel getGroupById(String id, RealmModel realm);
|
||||
|
||||
|
||||
|
||||
List<RealmModel> getRealms();
|
||||
boolean removeRealm(String id);
|
||||
|
||||
void close();
|
||||
}
|
||||
|
|
21
model/api/src/main/java/org/keycloak/models/RoleMapperModel.java
Executable file
21
model/api/src/main/java/org/keycloak/models/RoleMapperModel.java
Executable file
|
@ -0,0 +1,21 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface RoleMapperModel {
|
||||
Set<RoleModel> getRealmRoleMappings();
|
||||
|
||||
Set<RoleModel> getClientRoleMappings(ClientModel app);
|
||||
|
||||
boolean hasRole(RoleModel role);
|
||||
|
||||
void grantRole(RoleModel role);
|
||||
|
||||
Set<RoleModel> getRoleMappings();
|
||||
|
||||
void deleteRoleMapping(RoleModel role);
|
||||
}
|
|
@ -165,6 +165,16 @@ public class UserFederationManager implements UserProvider {
|
|||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
return session.userStorage().getGroupMembers(realm, group, firstResult, maxResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
|
||||
return getGroupMembers(realm, group, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||
UserModel user = session.userStorage().getUserByUsername(username.toLowerCase(), realm);
|
||||
|
@ -353,6 +363,16 @@ public class UserFederationManager implements UserProvider {
|
|||
session.userStorage().preRemove(realm, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, GroupModel group) {
|
||||
for (UserFederationProviderModel federation : realm.getUserFederationProviders()) {
|
||||
UserFederationProvider fed = getFederationProvider(federation);
|
||||
fed.preRemove(realm, group);
|
||||
}
|
||||
session.userStorage().preRemove(realm, group);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, RoleModel role) {
|
||||
for (UserFederationProviderModel federation : realm.getUserFederationProviders()) {
|
||||
|
|
|
@ -119,6 +119,14 @@ public interface UserFederationProvider extends Provider {
|
|||
*/
|
||||
void preRemove(RealmModel realm, RoleModel role);
|
||||
|
||||
/**
|
||||
* called before a role is removed.
|
||||
*
|
||||
* @param realm
|
||||
* @param group
|
||||
*/
|
||||
void preRemove(RealmModel realm, GroupModel group);
|
||||
|
||||
/**
|
||||
* Is the Keycloak UserModel still valid and/or existing in federated storage? Keycloak may call this method
|
||||
* in various user operations. The local storage may be deleted if this method returns false.
|
||||
|
|
|
@ -8,7 +8,7 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserModel {
|
||||
public interface UserModel extends RoleMapperModel {
|
||||
String USERNAME = "username";
|
||||
String LAST_NAME = "lastName";
|
||||
String FIRST_NAME = "firstName";
|
||||
|
@ -94,12 +94,10 @@ public interface UserModel {
|
|||
|
||||
void updateCredentialDirectly(UserCredentialValueModel cred);
|
||||
|
||||
Set<RoleModel> getRealmRoleMappings();
|
||||
Set<RoleModel> getClientRoleMappings(ClientModel app);
|
||||
boolean hasRole(RoleModel role);
|
||||
void grantRole(RoleModel role);
|
||||
Set<RoleModel> getRoleMappings();
|
||||
void deleteRoleMapping(RoleModel role);
|
||||
Set<GroupModel> getGroups();
|
||||
void joinGroup(GroupModel group);
|
||||
void leaveGroup(GroupModel group);
|
||||
boolean isMemberOf(GroupModel group);
|
||||
|
||||
String getFederationLink();
|
||||
void setFederationLink(String link);
|
||||
|
|
|
@ -25,12 +25,16 @@ public interface UserProvider extends Provider {
|
|||
UserModel getUserById(String id, RealmModel realm);
|
||||
UserModel getUserByUsername(String username, RealmModel realm);
|
||||
UserModel getUserByEmail(String email, RealmModel realm);
|
||||
|
||||
List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults);
|
||||
|
||||
UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm);
|
||||
UserModel getUserByServiceAccountClient(ClientModel client);
|
||||
List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
|
||||
|
||||
// Service account is included for counts
|
||||
int getUsersCount(RealmModel realm);
|
||||
List<UserModel> getGroupMembers(RealmModel realm, GroupModel group);
|
||||
List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts);
|
||||
List<UserModel> searchForUser(String search, RealmModel realm);
|
||||
List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
|
||||
|
@ -50,6 +54,7 @@ public interface UserProvider extends Provider {
|
|||
void preRemove(RealmModel realm, UserFederationProviderModel link);
|
||||
|
||||
void preRemove(RealmModel realm, RoleModel role);
|
||||
void preRemove(RealmModel realm, GroupModel group);
|
||||
|
||||
void preRemove(RealmModel realm, ClientModel client);
|
||||
void preRemove(ClientModel realm, ProtocolMapperModel protocolMapper);
|
||||
|
|
60
model/api/src/main/java/org/keycloak/models/entities/GroupEntity.java
Executable file
60
model/api/src/main/java/org/keycloak/models/entities/GroupEntity.java
Executable file
|
@ -0,0 +1,60 @@
|
|||
package org.keycloak.models.entities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bburke@redhat.com">Bill Burke/a>
|
||||
*/
|
||||
public class GroupEntity extends AbstractIdentifiableEntity {
|
||||
|
||||
private String name;
|
||||
private String realmId;
|
||||
|
||||
private List<String> roleIds;
|
||||
private String parentId;
|
||||
private Map<String, List<String>> attributes;
|
||||
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
public List<String> getRoleIds() {
|
||||
return roleIds;
|
||||
}
|
||||
|
||||
public void setRoleIds(List<String> roleIds) {
|
||||
this.roleIds = roleIds;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(Map<String, List<String>> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getParentId() {
|
||||
return parentId;
|
||||
}
|
||||
|
||||
public void setParentId(String parentId) {
|
||||
this.parentId = parentId;
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
|||
private String realmId;
|
||||
|
||||
private List<String> roleIds;
|
||||
private List<String> groupIds;
|
||||
|
||||
private Map<String, List<String>> attributes;
|
||||
private List<String> requiredActions;
|
||||
|
@ -157,5 +158,13 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
|||
public void setServiceAccountClientLink(String serviceAccountClientLink) {
|
||||
this.serviceAccountClientLink = serviceAccountClientLink;
|
||||
}
|
||||
|
||||
public List<String> getGroupIds() {
|
||||
return groupIds;
|
||||
}
|
||||
|
||||
public void setGroupIds(List<String> groupIds) {
|
||||
this.groupIds = groupIds;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.models.utils;
|
|||
import org.bouncycastle.openssl.PEMWriter;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -279,6 +280,24 @@ public final class KeycloakModelUtils {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param groups
|
||||
* @param targetGroup
|
||||
* @return true if targetGroup is in groups (directly or indirectly via parent child relationship)
|
||||
*/
|
||||
public static boolean isMember(Set<GroupModel> groups, GroupModel targetGroup) {
|
||||
if (groups.contains(targetGroup)) return true;
|
||||
|
||||
for (GroupModel mapping : groups) {
|
||||
GroupModel child = mapping;
|
||||
while(child.getParent() != null) {
|
||||
if (child.getParent().equals(targetGroup)) return true;
|
||||
child = child.getParent();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// USER FEDERATION RELATED STUFF
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.keycloak.models.AuthenticatorConfigModel;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.ModelException;
|
||||
|
@ -30,6 +31,7 @@ import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
|
|||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
|
@ -58,6 +60,59 @@ import java.util.Set;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ModelToRepresentation {
|
||||
public static GroupRepresentation toRepresentation(GroupModel group, boolean full) {
|
||||
GroupRepresentation rep = new GroupRepresentation();
|
||||
rep.setId(group.getId());
|
||||
rep.setName(group.getName());
|
||||
if (!full) return rep;
|
||||
// Role mappings
|
||||
Set<RoleModel> roles = group.getRoleMappings();
|
||||
List<String> realmRoleNames = new ArrayList<>();
|
||||
Map<String, List<String>> clientRoleNames = new HashMap<>();
|
||||
for (RoleModel role : roles) {
|
||||
if (role.getContainer() instanceof RealmModel) {
|
||||
realmRoleNames.add(role.getName());
|
||||
} else {
|
||||
ClientModel client = (ClientModel)role.getContainer();
|
||||
String clientId = client.getClientId();
|
||||
List<String> currentClientRoles = clientRoleNames.get(clientId);
|
||||
if (currentClientRoles == null) {
|
||||
currentClientRoles = new ArrayList<>();
|
||||
clientRoleNames.put(clientId, currentClientRoles);
|
||||
}
|
||||
|
||||
currentClientRoles.add(role.getName());
|
||||
}
|
||||
}
|
||||
rep.setRealmRoles(realmRoleNames);
|
||||
rep.setClientRoles(clientRoleNames);
|
||||
Map<String, List<String>> attributes = group.getAttributes();
|
||||
rep.setAttributes(attributes);
|
||||
return rep;
|
||||
}
|
||||
|
||||
public static List<GroupRepresentation> toGroupHierarchy(RealmModel realm, boolean full) {
|
||||
List<GroupRepresentation> hierarchy = new LinkedList<>();
|
||||
List<GroupModel> groups = realm.getTopLevelGroups();
|
||||
if (groups == null) return hierarchy;
|
||||
for (GroupModel group : groups) {
|
||||
GroupRepresentation rep = toGroupHierarchy(group, full);
|
||||
hierarchy.add(rep);
|
||||
}
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
public static GroupRepresentation toGroupHierarchy(GroupModel group, boolean full) {
|
||||
GroupRepresentation rep = toRepresentation(group, full);
|
||||
List<GroupRepresentation> subGroups = new LinkedList<>();
|
||||
for (GroupModel subGroup : group.getSubGroups()) {
|
||||
subGroups.add(toGroupHierarchy(subGroup, full));
|
||||
}
|
||||
rep.setSubGroups(subGroups);
|
||||
return rep;
|
||||
}
|
||||
|
||||
|
||||
public static UserRepresentation toRepresentation(UserModel user) {
|
||||
UserRepresentation rep = new UserRepresentation();
|
||||
rep.setId(user.getId());
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.models.utils;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
|
@ -255,4 +256,26 @@ public class UserModelDelegate implements UserModel {
|
|||
public void setCreatedTimestamp(Long timestamp){
|
||||
delegate.setCreatedTimestamp(timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GroupModel> getGroups() {
|
||||
return delegate.getGroups();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void joinGroup(GroupModel group) {
|
||||
delegate.joinGroup(group);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leaveGroup(GroupModel group) {
|
||||
delegate.leaveGroup(group);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMemberOf(GroupModel group) {
|
||||
return delegate.isMemberOf(group);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.keycloak.connections.file.FileConnectionProvider;
|
|||
import org.keycloak.connections.file.InMemoryModel;
|
||||
import org.keycloak.migration.MigrationModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -78,6 +79,11 @@ public class FileRealmProvider implements RealmProvider {
|
|||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupById(String id, RealmModel realm) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm(String id) {
|
||||
RealmModel model = inMemoryModel.getRealm(id);
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.keycloak.connections.file.InMemoryModel;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
|
@ -80,6 +81,21 @@ public class FileUserProvider implements UserProvider {
|
|||
return inMemoryModel.getUser(realm.getId(), userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, GroupModel group) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||
for (UserModel user : inMemoryModel.getUsers(realm.getId())) {
|
||||
|
|
213
model/file/src/main/java/org/keycloak/models/file/adapter/GroupAdapter.java
Executable file
213
model/file/src/main/java/org/keycloak/models/file/adapter/GroupAdapter.java
Executable file
|
@ -0,0 +1,213 @@
|
|||
package org.keycloak.models.file.adapter;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.entities.GroupEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class GroupAdapter implements GroupModel {
|
||||
|
||||
private final GroupEntity group;
|
||||
private RealmModel realm;
|
||||
private KeycloakSession session;
|
||||
|
||||
public GroupAdapter(KeycloakSession session, RealmModel realm, GroupEntity group) {
|
||||
this.group = group;
|
||||
this.realm = realm;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return group.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return group.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
group.setName(name);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof GroupModel)) return false;
|
||||
|
||||
GroupModel that = (GroupModel) o;
|
||||
return that.getId().equals(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
if (group.getAttributes() == null) {
|
||||
group.setAttributes(new HashMap<String, List<String>>());
|
||||
}
|
||||
|
||||
List<String> attrValues = new ArrayList<>();
|
||||
attrValues.add(value);
|
||||
group.getAttributes().put(name, attrValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
if (group.getAttributes() == null) {
|
||||
group.setAttributes(new HashMap<String, List<String>>());
|
||||
}
|
||||
|
||||
group.getAttributes().put(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
if (group.getAttributes() == null) return;
|
||||
|
||||
group.getAttributes().remove(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstAttribute(String name) {
|
||||
if (group.getAttributes()==null) return null;
|
||||
|
||||
List<String> attrValues = group.getAttributes().get(name);
|
||||
return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
if (group.getAttributes()==null) return Collections.<String>emptyList();
|
||||
List<String> attrValues = group.getAttributes().get(name);
|
||||
return (attrValues == null) ? Collections.<String>emptyList() : Collections.unmodifiableList(attrValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return group.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map) group.getAttributes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
Set<RoleModel> roles = getRoleMappings();
|
||||
return KeycloakModelUtils.hasRole(roles, role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantRole(RoleModel role) {
|
||||
if (group.getRoleIds() == null) {
|
||||
group.setRoleIds(new LinkedList<String>());
|
||||
}
|
||||
if (group.getRoleIds().contains(role.getId())) {
|
||||
return;
|
||||
}
|
||||
group.getRoleIds().add(role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRoleMappings() {
|
||||
if (group.getRoleIds() == null || group.getRoleIds().isEmpty()) return Collections.EMPTY_SET;
|
||||
Set<RoleModel> roles = new HashSet<>();
|
||||
for (String id : group.getRoleIds()) {
|
||||
roles.add(realm.getRoleById(id));
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRealmRoleMappings() {
|
||||
Set<RoleModel> allRoles = getRoleMappings();
|
||||
|
||||
// Filter to retrieve just realm roles
|
||||
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
|
||||
for (RoleModel role : allRoles) {
|
||||
if (role.getContainer() instanceof RealmModel) {
|
||||
realmRoles.add(role);
|
||||
}
|
||||
}
|
||||
return realmRoles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRoleMapping(RoleModel role) {
|
||||
if (group == null || role == null) return;
|
||||
if (group.getRoleIds() == null) return;
|
||||
group.getRoleIds().remove(role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
||||
Set<RoleModel> result = new HashSet<RoleModel>();
|
||||
Set<RoleModel> roles = getRoleMappings();
|
||||
|
||||
for (RoleModel role : roles) {
|
||||
if (app.equals(role.getContainer())) {
|
||||
result.add(role);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getParent() {
|
||||
if (group.getParentId() == null) return null;
|
||||
return realm.getGroupById(group.getParentId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentId() {
|
||||
return group.getParentId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GroupModel> getSubGroups() {
|
||||
Set<GroupModel> subGroups = new HashSet<>();
|
||||
for (GroupModel groupModel : realm.getGroups()) {
|
||||
if (groupModel.getParent().equals(this)) {
|
||||
subGroups.add(groupModel);
|
||||
}
|
||||
}
|
||||
return subGroups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(GroupModel group) {
|
||||
this.group.setParentId(group.getId());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChild(GroupModel subGroup) {
|
||||
subGroup.setParent(this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeChild(GroupModel subGroup) {
|
||||
subGroup.setParent(null);
|
||||
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
|
|||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -88,6 +89,7 @@ public class RealmAdapter implements RealmModel {
|
|||
private final Map<String, ClientModel> allApps = new HashMap<String, ClientModel>();
|
||||
private ClientModel masterAdminApp = null;
|
||||
private final Map<String, RoleAdapter> allRoles = new HashMap<String, RoleAdapter>();
|
||||
private final Map<String, GroupAdapter> allGroups = new HashMap<String, GroupAdapter>();
|
||||
private final Map<String, IdentityProviderModel> allIdProviders = new HashMap<String, IdentityProviderModel>();
|
||||
|
||||
public RealmAdapter(KeycloakSession session, RealmEntity realm, InMemoryModel inMemoryModel) {
|
||||
|
@ -601,6 +603,45 @@ public class RealmAdapter implements RealmModel {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupById(String id) {
|
||||
GroupModel found = allGroups.get(id);
|
||||
if (found != null) return found;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveGroup(GroupModel group, GroupModel toParent) {
|
||||
if (group.getParentId() != null) {
|
||||
group.getParent().removeChild(group);
|
||||
}
|
||||
group.setParent(toParent);
|
||||
if (toParent != null) toParent.addChild(group);
|
||||
else addTopLevelGroup(group);
|
||||
}
|
||||
@Override
|
||||
public List<GroupModel> getGroups() {
|
||||
List<GroupModel> list = new LinkedList<>();
|
||||
for (GroupAdapter group : allGroups.values()) {
|
||||
list.add(group);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GroupModel> getTopLevelGroups() {
|
||||
List<GroupModel> list = new LinkedList<>();
|
||||
for (GroupAdapter group : allGroups.values()) {
|
||||
if (group.getParent() == null) list.add(group);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeGroup(GroupModel group) {
|
||||
return allGroups.remove(group.getId()) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDefaultRoles() {
|
||||
return realm.getDefaultRoles();
|
||||
|
@ -1787,4 +1828,13 @@ public class RealmAdapter implements RealmModel {
|
|||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel createGroup(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTopLevelGroup(GroupModel subGroup) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.keycloak.models.ClientModel;
|
|||
|
||||
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
|
||||
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
|
@ -59,6 +60,7 @@ public class UserAdapter implements UserModel, Comparable {
|
|||
private final RealmModel realm;
|
||||
|
||||
private final Set<RoleModel> allRoles = new HashSet<RoleModel>();
|
||||
private final Set<GroupModel> allGroups = new HashSet<GroupModel>();
|
||||
|
||||
public UserAdapter(RealmModel realm, UserEntity userEntity, InMemoryModel inMemoryModel) {
|
||||
this.user = userEntity;
|
||||
|
@ -467,6 +469,29 @@ public class UserAdapter implements UserModel, Comparable {
|
|||
credentialEntity.setPeriod(credModel.getPeriod());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GroupModel> getGroups() {
|
||||
return Collections.unmodifiableSet(allGroups);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void joinGroup(GroupModel group) {
|
||||
allGroups.add(group);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leaveGroup(GroupModel group) {
|
||||
if (user == null || group == null) return;
|
||||
allGroups.remove(group);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMemberOf(GroupModel group) {
|
||||
return KeycloakModelUtils.isMember(getGroups(), group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
Set<RoleModel> roles = getRoleMappings();
|
||||
|
|
36
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java
vendored
Normal file → Executable file
36
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java
vendored
Normal file → Executable file
|
@ -21,9 +21,11 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
|
|||
protected Set<String> realmInvalidations = new HashSet<String>();
|
||||
protected Set<String> appInvalidations = new HashSet<String>();
|
||||
protected Set<String> roleInvalidations = new HashSet<String>();
|
||||
protected Set<String> groupInvalidations = new HashSet<String>();
|
||||
protected Map<String, RealmModel> managedRealms = new HashMap<String, RealmModel>();
|
||||
protected Map<String, ClientModel> managedApplications = new HashMap<String, ClientModel>();
|
||||
protected Map<String, RoleModel> managedRoles = new HashMap<String, RoleModel>();
|
||||
protected Map<String, GroupModel> managedGroups = new HashMap<String, GroupModel>();
|
||||
|
||||
protected boolean clearAll;
|
||||
|
||||
|
@ -73,6 +75,12 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
|
|||
roleInvalidations.add(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerGroupInvalidation(String id) {
|
||||
groupInvalidations.add(id);
|
||||
|
||||
}
|
||||
|
||||
protected void runInvalidations() {
|
||||
for (String id : realmInvalidations) {
|
||||
cache.invalidateCachedRealmById(id);
|
||||
|
@ -80,6 +88,9 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
|
|||
for (String id : roleInvalidations) {
|
||||
cache.invalidateRoleById(id);
|
||||
}
|
||||
for (String id : groupInvalidations) {
|
||||
cache.invalidateGroupById(id);
|
||||
}
|
||||
for (String id : appInvalidations) {
|
||||
cache.invalidateCachedApplicationById(id);
|
||||
}
|
||||
|
@ -254,6 +265,31 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
|
|||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupById(String id, RealmModel realm) {
|
||||
if (!cache.isEnabled()) return getDelegate().getGroupById(id, realm);
|
||||
CachedGroup cached = cache.getGroup(id);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
}
|
||||
|
||||
if (cached == null) {
|
||||
GroupModel model = getDelegate().getGroupById(id, realm);
|
||||
if (model == null) return null;
|
||||
if (groupInvalidations.contains(id)) return model;
|
||||
cached = new CachedGroup(realm, model);
|
||||
cache.addCachedGroup(cached);
|
||||
|
||||
} else if (groupInvalidations.contains(id)) {
|
||||
return getDelegate().getGroupById(id, realm);
|
||||
} else if (managedGroups.containsKey(id)) {
|
||||
return managedGroups.get(id);
|
||||
}
|
||||
GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
|
||||
managedGroups.put(id, adapter);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClientById(String id, RealmModel realm) {
|
||||
if (!cache.isEnabled()) return getDelegate().getClientById(id, realm);
|
||||
|
|
15
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
vendored
Normal file → Executable file
15
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
vendored
Normal file → Executable file
|
@ -197,6 +197,16 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
return getDelegate().getUserByFederatedIdentity(socialLink, realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
return getDelegate().getGroupMembers(realm, group, firstResult, maxResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
|
||||
return getDelegate().getGroupMembers(realm, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByServiceAccountClient(ClientModel client) {
|
||||
return getDelegate().getUserByServiceAccountClient(client);
|
||||
|
@ -319,6 +329,11 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
public void preRemove(RealmModel realm, RoleModel role) {
|
||||
getDelegate().preRemove(realm, role);
|
||||
}
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, GroupModel group) {
|
||||
getDelegate().preRemove(realm, group);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, UserFederationProviderModel link) {
|
||||
|
|
244
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
vendored
Executable file
244
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
vendored
Executable file
|
@ -0,0 +1,244 @@
|
|||
package org.keycloak.models.cache.infinispan;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.cache.CacheRealmProvider;
|
||||
import org.keycloak.models.cache.CacheUserProvider;
|
||||
import org.keycloak.models.cache.entities.CachedGroup;
|
||||
import org.keycloak.models.cache.entities.CachedUser;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class GroupAdapter implements GroupModel {
|
||||
protected GroupModel updated;
|
||||
protected CachedGroup cached;
|
||||
protected CacheRealmProvider cacheSession;
|
||||
protected KeycloakSession keycloakSession;
|
||||
protected RealmModel realm;
|
||||
|
||||
public GroupAdapter(CachedGroup cached, CacheRealmProvider cacheSession, KeycloakSession keycloakSession, RealmModel realm) {
|
||||
this.cached = cached;
|
||||
this.cacheSession = cacheSession;
|
||||
this.keycloakSession = keycloakSession;
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
protected void getDelegateForUpdate() {
|
||||
if (updated == null) {
|
||||
cacheSession.registerGroupInvalidation(getId());
|
||||
updated = cacheSession.getDelegate().getGroupById(getId(), realm);
|
||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof GroupModel)) return false;
|
||||
|
||||
GroupModel that = (GroupModel) o;
|
||||
|
||||
if (!cached.getId().equals(that.getId())) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return cached.getId().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
if (updated != null) return updated.getId();
|
||||
return cached.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
if (updated != null) return updated.getName();
|
||||
return cached.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
getDelegateForUpdate();
|
||||
updated.setName(name);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
getDelegateForUpdate();
|
||||
updated.setSingleAttribute(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
getDelegateForUpdate();
|
||||
updated.setAttribute(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
getDelegateForUpdate();
|
||||
updated.removeAttribute(name);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstAttribute(String name) {
|
||||
if (updated != null) return updated.getFirstAttribute(name);
|
||||
return cached.getAttributes().getFirst(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
List<String> values = cached.getAttributes().get(name);
|
||||
if (values == null) return null;
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return cached.getAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRealmRoleMappings() {
|
||||
if (updated != null) return updated.getRealmRoleMappings();
|
||||
Set<RoleModel> roleMappings = getRoleMappings();
|
||||
Set<RoleModel> realmMappings = new HashSet<RoleModel>();
|
||||
for (RoleModel role : roleMappings) {
|
||||
RoleContainerModel container = role.getContainer();
|
||||
if (container instanceof RealmModel) {
|
||||
if (((RealmModel) container).getId().equals(realm.getId())) {
|
||||
realmMappings.add(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
return realmMappings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
||||
if (updated != null) return updated.getClientRoleMappings(app);
|
||||
Set<RoleModel> roleMappings = getRoleMappings();
|
||||
Set<RoleModel> appMappings = new HashSet<RoleModel>();
|
||||
for (RoleModel role : roleMappings) {
|
||||
RoleContainerModel container = role.getContainer();
|
||||
if (container instanceof ClientModel) {
|
||||
if (((ClientModel) container).getId().equals(app.getId())) {
|
||||
appMappings.add(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
return appMappings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
if (updated != null) return updated.hasRole(role);
|
||||
if (cached.getRoleMappings().contains(role.getId())) return true;
|
||||
|
||||
Set<RoleModel> mappings = getRoleMappings();
|
||||
for (RoleModel mapping: mappings) {
|
||||
if (mapping.hasRole(role)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantRole(RoleModel role) {
|
||||
getDelegateForUpdate();
|
||||
updated.grantRole(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRoleMappings() {
|
||||
if (updated != null) return updated.getRoleMappings();
|
||||
Set<RoleModel> roles = new HashSet<RoleModel>();
|
||||
for (String id : cached.getRoleMappings()) {
|
||||
RoleModel roleById = keycloakSession.realms().getRoleById(id, realm);
|
||||
if (roleById == null) {
|
||||
// chance that role was removed, so just delegate to persistence and get user invalidated
|
||||
getDelegateForUpdate();
|
||||
return updated.getRoleMappings();
|
||||
}
|
||||
roles.add(roleById);
|
||||
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRoleMapping(RoleModel role) {
|
||||
getDelegateForUpdate();
|
||||
updated.deleteRoleMapping(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getParent() {
|
||||
if (updated != null) return updated.getParent();
|
||||
if (cached.getParentId() == null) return null;
|
||||
return keycloakSession.realms().getGroupById(cached.getParentId(), realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentId() {
|
||||
if (updated != null) return updated.getParentId();
|
||||
return cached.getParentId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GroupModel> getSubGroups() {
|
||||
if (updated != null) return updated.getSubGroups();
|
||||
Set<GroupModel> subGroups = new HashSet<>();
|
||||
for (String id : cached.getSubGroups()) {
|
||||
GroupModel subGroup = keycloakSession.realms().getGroupById(id, realm);
|
||||
if (subGroup == null) {
|
||||
// chance that role was removed, so just delegate to persistence and get user invalidated
|
||||
getDelegateForUpdate();
|
||||
return updated.getSubGroups();
|
||||
|
||||
}
|
||||
subGroups.add(subGroup);
|
||||
}
|
||||
return subGroups;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setParent(GroupModel group) {
|
||||
getDelegateForUpdate();
|
||||
updated.setParent(group);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChild(GroupModel subGroup) {
|
||||
getDelegateForUpdate();
|
||||
updated.addChild(subGroup);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeChild(GroupModel subGroup) {
|
||||
getDelegateForUpdate();
|
||||
updated.removeChild(subGroup);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import org.infinispan.Cache;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.cache.RealmCache;
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedGroup;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
import org.keycloak.models.cache.entities.CachedRole;
|
||||
|
||||
|
@ -101,12 +102,49 @@ public class InfinispanRealmCache implements RealmCache {
|
|||
cache.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedGroup getGroup(String id) {
|
||||
if (!enabled) return null;
|
||||
return get(id, CachedGroup.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateGroup(CachedGroup role) {
|
||||
logger.tracev("Removing group {0}", role.getId());
|
||||
cache.remove(role.getId());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCachedGroup(CachedGroup role) {
|
||||
if (!enabled) return;
|
||||
logger.tracev("Adding group {0}", role.getId());
|
||||
cache.put(role.getId(), role);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCachedGroupById(String id) {
|
||||
logger.tracev("Removing group {0}", id);
|
||||
cache.remove(id);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateGroupById(String id) {
|
||||
logger.tracev("Removing group {0}", id);
|
||||
cache.remove(id);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedRole getRole(String id) {
|
||||
if (!enabled) return null;
|
||||
return get(id, CachedRole.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void invalidateRole(CachedRole role) {
|
||||
logger.tracev("Removing role {0}", role.getId());
|
||||
|
|
|
@ -1262,4 +1262,61 @@ public class RealmAdapter implements RealmModel {
|
|||
if (updated != null) return updated.getRequiredActionProviderByAlias(alias);
|
||||
return cached.getRequiredActionProvidersByAlias().get(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupById(String id) {
|
||||
if (updated != null) return updated.getGroupById(id);
|
||||
return cacheSession.getGroupById(id, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GroupModel> getGroups() {
|
||||
if (updated != null) return updated.getGroups();
|
||||
if (cached.getGroups().isEmpty()) return Collections.EMPTY_LIST;
|
||||
List<GroupModel> list = new LinkedList<>();
|
||||
for (String id : cached.getGroups()) {
|
||||
GroupModel group = cacheSession.getGroupById(id, this);
|
||||
if (group == null) continue;
|
||||
list.add(group);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GroupModel> getTopLevelGroups() {
|
||||
List<GroupModel> all = getGroups();
|
||||
Iterator<GroupModel> it = all.iterator();
|
||||
while (it.hasNext()) {
|
||||
GroupModel group = it.next();
|
||||
if (group.getParent() != null) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeGroup(GroupModel group) {
|
||||
getDelegateForUpdate();
|
||||
return updated.removeGroup(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel createGroup(String name) {
|
||||
getDelegateForUpdate();
|
||||
return updated.createGroup(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTopLevelGroup(GroupModel subGroup) {
|
||||
getDelegateForUpdate();
|
||||
updated.addTopLevelGroup(subGroup);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveGroup(GroupModel group, GroupModel toParent) {
|
||||
getDelegateForUpdate();
|
||||
updated.moveGroup(group, toParent);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -317,6 +317,44 @@ public class UserAdapter implements UserModel {
|
|||
updated.deleteRoleMapping(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GroupModel> getGroups() {
|
||||
if (updated != null) return updated.getGroups();
|
||||
Set<GroupModel> groups = new HashSet<GroupModel>();
|
||||
for (String id : cached.getRoleMappings()) {
|
||||
GroupModel groupModel = keycloakSession.realms().getGroupById(id, realm);
|
||||
if (groupModel == null) {
|
||||
// chance that role was removed, so just delete to persistence and get user invalidated
|
||||
getDelegateForUpdate();
|
||||
return updated.getGroups();
|
||||
}
|
||||
groups.add(groupModel);
|
||||
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void joinGroup(GroupModel group) {
|
||||
getDelegateForUpdate();
|
||||
updated.joinGroup(group);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leaveGroup(GroupModel group) {
|
||||
getDelegateForUpdate();
|
||||
updated.leaveGroup(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMemberOf(GroupModel group) {
|
||||
if (updated != null) return updated.isMemberOf(group);
|
||||
if (cached.getGroups().contains(group.getId())) return true;
|
||||
Set<GroupModel> roles = getGroups();
|
||||
return KeycloakModelUtils.isMember(roles, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConsent(UserConsentModel consent) {
|
||||
getDelegateForUpdate();
|
||||
|
@ -348,4 +386,5 @@ public class UserAdapter implements UserModel {
|
|||
getDelegateForUpdate();
|
||||
return updated.revokeConsentForClient(clientId);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,4 +17,6 @@ public interface CacheRealmProvider extends RealmProvider {
|
|||
void registerApplicationInvalidation(String id);
|
||||
|
||||
void registerRoleInvalidation(String id);
|
||||
|
||||
void registerGroupInvalidation(String id);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.models.cache;
|
||||
|
||||
import org.keycloak.models.cache.entities.CachedClient;
|
||||
import org.keycloak.models.cache.entities.CachedGroup;
|
||||
import org.keycloak.models.cache.entities.CachedRealm;
|
||||
import org.keycloak.models.cache.entities.CachedRole;
|
||||
|
||||
|
@ -39,6 +40,16 @@ public interface RealmCache {
|
|||
|
||||
void invalidateRoleById(String id);
|
||||
|
||||
CachedGroup getGroup(String id);
|
||||
|
||||
void invalidateGroup(CachedGroup role);
|
||||
|
||||
void addCachedGroup(CachedGroup role);
|
||||
|
||||
void invalidateCachedGroupById(String id);
|
||||
|
||||
void invalidateGroupById(String id);
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
void setEnabled(boolean enabled);
|
||||
|
|
74
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedGroup.java
vendored
Executable file
74
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedGroup.java
vendored
Executable file
|
@ -0,0 +1,74 @@
|
|||
package org.keycloak.models.cache.entities;
|
||||
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class CachedGroup implements Serializable {
|
||||
private String id;
|
||||
private String realm;
|
||||
private String name;
|
||||
private String parentId;
|
||||
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
|
||||
private Set<String> roleMappings = new HashSet<>();
|
||||
private Set<String> subGroups = new HashSet<>();
|
||||
|
||||
public CachedGroup(RealmModel realm, GroupModel group) {
|
||||
this.id = group.getId();
|
||||
this.realm = realm.getId();
|
||||
this.name = group.getName();
|
||||
this.parentId = group.getParentId();
|
||||
|
||||
this.attributes.putAll(group.getAttributes());
|
||||
for (RoleModel role : group.getRoleMappings()) {
|
||||
roleMappings.add(role.getId());
|
||||
}
|
||||
Set<GroupModel> subGroups1 = group.getSubGroups();
|
||||
if (subGroups1 != null) {
|
||||
for (GroupModel subGroup : subGroups1) {
|
||||
subGroups.add(subGroup.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public MultivaluedHashMap<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public Set<String> getRoleMappings() {
|
||||
return roleMappings;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getParentId() {
|
||||
return parentId;
|
||||
}
|
||||
|
||||
public Set<String> getSubGroups() {
|
||||
return subGroups;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
|
|||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
|
@ -106,6 +107,7 @@ public class CachedRealm implements Serializable {
|
|||
protected Set<String> adminEnabledEventOperations = new HashSet<String>();
|
||||
protected boolean adminEventsDetailsEnabled;
|
||||
private List<String> defaultRoles = new LinkedList<String>();
|
||||
private Set<String> groups = new HashSet<String>();
|
||||
private Map<String, String> realmRoles = new HashMap<String, String>();
|
||||
private Map<String, String> clients = new HashMap<String, String>();
|
||||
private boolean internationalizationEnabled;
|
||||
|
@ -216,6 +218,9 @@ public class CachedRealm implements Serializable {
|
|||
executionsById.put(execution.getId(), execution);
|
||||
}
|
||||
}
|
||||
for (GroupModel group : model.getGroups()) {
|
||||
groups.add(group.getId());
|
||||
}
|
||||
for (AuthenticatorConfigModel authenticator : model.getAuthenticatorConfigs()) {
|
||||
authenticatorConfigs.put(authenticator.getId(), authenticator);
|
||||
}
|
||||
|
@ -507,4 +512,8 @@ public class CachedRealm implements Serializable {
|
|||
public AuthenticationFlowModel getClientAuthenticationFlow() {
|
||||
return clientAuthenticationFlow;
|
||||
}
|
||||
|
||||
public Set<String> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.models.cache.entities;
|
||||
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
|
@ -33,6 +34,7 @@ public class CachedUser implements Serializable {
|
|||
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
|
||||
private Set<String> requiredActions = new HashSet<>();
|
||||
private Set<String> roleMappings = new HashSet<>();
|
||||
private Set<String> groups = new HashSet<>();
|
||||
|
||||
public CachedUser(RealmModel realm, UserModel user) {
|
||||
this.id = user.getId();
|
||||
|
@ -53,6 +55,12 @@ public class CachedUser implements Serializable {
|
|||
for (RoleModel role : user.getRoleMappings()) {
|
||||
roleMappings.add(role.getId());
|
||||
}
|
||||
Set<GroupModel> groupMappings = user.getGroups();
|
||||
if (groupMappings != null) {
|
||||
for (GroupModel group : groupMappings) {
|
||||
groups.add(group.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
|
@ -118,4 +126,8 @@ public class CachedUser implements Serializable {
|
|||
public String getServiceAccountClientLink() {
|
||||
return serviceAccountClientLink;
|
||||
}
|
||||
|
||||
public Set<String> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
}
|
||||
|
|
328
model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
Executable file
328
model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
Executable file
|
@ -0,0 +1,328 @@
|
|||
package org.keycloak.models.jpa;
|
||||
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.jpa.entities.CredentialEntity;
|
||||
import org.keycloak.models.jpa.entities.GroupAttributeEntity;
|
||||
import org.keycloak.models.jpa.entities.GroupEntity;
|
||||
import org.keycloak.models.jpa.entities.GroupRoleMappingEntity;
|
||||
import org.keycloak.models.jpa.entities.RoleEntity;
|
||||
import org.keycloak.models.jpa.entities.UserAttributeEntity;
|
||||
import org.keycloak.models.jpa.entities.UserConsentEntity;
|
||||
import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
|
||||
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
|
||||
import org.keycloak.models.jpa.entities.UserEntity;
|
||||
import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
|
||||
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class GroupAdapter implements GroupModel {
|
||||
|
||||
protected GroupEntity group;
|
||||
protected EntityManager em;
|
||||
protected RealmModel realm;
|
||||
|
||||
public GroupAdapter(RealmModel realm, EntityManager em, GroupEntity group) {
|
||||
this.em = em;
|
||||
this.group = group;
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public GroupEntity getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return group.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return group.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
group.setName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getParent() {
|
||||
GroupEntity parent = group.getParent();
|
||||
if (parent == null) return null;
|
||||
return realm.getGroupById(parent.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentId() {
|
||||
GroupEntity parent = group.getParent();
|
||||
if (parent == null) return null;
|
||||
return parent.getId();
|
||||
}
|
||||
|
||||
public static GroupEntity toEntity(GroupModel model, EntityManager em) {
|
||||
if (model instanceof GroupAdapter) {
|
||||
return ((GroupAdapter)model).getGroup();
|
||||
}
|
||||
return em.getReference(GroupEntity.class, model.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(GroupModel parent) {
|
||||
if (parent == null) group.setParent(null);
|
||||
else {
|
||||
GroupEntity parentEntity = toEntity(parent, em);
|
||||
group.setParent(parentEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChild(GroupModel subGroup) {
|
||||
subGroup.setParent(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeChild(GroupModel subGroup) {
|
||||
subGroup.setParent(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GroupModel> getSubGroups() {
|
||||
TypedQuery<String> query = em.createNamedQuery("getGroupIdsByParent", String.class);
|
||||
query.setParameter("parent", group);
|
||||
List<String> ids = query.getResultList();
|
||||
Set<GroupModel> set = new HashSet<>();
|
||||
for (String id : ids) {
|
||||
GroupModel subGroup = realm.getGroupById(id);
|
||||
if (subGroup == null) continue;
|
||||
set.add(subGroup);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
boolean found = false;
|
||||
List<GroupAttributeEntity> toRemove = new ArrayList<>();
|
||||
for (GroupAttributeEntity attr : group.getAttributes()) {
|
||||
if (attr.getName().equals(name)) {
|
||||
if (!found) {
|
||||
attr.setValue(value);
|
||||
found = true;
|
||||
} else {
|
||||
toRemove.add(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (GroupAttributeEntity attr : toRemove) {
|
||||
em.remove(attr);
|
||||
group.getAttributes().remove(attr);
|
||||
}
|
||||
|
||||
if (found) {
|
||||
return;
|
||||
}
|
||||
|
||||
persistAttributeValue(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
// Remove all existing
|
||||
removeAttribute(name);
|
||||
|
||||
// Put all new
|
||||
for (String value : values) {
|
||||
persistAttributeValue(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void persistAttributeValue(String name, String value) {
|
||||
GroupAttributeEntity attr = new GroupAttributeEntity();
|
||||
attr.setId(KeycloakModelUtils.generateId());
|
||||
attr.setName(name);
|
||||
attr.setValue(value);
|
||||
attr.setGroup(group);
|
||||
em.persist(attr);
|
||||
group.getAttributes().add(attr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
Iterator<GroupAttributeEntity> it = group.getAttributes().iterator();
|
||||
while (it.hasNext()) {
|
||||
GroupAttributeEntity attr = it.next();
|
||||
if (attr.getName().equals(name)) {
|
||||
it.remove();
|
||||
em.remove(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstAttribute(String name) {
|
||||
for (GroupAttributeEntity attr : group.getAttributes()) {
|
||||
if (attr.getName().equals(name)) {
|
||||
return attr.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (GroupAttributeEntity attr : group.getAttributes()) {
|
||||
if (attr.getName().equals(name)) {
|
||||
result.add(attr.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
MultivaluedHashMap<String, String> result = new MultivaluedHashMap<>();
|
||||
for (GroupAttributeEntity attr : group.getAttributes()) {
|
||||
result.add(attr.getName(), attr.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
Set<RoleModel> roles = getRoleMappings();
|
||||
return KeycloakModelUtils.hasRole(roles, role);
|
||||
}
|
||||
|
||||
protected TypedQuery<GroupRoleMappingEntity> getGroupRoleMappingEntityTypedQuery(RoleModel role) {
|
||||
TypedQuery<GroupRoleMappingEntity> query = em.createNamedQuery("groupHasRole", GroupRoleMappingEntity.class);
|
||||
query.setParameter("group", getGroup());
|
||||
query.setParameter("roleId", role.getId());
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantRole(RoleModel role) {
|
||||
if (hasRole(role)) return;
|
||||
GroupRoleMappingEntity entity = new GroupRoleMappingEntity();
|
||||
entity.setGroup(getGroup());
|
||||
entity.setRoleId(role.getId());
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
em.detach(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRealmRoleMappings() {
|
||||
Set<RoleModel> roleMappings = getRoleMappings();
|
||||
|
||||
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
|
||||
for (RoleModel role : roleMappings) {
|
||||
RoleContainerModel container = role.getContainer();
|
||||
if (container instanceof RealmModel) {
|
||||
realmRoles.add(role);
|
||||
}
|
||||
}
|
||||
return realmRoles;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRoleMappings() {
|
||||
// we query ids only as the role might be cached and following the @ManyToOne will result in a load
|
||||
// even if we're getting just the id.
|
||||
TypedQuery<String> query = em.createNamedQuery("groupRoleMappingIds", String.class);
|
||||
query.setParameter("group", getGroup());
|
||||
List<String> ids = query.getResultList();
|
||||
Set<RoleModel> roles = new HashSet<RoleModel>();
|
||||
for (String roleId : ids) {
|
||||
RoleModel roleById = realm.getRoleById(roleId);
|
||||
if (roleById == null) continue;
|
||||
roles.add(roleById);
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRoleMapping(RoleModel role) {
|
||||
if (group == null || role == null) return;
|
||||
|
||||
TypedQuery<GroupRoleMappingEntity> query = getGroupRoleMappingEntityTypedQuery(role);
|
||||
List<GroupRoleMappingEntity> results = query.getResultList();
|
||||
if (results.size() == 0) return;
|
||||
for (GroupRoleMappingEntity entity : results) {
|
||||
em.remove(entity);
|
||||
}
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
||||
Set<RoleModel> roleMappings = getRoleMappings();
|
||||
|
||||
Set<RoleModel> roles = new HashSet<RoleModel>();
|
||||
for (RoleModel role : roleMappings) {
|
||||
RoleContainerModel container = role.getContainer();
|
||||
if (container instanceof ClientModel) {
|
||||
ClientModel appModel = (ClientModel)container;
|
||||
if (appModel.getId().equals(app.getId())) {
|
||||
roles.add(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof UserModel)) return false;
|
||||
|
||||
UserModel that = (UserModel) o;
|
||||
return that.getId().equals(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -2,11 +2,13 @@ package org.keycloak.models.jpa;
|
|||
|
||||
import org.keycloak.migration.MigrationModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.jpa.entities.ClientEntity;
|
||||
import org.keycloak.models.jpa.entities.GroupEntity;
|
||||
import org.keycloak.models.jpa.entities.RealmEntity;
|
||||
import org.keycloak.models.jpa.entities.RoleEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
@ -101,6 +103,14 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
adapter.removeClient(a.getId());
|
||||
}
|
||||
|
||||
int num = em.createNamedQuery("deleteGroupRoleMappingsByRealm")
|
||||
.setParameter("realm", realm).executeUpdate();
|
||||
num = em.createNamedQuery("deleteGroupAttributesByRealm")
|
||||
.setParameter("realm", realm).executeUpdate();
|
||||
num = em.createNamedQuery("deleteGroupsByRealm")
|
||||
.setParameter("realm", realm).executeUpdate();
|
||||
|
||||
|
||||
em.remove(realm);
|
||||
return true;
|
||||
}
|
||||
|
@ -117,6 +127,14 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
return new RoleAdapter(realm, em, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupById(String id, RealmModel realm) {
|
||||
GroupEntity groupEntity = em.find(GroupEntity.class, id);
|
||||
if (groupEntity == null) return null;
|
||||
if (!groupEntity.getRealm().getId().equals(realm.getId())) return null;
|
||||
return new GroupAdapter(realm, em, groupEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClientById(String id, RealmModel realm) {
|
||||
ClientEntity app = em.find(ClientEntity.class, id);
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.models.jpa;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -175,6 +176,8 @@ public class JpaUserProvider implements UserProvider {
|
|||
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||
num = em.createNamedQuery("deleteUsersByRealm")
|
||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||
num = em.createNamedQuery("deleteUserGroupMembershipByRealm")
|
||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -225,6 +228,25 @@ public class JpaUserProvider implements UserProvider {
|
|||
.executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery("groupMembership", UserEntity.class);
|
||||
query.setParameter("groupId", group.getId());
|
||||
List<UserEntity> results = query.getResultList();
|
||||
|
||||
List<UserModel> users = new ArrayList<UserModel>();
|
||||
for (UserEntity user : results) {
|
||||
users.add(new UserAdapter(realm, em, user));
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, GroupModel group) {
|
||||
em.createNamedQuery("deleteUserGroupMembershipsByGroup").setParameter("groupId", group.getId()).executeUpdate();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserById(String id, RealmModel realm) {
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserById", UserEntity.class);
|
||||
|
@ -324,6 +346,25 @@ public class JpaUserProvider implements UserProvider {
|
|||
return users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery("groupMembership", UserEntity.class);
|
||||
query.setParameter("groupId", group.getId());
|
||||
if (firstResult != -1) {
|
||||
query.setFirstResult(firstResult);
|
||||
}
|
||||
if (maxResults != -1) {
|
||||
query.setMaxResults(maxResults);
|
||||
}
|
||||
List<UserEntity> results = query.getResultList();
|
||||
|
||||
List<UserModel> users = new ArrayList<UserModel>();
|
||||
for (UserEntity user : results) {
|
||||
users.add(new UserAdapter(realm, em, user));
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUser(String search, RealmModel realm) {
|
||||
return searchForUser(search, realm, -1, -1);
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
|
|||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -24,6 +25,7 @@ import org.keycloak.models.jpa.entities.AuthenticationExecutionEntity;
|
|||
import org.keycloak.models.jpa.entities.AuthenticationFlowEntity;
|
||||
import org.keycloak.models.jpa.entities.AuthenticatorConfigEntity;
|
||||
import org.keycloak.models.jpa.entities.ClientEntity;
|
||||
import org.keycloak.models.jpa.entities.GroupEntity;
|
||||
import org.keycloak.models.jpa.entities.IdentityProviderEntity;
|
||||
import org.keycloak.models.jpa.entities.IdentityProviderMapperEntity;
|
||||
import org.keycloak.models.jpa.entities.RealmAttributeEntity;
|
||||
|
@ -1944,4 +1946,84 @@ public class RealmAdapter implements RealmModel {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveGroup(GroupModel group, GroupModel toParent) {
|
||||
if (group.getParentId() != null) {
|
||||
group.getParent().removeChild(group);
|
||||
}
|
||||
group.setParent(toParent);
|
||||
if (toParent != null) toParent.addChild(group);
|
||||
else addTopLevelGroup(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupById(String id) {
|
||||
return session.realms().getGroupById(id, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GroupModel> getGroups() {
|
||||
List<GroupModel> list = new LinkedList<>();
|
||||
Collection<GroupEntity> groups = realm.getGroups();
|
||||
if (groups == null) return list;
|
||||
for (GroupEntity entity : groups) {
|
||||
list.add(new GroupAdapter(this, em, entity));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GroupModel> getTopLevelGroups() {
|
||||
List<GroupModel> all = getGroups();
|
||||
Iterator<GroupModel> it = all.iterator();
|
||||
while (it.hasNext()) {
|
||||
GroupModel group = it.next();
|
||||
if (group.getParent() != null) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeGroup(GroupModel group) {
|
||||
if (group == null) {
|
||||
return false;
|
||||
}
|
||||
GroupEntity groupEntity = GroupAdapter.toEntity(group, em);
|
||||
if (!groupEntity.getRealm().getId().equals(getId())) {
|
||||
return false;
|
||||
}
|
||||
for (GroupModel subGroup : group.getSubGroups()) {
|
||||
removeGroup(subGroup);
|
||||
}
|
||||
|
||||
|
||||
session.users().preRemove(this, group);
|
||||
moveGroup(group, null);
|
||||
realm.getGroups().remove(groupEntity);
|
||||
em.createNamedQuery("deleteGroupAttributesByGroup").setParameter("group", groupEntity).executeUpdate();
|
||||
em.createNamedQuery("deleteGroupRoleMappingsByGroup").setParameter("group", groupEntity).executeUpdate();
|
||||
em.remove(groupEntity);
|
||||
return true;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel createGroup(String name) {
|
||||
GroupEntity groupEntity = new GroupEntity();
|
||||
groupEntity.setId(KeycloakModelUtils.generateId());
|
||||
groupEntity.setName(name);
|
||||
groupEntity.setRealm(realm);
|
||||
em.persist(groupEntity);
|
||||
|
||||
return new GroupAdapter(this, em, groupEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTopLevelGroup(GroupModel subGroup) {
|
||||
subGroup.setParent(null);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.models.jpa;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
|
@ -19,6 +20,7 @@ import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
|
|||
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
|
||||
import org.keycloak.models.jpa.entities.UserAttributeEntity;
|
||||
import org.keycloak.models.jpa.entities.UserEntity;
|
||||
import org.keycloak.models.jpa.entities.UserGroupMembershipEntity;
|
||||
import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
|
||||
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
@ -485,6 +487,63 @@ public class UserAdapter implements UserModel {
|
|||
em.flush();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Set<GroupModel> getGroups() {
|
||||
// we query ids only as the group might be cached and following the @ManyToOne will result in a load
|
||||
// even if we're getting just the id.
|
||||
TypedQuery<String> query = em.createNamedQuery("userGroupIds", String.class);
|
||||
query.setParameter("user", getUser());
|
||||
List<String> ids = query.getResultList();
|
||||
Set<GroupModel> groups = new HashSet<>();
|
||||
for (String groupId : ids) {
|
||||
GroupModel group = realm.getGroupById(groupId);
|
||||
if (group == null) continue;
|
||||
groups.add(group);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void joinGroup(GroupModel group) {
|
||||
if (isMemberOf(group)) return;
|
||||
UserGroupMembershipEntity entity = new UserGroupMembershipEntity();
|
||||
entity.setUser(getUser());
|
||||
entity.setGroupId(group.getId());
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
em.detach(entity);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leaveGroup(GroupModel group) {
|
||||
if (user == null || group == null) return;
|
||||
|
||||
TypedQuery<UserGroupMembershipEntity> query = getUserGroupMappingQuery(group);
|
||||
List<UserGroupMembershipEntity> results = query.getResultList();
|
||||
if (results.size() == 0) return;
|
||||
for (UserGroupMembershipEntity entity : results) {
|
||||
em.remove(entity);
|
||||
}
|
||||
em.flush();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMemberOf(GroupModel group) {
|
||||
Set<GroupModel> roles = getGroups();
|
||||
return KeycloakModelUtils.isMember(roles, group);
|
||||
}
|
||||
|
||||
protected TypedQuery<UserGroupMembershipEntity> getUserGroupMappingQuery(GroupModel group) {
|
||||
TypedQuery<UserGroupMembershipEntity> query = em.createNamedQuery("userMemberOf", UserGroupMembershipEntity.class);
|
||||
query.setParameter("user", getUser());
|
||||
query.setParameter("groupId", group.getId());
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
Set<RoleModel> roles = getRoleMappings();
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package org.keycloak.models.jpa.entities;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getGroupAttributesByNameAndValue", query="select attr from GroupAttributeEntity attr where attr.name = :name and attr.value = :value"),
|
||||
@NamedQuery(name="deleteGroupAttributesByGroup", query="delete from GroupAttributeEntity attr where attr.group = :group"),
|
||||
@NamedQuery(name="deleteGroupAttributesByRealm", query="delete from GroupAttributeEntity attr where attr.group IN (select u from GroupEntity u where u.realm=:realm)")
|
||||
})
|
||||
@Table(name="GROUP_ATTRIBUTE")
|
||||
@Entity
|
||||
public class GroupAttributeEntity {
|
||||
|
||||
@Id
|
||||
@Column(name="ID", length = 36)
|
||||
protected String id;
|
||||
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
@JoinColumn(name = "GROUP_ID")
|
||||
protected GroupEntity group;
|
||||
|
||||
@Column(name = "NAME")
|
||||
protected String name;
|
||||
@Column(name = "VALUE")
|
||||
protected String value;
|
||||
|
||||
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 String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public GroupEntity getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public void setGroup(GroupEntity group) {
|
||||
this.group = group;
|
||||
}
|
||||
}
|
108
model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
Executable file
108
model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
Executable file
|
@ -0,0 +1,108 @@
|
|||
package org.keycloak.models.jpa.entities;
|
||||
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getAllGroupsByRealm", query="select u from GroupEntity u where u.realm = :realm order by u.name"),
|
||||
@NamedQuery(name="getGroupById", query="select u from GroupEntity u where u.id = :id and u.realm = :realm"),
|
||||
@NamedQuery(name="getGroupIdsByParent", query="select u.id from GroupEntity u where u.parent = :parent"),
|
||||
@NamedQuery(name="getGroupCount", query="select count(u) from GroupEntity u where u.realm = :realm"),
|
||||
@NamedQuery(name="deleteGroupsByRealm", query="delete from GroupEntity u where u.realm = :realm")
|
||||
})
|
||||
@Entity
|
||||
@Table(name="KEYCLOAK_GROUP")
|
||||
public class GroupEntity {
|
||||
@Id
|
||||
@Column(name="ID", length = 36)
|
||||
protected String id;
|
||||
|
||||
@Column(name = "NAME")
|
||||
protected String name;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "PARENT_GROUP")
|
||||
private GroupEntity parent;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "REALM_ID")
|
||||
private RealmEntity realm;
|
||||
|
||||
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="group")
|
||||
protected Collection<GroupAttributeEntity> attributes = new ArrayList<GroupAttributeEntity>();
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Collection<GroupAttributeEntity> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(Collection<GroupAttributeEntity> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public RealmEntity getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(RealmEntity realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public GroupEntity getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(GroupEntity parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
GroupEntity that = (GroupEntity) o;
|
||||
|
||||
if (!id.equals(that.id)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package org.keycloak.models.jpa.entities;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="groupHasRole", query="select m from GroupRoleMappingEntity m where m.group = :group and m.roleId = :roleId"),
|
||||
@NamedQuery(name="groupRoleMappings", query="select m from GroupRoleMappingEntity m where m.group = :group"),
|
||||
@NamedQuery(name="groupRoleMappingIds", query="select m.roleId from GroupRoleMappingEntity m where m.group = :group"),
|
||||
@NamedQuery(name="deleteGroupRoleMappingsByRealm", query="delete from GroupRoleMappingEntity mapping where mapping.group IN (select u from GroupEntity u where u.realm=:realm)"),
|
||||
@NamedQuery(name="deleteGroupRoleMappingsByRole", query="delete from GroupRoleMappingEntity m where m.roleId = :roleId"),
|
||||
@NamedQuery(name="deleteGroupRoleMappingsByGroup", query="delete from GroupRoleMappingEntity m where m.group = :group")
|
||||
|
||||
})
|
||||
@Table(name="GROUP_ROLE_MAPPING")
|
||||
@Entity
|
||||
@IdClass(GroupRoleMappingEntity.Key.class)
|
||||
public class GroupRoleMappingEntity {
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
@JoinColumn(name="GROUP_ID")
|
||||
protected GroupEntity group;
|
||||
|
||||
@Id
|
||||
@Column(name = "ROLE_ID")
|
||||
protected String roleId;
|
||||
|
||||
public GroupEntity getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public void setGroup(GroupEntity group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public String getRoleId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
public void setRoleId(String roleId) {
|
||||
this.roleId = roleId;
|
||||
}
|
||||
|
||||
public static class Key implements Serializable {
|
||||
|
||||
protected GroupEntity group;
|
||||
|
||||
protected String roleId;
|
||||
|
||||
public Key() {
|
||||
}
|
||||
|
||||
public Key(GroupEntity group, String roleId) {
|
||||
this.group = group;
|
||||
this.roleId = roleId;
|
||||
}
|
||||
|
||||
public GroupEntity getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public String getRoleId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Key key = (Key) o;
|
||||
|
||||
if (!roleId.equals(key.roleId)) return false;
|
||||
if (!group.equals(key.group)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = group.hashCode();
|
||||
result = 31 * result + roleId.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -133,6 +133,9 @@ public class RealmEntity {
|
|||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||
Collection<GroupEntity> groups = new ArrayList<GroupEntity>();
|
||||
|
||||
@ElementCollection
|
||||
@MapKeyColumn(name="NAME")
|
||||
@Column(name="VALUE")
|
||||
|
@ -718,5 +721,21 @@ public class RealmEntity {
|
|||
public void setClientAuthenticationFlow(String clientAuthenticationFlow) {
|
||||
this.clientAuthenticationFlow = clientAuthenticationFlow;
|
||||
}
|
||||
|
||||
public Collection<GroupEntity> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public void setGroups(Collection<GroupEntity> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
public void addGroup(GroupEntity group) {
|
||||
if (groups == null) {
|
||||
groups = new ArrayList<GroupEntity>();
|
||||
}
|
||||
groups.add(group);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
package org.keycloak.models.jpa.entities;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="userMemberOf", query="select m from UserGroupMembershipEntity m where m.user = :user and m.groupId = :groupId"),
|
||||
@NamedQuery(name="userGroupMembership", query="select m from UserGroupMembershipEntity m where m.user = :user"),
|
||||
@NamedQuery(name="groupMembership", query="select g.user from UserGroupMembershipEntity g where g.groupId = :groupId"),
|
||||
@NamedQuery(name="userGroupIds", query="select m.groupId from UserGroupMembershipEntity m where m.user = :user"),
|
||||
@NamedQuery(name="deleteUserGroupMembershipByRealm", query="delete from UserGroupMembershipEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId)"),
|
||||
@NamedQuery(name="deleteUserGroupMembershipsByRealmAndLink", query="delete from UserGroupMembershipEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
|
||||
@NamedQuery(name="deleteUserGroupMembershipsByGroup", query="delete from UserGroupMembershipEntity m where m.groupId = :groupId"),
|
||||
@NamedQuery(name="deleteUserGroupMembershipsByUser", query="delete from UserGroupMembershipEntity m where m.user = :user")
|
||||
|
||||
})
|
||||
@Table(name="USER_GROUP_MEMBERSHIP")
|
||||
@Entity
|
||||
@IdClass(UserGroupMembershipEntity.Key.class)
|
||||
public class UserGroupMembershipEntity {
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
@JoinColumn(name="USER_ID")
|
||||
protected UserEntity user;
|
||||
|
||||
@Id
|
||||
@Column(name = "GROUP_ID")
|
||||
protected String groupId;
|
||||
|
||||
public UserEntity getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(UserEntity user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public static class Key implements Serializable {
|
||||
|
||||
protected UserEntity user;
|
||||
|
||||
protected String groupId;
|
||||
|
||||
public Key() {
|
||||
}
|
||||
|
||||
public Key(UserEntity user, String groupId) {
|
||||
this.user = user;
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public UserEntity getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Key key = (Key) o;
|
||||
|
||||
if (!groupId.equals(key.groupId)) return false;
|
||||
if (!user.equals(key.user)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = user.hashCode();
|
||||
result = 31 * result + groupId.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
package org.keycloak.models.mongo.keycloak.adapters;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.QueryBuilder;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
|
||||
import org.keycloak.models.mongo.utils.MongoModelUtils;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class GroupAdapter extends AbstractMongoAdapter<MongoGroupEntity> implements GroupModel {
|
||||
|
||||
private final MongoGroupEntity group;
|
||||
private RealmModel realm;
|
||||
private KeycloakSession session;
|
||||
|
||||
public GroupAdapter(KeycloakSession session, RealmModel realm, MongoGroupEntity group, MongoStoreInvocationContext invContext) {
|
||||
super(invContext);
|
||||
this.group = group;
|
||||
this.realm = realm;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return group.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return group.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
group.setName(name);
|
||||
updateGroup();
|
||||
}
|
||||
|
||||
protected void updateGroup() {
|
||||
super.updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MongoGroupEntity getMongoEntity() {
|
||||
return group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof GroupModel)) return false;
|
||||
|
||||
GroupModel that = (GroupModel) o;
|
||||
return that.getId().equals(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
if (group.getAttributes() == null) {
|
||||
group.setAttributes(new HashMap<String, List<String>>());
|
||||
}
|
||||
|
||||
List<String> attrValues = new ArrayList<>();
|
||||
attrValues.add(value);
|
||||
group.getAttributes().put(name, attrValues);
|
||||
updateGroup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
if (group.getAttributes() == null) {
|
||||
group.setAttributes(new HashMap<String, List<String>>());
|
||||
}
|
||||
|
||||
group.getAttributes().put(name, values);
|
||||
updateGroup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
if (group.getAttributes() == null) return;
|
||||
|
||||
group.getAttributes().remove(name);
|
||||
updateGroup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstAttribute(String name) {
|
||||
if (group.getAttributes()==null) return null;
|
||||
|
||||
List<String> attrValues = group.getAttributes().get(name);
|
||||
return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
if (group.getAttributes()==null) return Collections.<String>emptyList();
|
||||
List<String> attrValues = group.getAttributes().get(name);
|
||||
return (attrValues == null) ? Collections.<String>emptyList() : Collections.unmodifiableList(attrValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return group.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map) group.getAttributes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
Set<RoleModel> roles = getRoleMappings();
|
||||
return KeycloakModelUtils.hasRole(roles, role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantRole(RoleModel role) {
|
||||
getMongoStore().pushItemToList(group, "roleIds", role.getId(), true, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRoleMappings() {
|
||||
if (group.getRoleIds() == null || group.getRoleIds().isEmpty()) return Collections.EMPTY_SET;
|
||||
Set<RoleModel> roles = new HashSet<>();
|
||||
for (String id : group.getRoleIds()) {
|
||||
roles.add(realm.getRoleById(id));
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRealmRoleMappings() {
|
||||
Set<RoleModel> allRoles = getRoleMappings();
|
||||
|
||||
// Filter to retrieve just realm roles
|
||||
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
|
||||
for (RoleModel role : allRoles) {
|
||||
if (role.getContainer() instanceof RealmModel) {
|
||||
realmRoles.add(role);
|
||||
}
|
||||
}
|
||||
return realmRoles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRoleMapping(RoleModel role) {
|
||||
if (group == null || role == null) return;
|
||||
|
||||
getMongoStore().pullItemFromList(group, "roleIds", role.getId(), invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
||||
Set<RoleModel> result = new HashSet<RoleModel>();
|
||||
Set<RoleModel> roles = getRoleMappings();
|
||||
|
||||
for (RoleModel role : roles) {
|
||||
if (app.equals(role.getContainer())) {
|
||||
result.add(role);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getParent() {
|
||||
if (group.getParentId() == null) return null;
|
||||
return realm.getGroupById(group.getParentId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentId() {
|
||||
return group.getParentId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GroupModel> getSubGroups() {
|
||||
Set<GroupModel> subGroups = new HashSet<>();
|
||||
for (GroupModel groupModel : realm.getGroups()) {
|
||||
if (groupModel.getParent().equals(this)) {
|
||||
subGroups.add(groupModel);
|
||||
}
|
||||
}
|
||||
return subGroups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(GroupModel group) {
|
||||
this.group.setParentId(group.getId());
|
||||
updateGroup();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChild(GroupModel subGroup) {
|
||||
subGroup.setParent(this);
|
||||
updateGroup();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeChild(GroupModel subGroup) {
|
||||
subGroup.setParent(null);
|
||||
updateGroup();
|
||||
|
||||
}
|
||||
}
|
|
@ -7,11 +7,13 @@ import org.keycloak.connections.mongo.api.MongoStore;
|
|||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.migration.MigrationModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
|
||||
|
@ -121,6 +123,14 @@ public class MongoRealmProvider implements RealmProvider {
|
|||
return new RoleAdapter(session, realm, role, null, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupById(String id, RealmModel realm) {
|
||||
MongoGroupEntity group = getMongoStore().loadEntity(MongoGroupEntity.class, id, invocationContext);
|
||||
if (group == null) return null;
|
||||
if (group.getRealmId() != null && !group.getRealmId().equals(realm.getId())) return null;
|
||||
return new GroupAdapter(session, realm, group, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClientById(String id, RealmModel realm) {
|
||||
MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, id, invocationContext);
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -90,10 +91,26 @@ public class MongoUserProvider implements UserProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
QueryBuilder queryBuilder = new QueryBuilder()
|
||||
.and("realmId").is(realm.getId());
|
||||
queryBuilder.and("groupIds").is(group.getId());
|
||||
DBObject sort = new BasicDBObject("username", 1);
|
||||
|
||||
List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, queryBuilder.get(), sort, firstResult, maxResults, invocationContext);
|
||||
return convertUserEntities(realm, users);
|
||||
}
|
||||
|
||||
protected MongoStore getMongoStore() {
|
||||
return invocationContext.getMongoStore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
|
||||
return getGroupMembers(realm, group, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
|
||||
DBObject query = new QueryBuilder()
|
||||
|
@ -424,6 +441,17 @@ public class MongoUserProvider implements UserProvider {
|
|||
getMongoStore().updateEntities(MongoUserConsentEntity.class, query, pull, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, GroupModel group) {
|
||||
// Remove this role from all users, which has it
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("groupIds").is(group.getId())
|
||||
.get();
|
||||
|
||||
DBObject pull = new BasicDBObject("$pull", query);
|
||||
getMongoStore().updateEntities(MongoUserEntity.class, query, pull, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, RoleModel role) {
|
||||
// Remove this role from all users, which has it
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
|
|||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -34,6 +35,7 @@ import org.keycloak.models.entities.RequiredCredentialEntity;
|
|||
import org.keycloak.models.entities.UserFederationMapperEntity;
|
||||
import org.keycloak.models.entities.UserFederationProviderEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
@ -607,6 +609,79 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
return model.getRoleById(id, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel createGroup(String name) {
|
||||
MongoGroupEntity group = new MongoGroupEntity();
|
||||
group.setId(KeycloakModelUtils.generateId());
|
||||
group.setName(name);
|
||||
group.setRealmId(getId());
|
||||
|
||||
getMongoStore().insertEntity(group, invocationContext);
|
||||
|
||||
return new GroupAdapter(session, this, group, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTopLevelGroup(GroupModel subGroup) {
|
||||
subGroup.setParent(null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveGroup(GroupModel group, GroupModel toParent) {
|
||||
if (group.getParentId() != null) {
|
||||
group.getParent().removeChild(group);
|
||||
}
|
||||
group.setParent(toParent);
|
||||
if (toParent != null) toParent.addChild(group);
|
||||
else addTopLevelGroup(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupById(String id) {
|
||||
return model.getGroupById(id, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GroupModel> getGroups() {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("realmId").is(getId())
|
||||
.get();
|
||||
List<MongoGroupEntity> groups = getMongoStore().loadEntities(MongoGroupEntity.class, query, invocationContext);
|
||||
|
||||
List<GroupModel> result = new LinkedList<>();
|
||||
|
||||
if (groups == null) return result;
|
||||
for (MongoGroupEntity group : groups) {
|
||||
result.add(new GroupAdapter(session, this, group, invocationContext));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GroupModel> getTopLevelGroups() {
|
||||
List<GroupModel> all = getGroups();
|
||||
Iterator<GroupModel> it = all.iterator();
|
||||
while (it.hasNext()) {
|
||||
GroupModel group = it.next();
|
||||
if (group.getParent() != null) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeGroup(GroupModel group) {
|
||||
for (GroupModel subGroup : group.getSubGroups()) {
|
||||
removeGroup(subGroup);
|
||||
}
|
||||
session.users().preRemove(this, group);
|
||||
moveGroup(group, null);
|
||||
return getMongoStore().removeEntity(MongoGroupEntity.class, group.getId(), invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDefaultRoles() {
|
||||
return realm.getDefaultRoles();
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.mongodb.QueryBuilder;
|
|||
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
|
@ -450,6 +451,37 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
|||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GroupModel> getGroups() {
|
||||
if (user.getGroupIds() == null && user.getGroupIds().size() == 0) return Collections.EMPTY_SET;
|
||||
Set<GroupModel> groups = new HashSet<>();
|
||||
for (String id : user.getGroupIds()) {
|
||||
groups.add(realm.getGroupById(id));
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void joinGroup(GroupModel group) {
|
||||
getMongoStore().pushItemToList(getUser(), "groupIds", group.getId(), true, invocationContext);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leaveGroup(GroupModel group) {
|
||||
if (user == null || group == null) return;
|
||||
|
||||
getMongoStore().pullItemFromList(getUser(), "groupIds", group.getId(), invocationContext);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMemberOf(GroupModel group) {
|
||||
if (user.getGroupIds().contains(group.getId())) return true;
|
||||
Set<GroupModel> groups = getGroups();
|
||||
return KeycloakModelUtils.isMember(groups, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
Set<RoleModel> roles = getRoleMappings();
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package org.keycloak.models.mongo.keycloak.entities;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.QueryBuilder;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||
import org.keycloak.connections.mongo.api.MongoField;
|
||||
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.connections.mongo.api.MongoStore;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.entities.GroupEntity;
|
||||
import org.keycloak.models.entities.RoleEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*/
|
||||
@MongoCollection(collectionName = "groups")
|
||||
public class MongoGroupEntity extends GroupEntity implements MongoIdentifiableEntity {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MongoGroupEntity.class);
|
||||
|
||||
@Override
|
||||
public void afterRemove(MongoStoreInvocationContext invContext) {
|
||||
}
|
||||
}
|
|
@ -4,11 +4,13 @@ import com.mongodb.DBObject;
|
|||
import com.mongodb.QueryBuilder;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.entities.ClientEntity;
|
||||
import org.keycloak.models.mongo.keycloak.adapters.ClientAdapter;
|
||||
import org.keycloak.models.mongo.keycloak.adapters.GroupAdapter;
|
||||
import org.keycloak.models.mongo.keycloak.adapters.UserAdapter;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
||||
|
|
|
@ -511,6 +511,7 @@ public class SamlService {
|
|||
processor.setClientSession(clientSession)
|
||||
.setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
|
||||
.setFlowId(flowId)
|
||||
.setBrowserFlow(true)
|
||||
.setConnection(clientConnection)
|
||||
.setEventBuilder(event)
|
||||
.setProtector(authManager.getProtector())
|
||||
|
|
|
@ -347,6 +347,7 @@ public class AuthorizationEndpoint {
|
|||
processor.setClientSession(clientSession)
|
||||
.setFlowPath(flowPath)
|
||||
.setFlowId(flowId)
|
||||
.setBrowserFlow(true)
|
||||
.setConnection(clientConnection)
|
||||
.setEventBuilder(event)
|
||||
.setProtector(authManager.getProtector())
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.jboss.resteasy.spi.NotFoundException;
|
|||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleMapperModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
|
@ -29,17 +30,17 @@ import java.util.Set;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UserClientRoleMappingsResource {
|
||||
protected static final Logger logger = Logger.getLogger(UserClientRoleMappingsResource.class);
|
||||
public class ClientRoleMappingsResource {
|
||||
protected static final Logger logger = Logger.getLogger(ClientRoleMappingsResource.class);
|
||||
|
||||
protected RealmModel realm;
|
||||
protected RealmAuth auth;
|
||||
protected UserModel user;
|
||||
protected RoleMapperModel user;
|
||||
protected ClientModel client;
|
||||
protected AdminEventBuilder adminEvent;
|
||||
private UriInfo uriInfo;
|
||||
|
||||
public UserClientRoleMappingsResource(UriInfo uriInfo, RealmModel realm, RealmAuth auth, UserModel user, ClientModel client, AdminEventBuilder adminEvent) {
|
||||
public ClientRoleMappingsResource(UriInfo uriInfo, RealmModel realm, RealmAuth auth, RoleMapperModel user, ClientModel client, AdminEventBuilder adminEvent) {
|
||||
this.uriInfo = uriInfo;
|
||||
this.realm = realm;
|
||||
this.auth = auth;
|
||||
|
@ -105,10 +106,10 @@ public class UserClientRoleMappingsResource {
|
|||
return getAvailableRoles(user, available);
|
||||
}
|
||||
|
||||
public static List<RoleRepresentation> getAvailableRoles(UserModel user, Set<RoleModel> available) {
|
||||
public static List<RoleRepresentation> getAvailableRoles(RoleMapperModel mapper, Set<RoleModel> available) {
|
||||
Set<RoleModel> roles = new HashSet<RoleModel>();
|
||||
for (RoleModel roleModel : available) {
|
||||
if (user.hasRole(roleModel)) continue;
|
||||
if (mapper.hasRole(roleModel)) continue;
|
||||
roles.add(roleModel);
|
||||
}
|
||||
|
261
services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java
Executable file
261
services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java
Executable file
|
@ -0,0 +1,261 @@
|
|||
package org.keycloak.services.resources.admin;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.jboss.resteasy.spi.NotFoundException;
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.net.URI;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Bill Burke
|
||||
*/
|
||||
public class GroupResource {
|
||||
|
||||
private static Logger logger = Logger.getLogger(GroupResource.class);
|
||||
|
||||
private final RealmModel realm;
|
||||
private final KeycloakSession session;
|
||||
private final RealmAuth auth;
|
||||
private final AdminEventBuilder adminEvent;
|
||||
|
||||
public GroupResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) {
|
||||
this.realm = realm;
|
||||
this.session = session;
|
||||
this.auth = auth;
|
||||
this.adminEvent = adminEvent;
|
||||
}
|
||||
|
||||
@Context private UriInfo uriInfo;
|
||||
|
||||
public GroupResource(RealmAuth auth, RealmModel realm, KeycloakSession session, AdminEventBuilder adminEvent) {
|
||||
this.realm = realm;
|
||||
this.session = session;
|
||||
this.auth = auth;
|
||||
this.adminEvent = adminEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group hierarchy. Only name and ids are returned.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<GroupRepresentation> getGroups() {
|
||||
this.auth.requireView();
|
||||
return ModelToRepresentation.toGroupHierarchy(realm, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or create child as a top level group. This will update the group and set the parent if it exists. Create it and set the parent
|
||||
* if the group doesn't exist.
|
||||
*
|
||||
* @param rep
|
||||
*/
|
||||
@POST
|
||||
@Path("{id}")
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response addRealmGroup(@PathParam("id") String parentId, GroupRepresentation rep) {
|
||||
GroupModel parentModel = realm.getGroupById(parentId);
|
||||
Response.ResponseBuilder builder = Response.status(204);
|
||||
if (parentModel == null) {
|
||||
throw new NotFoundException("Could not find parent by id");
|
||||
}
|
||||
GroupModel child = null;
|
||||
if (rep.getId() != null) {
|
||||
child = realm.getGroupById(rep.getId());
|
||||
if (child == null) {
|
||||
throw new NotFoundException("Could not find child by id");
|
||||
}
|
||||
} else {
|
||||
child = realm.createGroup(rep.getName());
|
||||
updateGroup(rep, child);
|
||||
URI uri = uriInfo.getBaseUriBuilder()
|
||||
.path(uriInfo.getMatchedURIs().get(1))
|
||||
.path(child.getId()).build();
|
||||
builder.status(201).location(uri);
|
||||
|
||||
}
|
||||
child.setParent(parentModel);
|
||||
GroupRepresentation childRep = ModelToRepresentation.toRepresentation(child, true);
|
||||
return builder.type(MediaType.APPLICATION_JSON_TYPE).entity(childRep).build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Does not expand hierarchy. Subgroups will not be set.
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public GroupRepresentation getGroupById(@PathParam("id") String id) {
|
||||
this.auth.requireView();
|
||||
GroupModel group = realm.getGroupById(id);
|
||||
if (group == null) {
|
||||
throw new NotFoundException("Could not find group by id");
|
||||
}
|
||||
|
||||
return ModelToRepresentation.toRepresentation(group, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update group
|
||||
*
|
||||
* @param rep
|
||||
*/
|
||||
@PUT
|
||||
@Path("{id}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void updateGroup(@PathParam("id") String id, GroupRepresentation rep) {
|
||||
GroupModel model = realm.getGroupById(id);
|
||||
if (model == null) {
|
||||
throw new NotFoundException("Could not find group by id");
|
||||
}
|
||||
|
||||
updateGroup(rep, model);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{id}")
|
||||
public void deleteGroup(@PathParam("id") String id) {
|
||||
GroupModel model = realm.getGroupById(id);
|
||||
if (model == null) {
|
||||
throw new NotFoundException("Could not find group by id");
|
||||
}
|
||||
realm.removeGroup(model);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set or create child. This will just set the parent if it exists. Create it and set the parent
|
||||
* if the group doesn't exist.
|
||||
*
|
||||
* @param rep
|
||||
*/
|
||||
@POST
|
||||
@Path("{id}/children")
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response addGroup(@PathParam("id") String parentId, GroupRepresentation rep) {
|
||||
GroupModel parentModel = realm.getGroupById(parentId);
|
||||
Response.ResponseBuilder builder = Response.status(204);
|
||||
if (parentModel == null) {
|
||||
throw new NotFoundException("Could not find parent by id");
|
||||
}
|
||||
GroupModel child = null;
|
||||
if (rep.getId() != null) {
|
||||
child = realm.getGroupById(rep.getId());
|
||||
if (child == null) {
|
||||
throw new NotFoundException("Could not find child by id");
|
||||
}
|
||||
} else {
|
||||
child = realm.createGroup(rep.getName());
|
||||
updateGroup(rep, child);
|
||||
URI uri = uriInfo.getBaseUriBuilder()
|
||||
.path(uriInfo.getMatchedURIs().get(1))
|
||||
.path(child.getId()).build();
|
||||
builder.status(201).location(uri);
|
||||
|
||||
}
|
||||
realm.moveGroup(child, parentModel);
|
||||
GroupRepresentation childRep = ModelToRepresentation.toRepresentation(child, true);
|
||||
return builder.type(MediaType.APPLICATION_JSON_TYPE).entity(childRep).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* create or add a top level realm groupSet or create child. This will update the group and set the parent if it exists. Create it and set the parent
|
||||
* if the group doesn't exist.
|
||||
*
|
||||
* @param rep
|
||||
*/
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response addTopLevelGroup(GroupRepresentation rep) {
|
||||
GroupModel child = null;
|
||||
Response.ResponseBuilder builder = Response.status(204);
|
||||
if (rep.getId() != null) {
|
||||
child = realm.getGroupById(rep.getId());
|
||||
if (child == null) {
|
||||
throw new NotFoundException("Could not find child by id");
|
||||
}
|
||||
} else {
|
||||
child = realm.createGroup(rep.getName());
|
||||
updateGroup(rep, child);
|
||||
URI uri = uriInfo.getAbsolutePathBuilder()
|
||||
.path(child.getId()).build();
|
||||
builder.status(201).location(uri);
|
||||
}
|
||||
realm.moveGroup(child, null);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public void updateGroup(GroupRepresentation rep, GroupModel model) {
|
||||
if (rep.getName() != null) model.setName(rep.getName());
|
||||
|
||||
if (rep.getAttributes() != null) {
|
||||
Set<String> attrsToRemove = new HashSet<>(model.getAttributes().keySet());
|
||||
attrsToRemove.removeAll(rep.getAttributes().keySet());
|
||||
for (Map.Entry<String, List<String>> attr : rep.getAttributes().entrySet()) {
|
||||
model.setAttribute(attr.getKey(), attr.getValue());
|
||||
}
|
||||
|
||||
for (String attr : attrsToRemove) {
|
||||
model.removeAttribute(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Path("{id}/role-mappings")
|
||||
public RoleMapperResource getRoleMappings(@PathParam("id") String id) {
|
||||
|
||||
GroupModel group = session.realms().getGroupById(id, realm);
|
||||
if (group == null) {
|
||||
throw new NotFoundException("Group not found");
|
||||
}
|
||||
auth.init(RealmAuth.Resource.USER);
|
||||
|
||||
RoleMapperResource resource = new RoleMapperResource(realm, auth, group, adminEvent);
|
||||
ResteasyProviderFactory.getInstance().injectProperties(resource);
|
||||
return resource;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -619,4 +619,11 @@ public class RealmAdminResource {
|
|||
return new IdentityProvidersResource(realm, session, this.auth, adminEvent);
|
||||
}
|
||||
|
||||
@Path("groups")
|
||||
public GroupResource getGroups() {
|
||||
GroupResource resource = new GroupResource(realm, session, this.auth, adminEvent);
|
||||
ResteasyProviderFactory.getInstance().injectProperties(resource);
|
||||
return resource;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
package org.keycloak.services.resources.admin;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.jboss.resteasy.spi.BadRequestException;
|
||||
import org.jboss.resteasy.spi.NotFoundException;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailProvider;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelReadOnlyException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleMapperModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
import org.keycloak.representations.idm.ClientMappingsRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.MappingsRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.managers.BruteForceProtector;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Base resource for managing users
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RoleMapperResource {
|
||||
protected static final Logger logger = Logger.getLogger(RoleMapperResource.class);
|
||||
|
||||
protected RealmModel realm;
|
||||
|
||||
private RealmAuth auth;
|
||||
|
||||
private RoleMapperModel roleMapper;
|
||||
|
||||
private AdminEventBuilder adminEvent;
|
||||
|
||||
@Context
|
||||
protected ClientConnection clientConnection;
|
||||
|
||||
@Context
|
||||
protected UriInfo uriInfo;
|
||||
|
||||
@Context
|
||||
protected KeycloakSession session;
|
||||
|
||||
@Context
|
||||
protected HttpHeaders headers;
|
||||
|
||||
@Context
|
||||
protected BruteForceProtector protector;
|
||||
|
||||
public RoleMapperResource(RealmModel realm, RealmAuth auth, RoleMapperModel roleMapper, AdminEventBuilder adminEvent) {
|
||||
this.auth = auth;
|
||||
this.realm = realm;
|
||||
this.adminEvent = adminEvent;
|
||||
this.roleMapper = roleMapper;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get role mappings
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public MappingsRepresentation getRoleMappings() {
|
||||
auth.requireView();
|
||||
|
||||
MappingsRepresentation all = new MappingsRepresentation();
|
||||
Set<RoleModel> realmMappings = roleMapper.getRoleMappings();
|
||||
RealmManager manager = new RealmManager(session);
|
||||
if (realmMappings.size() > 0) {
|
||||
List<RoleRepresentation> realmRep = new ArrayList<RoleRepresentation>();
|
||||
for (RoleModel roleModel : realmMappings) {
|
||||
realmRep.add(ModelToRepresentation.toRepresentation(roleModel));
|
||||
}
|
||||
all.setRealmMappings(realmRep);
|
||||
}
|
||||
|
||||
List<ClientModel> clients = realm.getClients();
|
||||
if (clients.size() > 0) {
|
||||
Map<String, ClientMappingsRepresentation> appMappings = new HashMap<String, ClientMappingsRepresentation>();
|
||||
for (ClientModel client : clients) {
|
||||
Set<RoleModel> roleMappings = roleMapper.getClientRoleMappings(client);
|
||||
if (roleMappings.size() > 0) {
|
||||
ClientMappingsRepresentation mappings = new ClientMappingsRepresentation();
|
||||
mappings.setId(client.getId());
|
||||
mappings.setClient(client.getClientId());
|
||||
List<RoleRepresentation> roles = new ArrayList<RoleRepresentation>();
|
||||
mappings.setMappings(roles);
|
||||
for (RoleModel role : roleMappings) {
|
||||
roles.add(ModelToRepresentation.toRepresentation(role));
|
||||
}
|
||||
appMappings.put(client.getClientId(), mappings);
|
||||
all.setClientMappings(appMappings);
|
||||
}
|
||||
}
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get realm-level role mappings
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Path("realm")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public List<RoleRepresentation> getRealmRoleMappings() {
|
||||
auth.requireView();
|
||||
|
||||
Set<RoleModel> realmMappings = roleMapper.getRealmRoleMappings();
|
||||
List<RoleRepresentation> realmMappingsRep = new ArrayList<RoleRepresentation>();
|
||||
for (RoleModel roleModel : realmMappings) {
|
||||
realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel));
|
||||
}
|
||||
return realmMappingsRep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get effective realm-level role mappings
|
||||
*
|
||||
* This will recurse all composite roles to get the result.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Path("realm/composite")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public List<RoleRepresentation> getCompositeRealmRoleMappings() {
|
||||
auth.requireView();
|
||||
|
||||
Set<RoleModel> roles = realm.getRoles();
|
||||
List<RoleRepresentation> realmMappingsRep = new ArrayList<RoleRepresentation>();
|
||||
for (RoleModel roleModel : roles) {
|
||||
if (roleMapper.hasRole(roleModel)) {
|
||||
realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel));
|
||||
}
|
||||
}
|
||||
return realmMappingsRep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get realm-level roles that can be mapped
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Path("realm/available")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public List<RoleRepresentation> getAvailableRealmRoleMappings() {
|
||||
auth.requireView();
|
||||
|
||||
Set<RoleModel> available = realm.getRoles();
|
||||
return ClientRoleMappingsResource.getAvailableRoles(roleMapper, available);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add realm-level role mappings to the user
|
||||
*
|
||||
* @param roles Roles to add
|
||||
*/
|
||||
@Path("realm")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void addRealmRoleMappings(List<RoleRepresentation> roles) {
|
||||
auth.requireManage();
|
||||
|
||||
logger.debugv("** addRealmRoleMappings: {0}", roles);
|
||||
|
||||
for (RoleRepresentation role : roles) {
|
||||
RoleModel roleModel = realm.getRole(role.getName());
|
||||
if (roleModel == null || !roleModel.getId().equals(role.getId())) {
|
||||
throw new NotFoundException("Role not found");
|
||||
}
|
||||
roleMapper.grantRole(roleModel);
|
||||
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, role.getId()).representation(roles).success();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete realm-level role mappings
|
||||
*
|
||||
* @param roles
|
||||
*/
|
||||
@Path("realm")
|
||||
@DELETE
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void deleteRealmRoleMappings(List<RoleRepresentation> roles) {
|
||||
auth.requireManage();
|
||||
|
||||
logger.debug("deleteRealmRoleMappings");
|
||||
if (roles == null) {
|
||||
Set<RoleModel> roleModels = roleMapper.getRealmRoleMappings();
|
||||
for (RoleModel roleModel : roleModels) {
|
||||
roleMapper.deleteRoleMapping(roleModel);
|
||||
}
|
||||
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(roles).success();
|
||||
} else {
|
||||
for (RoleRepresentation role : roles) {
|
||||
RoleModel roleModel = realm.getRole(role.getName());
|
||||
if (roleModel == null || !roleModel.getId().equals(role.getId())) {
|
||||
throw new NotFoundException("Role not found");
|
||||
}
|
||||
roleMapper.deleteRoleMapping(roleModel);
|
||||
|
||||
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo, role.getId()).representation(roles).success();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Path("clients/{client}")
|
||||
public ClientRoleMappingsResource getUserClientRoleMappingsResource(@PathParam("client") String client) {
|
||||
ClientModel clientModel = realm.getClientById(client);
|
||||
if (clientModel == null) {
|
||||
throw new NotFoundException("Client not found");
|
||||
}
|
||||
|
||||
return new ClientRoleMappingsResource(uriInfo, realm, auth, roleMapper, clientModel, adminEvent);
|
||||
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
|
|||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.jboss.resteasy.spi.BadRequestException;
|
||||
import org.jboss.resteasy.spi.NotFoundException;
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.email.EmailException;
|
||||
|
@ -660,214 +661,18 @@ public class UsersResource {
|
|||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get role mappings for the user
|
||||
*
|
||||
* @param id User id
|
||||
* @return
|
||||
*/
|
||||
@Path("{id}/role-mappings")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public MappingsRepresentation getRoleMappings(@PathParam("id") String id) {
|
||||
auth.requireView();
|
||||
public RoleMapperResource getRoleMappings(@PathParam("id") String id) {
|
||||
|
||||
UserModel user = session.users().getUserById(id, realm);
|
||||
if (user == null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
auth.init(RealmAuth.Resource.USER);
|
||||
|
||||
MappingsRepresentation all = new MappingsRepresentation();
|
||||
Set<RoleModel> realmMappings = user.getRoleMappings();
|
||||
RealmManager manager = new RealmManager(session);
|
||||
if (realmMappings.size() > 0) {
|
||||
List<RoleRepresentation> realmRep = new ArrayList<RoleRepresentation>();
|
||||
for (RoleModel roleModel : realmMappings) {
|
||||
realmRep.add(ModelToRepresentation.toRepresentation(roleModel));
|
||||
}
|
||||
all.setRealmMappings(realmRep);
|
||||
}
|
||||
|
||||
List<ClientModel> clients = realm.getClients();
|
||||
if (clients.size() > 0) {
|
||||
Map<String, ClientMappingsRepresentation> appMappings = new HashMap<String, ClientMappingsRepresentation>();
|
||||
for (ClientModel client : clients) {
|
||||
Set<RoleModel> roleMappings = user.getClientRoleMappings(client);
|
||||
if (roleMappings.size() > 0) {
|
||||
ClientMappingsRepresentation mappings = new ClientMappingsRepresentation();
|
||||
mappings.setId(client.getId());
|
||||
mappings.setClient(client.getClientId());
|
||||
List<RoleRepresentation> roles = new ArrayList<RoleRepresentation>();
|
||||
mappings.setMappings(roles);
|
||||
for (RoleModel role : roleMappings) {
|
||||
roles.add(ModelToRepresentation.toRepresentation(role));
|
||||
}
|
||||
appMappings.put(client.getClientId(), mappings);
|
||||
all.setClientMappings(appMappings);
|
||||
}
|
||||
}
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get realm-level role mappings for the user
|
||||
*
|
||||
* @param id User id
|
||||
* @return
|
||||
*/
|
||||
@Path("{id}/role-mappings/realm")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public List<RoleRepresentation> getRealmRoleMappings(@PathParam("id") String id) {
|
||||
auth.requireView();
|
||||
|
||||
UserModel user = session.users().getUserById(id, realm);
|
||||
if (user == null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
|
||||
Set<RoleModel> realmMappings = user.getRealmRoleMappings();
|
||||
List<RoleRepresentation> realmMappingsRep = new ArrayList<RoleRepresentation>();
|
||||
for (RoleModel roleModel : realmMappings) {
|
||||
realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel));
|
||||
}
|
||||
return realmMappingsRep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get effective realm-level role mappings for the user
|
||||
*
|
||||
* This will recurse all composite roles to get the result.
|
||||
*
|
||||
* @param id User id
|
||||
* @return
|
||||
*/
|
||||
@Path("{id}/role-mappings/realm/composite")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public List<RoleRepresentation> getCompositeRealmRoleMappings(@PathParam("id") String id) {
|
||||
auth.requireView();
|
||||
|
||||
UserModel user = session.users().getUserById(id, realm);
|
||||
if (user == null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
|
||||
Set<RoleModel> roles = realm.getRoles();
|
||||
List<RoleRepresentation> realmMappingsRep = new ArrayList<RoleRepresentation>();
|
||||
for (RoleModel roleModel : roles) {
|
||||
if (user.hasRole(roleModel)) {
|
||||
realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel));
|
||||
}
|
||||
}
|
||||
return realmMappingsRep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get realm-level roles that can be mapped to this user
|
||||
*
|
||||
* @param id User id
|
||||
* @return
|
||||
*/
|
||||
@Path("{id}/role-mappings/realm/available")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public List<RoleRepresentation> getAvailableRealmRoleMappings(@PathParam("id") String id) {
|
||||
auth.requireView();
|
||||
|
||||
UserModel user = session.users().getUserById(id, realm);
|
||||
if (user == null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
|
||||
Set<RoleModel> available = realm.getRoles();
|
||||
return UserClientRoleMappingsResource.getAvailableRoles(user, available);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add realm-level role mappings to the user
|
||||
*
|
||||
* @param id User id
|
||||
* @param roles Roles to add
|
||||
*/
|
||||
@Path("{id}/role-mappings/realm")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void addRealmRoleMappings(@PathParam("id") String id, List<RoleRepresentation> roles) {
|
||||
auth.requireManage();
|
||||
|
||||
logger.debugv("** addRealmRoleMappings: {0}", roles);
|
||||
UserModel user = session.users().getUserById(id, realm);
|
||||
if (user == null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
|
||||
for (RoleRepresentation role : roles) {
|
||||
RoleModel roleModel = realm.getRole(role.getName());
|
||||
if (roleModel == null || !roleModel.getId().equals(role.getId())) {
|
||||
throw new NotFoundException("Role not found");
|
||||
}
|
||||
user.grantRole(roleModel);
|
||||
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, role.getId()).representation(roles).success();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete realm-level role mappings
|
||||
*
|
||||
* @param id User id
|
||||
* @param roles
|
||||
*/
|
||||
@Path("{id}/role-mappings/realm")
|
||||
@DELETE
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void deleteRealmRoleMappings(@PathParam("id") String id, List<RoleRepresentation> roles) {
|
||||
auth.requireManage();
|
||||
|
||||
logger.debug("deleteRealmRoleMappings");
|
||||
UserModel user = session.users().getUserById(id, realm);
|
||||
if (user == null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
|
||||
if (roles == null) {
|
||||
Set<RoleModel> roleModels = user.getRealmRoleMappings();
|
||||
for (RoleModel roleModel : roleModels) {
|
||||
user.deleteRoleMapping(roleModel);
|
||||
}
|
||||
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(roles).success();
|
||||
} else {
|
||||
for (RoleRepresentation role : roles) {
|
||||
RoleModel roleModel = realm.getRole(role.getName());
|
||||
if (roleModel == null || !roleModel.getId().equals(role.getId())) {
|
||||
throw new NotFoundException("Role not found");
|
||||
}
|
||||
user.deleteRoleMapping(roleModel);
|
||||
|
||||
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo, role.getId()).representation(roles).success();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Path("{id}/role-mappings/clients/{client}")
|
||||
public UserClientRoleMappingsResource getUserClientRoleMappingsResource(@PathParam("id") String id, @PathParam("client") String client) {
|
||||
UserModel user = session.users().getUserById(id, realm);
|
||||
if (user == null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
|
||||
ClientModel clientModel = realm.getClientById(client);
|
||||
if (clientModel == null) {
|
||||
throw new NotFoundException("Client not found");
|
||||
}
|
||||
|
||||
return new UserClientRoleMappingsResource(uriInfo, realm, auth, user, clientModel, adminEvent);
|
||||
RoleMapperResource resource = new RoleMapperResource(realm, auth, user, adminEvent);
|
||||
ResteasyProviderFactory.getInstance().injectProperties(resource);
|
||||
return resource;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.testsuite;
|
||||
|
||||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
|
@ -67,6 +68,11 @@ public class DummyUserFederationProvider implements UserFederationProvider {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, GroupModel group) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(RealmModel realm, UserModel local) {
|
||||
return false;
|
||||
|
|
|
@ -161,6 +161,9 @@ public class KeycloakServer {
|
|||
|
||||
if (!System.getProperties().containsKey("keycloak.theme.dir")) {
|
||||
System.setProperty("keycloak.theme.dir", file(dir.getAbsolutePath(), "forms", "common-themes", "src", "main", "resources", "theme").getAbsolutePath());
|
||||
} else {
|
||||
String foo = System.getProperty("keycloak.theme.dir");
|
||||
System.out.println(foo);
|
||||
}
|
||||
|
||||
if (!System.getProperties().containsKey("keycloak.theme.cacheTemplates")) {
|
||||
|
|
Loading…
Reference in a new issue