group token/assertion and tests
This commit is contained in:
parent
fa7a7d35a9
commit
bff334d365
32 changed files with 1248 additions and 174 deletions
|
@ -40,6 +40,8 @@ public class UserRepresentation {
|
||||||
@Deprecated
|
@Deprecated
|
||||||
protected List<SocialLinkRepresentation> socialLinks;
|
protected List<SocialLinkRepresentation> socialLinks;
|
||||||
|
|
||||||
|
protected List<String> groups;
|
||||||
|
|
||||||
public String getSelf() {
|
public String getSelf() {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
@ -216,4 +218,12 @@ public class UserRepresentation {
|
||||||
public void setServiceAccountClientId(String serviceAccountClientId) {
|
public void setServiceAccountClientId(String serviceAccountClientId) {
|
||||||
this.serviceAccountClientId = serviceAccountClientId;
|
this.serviceAccountClientId = serviceAccountClientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getGroups() {
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroups(List<String> groups) {
|
||||||
|
this.groups = groups;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import org.codehaus.jackson.JsonGenerator;
|
||||||
import org.codehaus.jackson.map.ObjectMapper;
|
import org.codehaus.jackson.map.ObjectMapper;
|
||||||
import org.codehaus.jackson.map.SerializationConfig;
|
import org.codehaus.jackson.map.SerializationConfig;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleContainerModel;
|
import org.keycloak.models.RoleContainerModel;
|
||||||
|
@ -15,6 +16,7 @@ import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
@ -294,6 +296,12 @@ public class ExportUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> groups = new LinkedList<>();
|
||||||
|
for (GroupModel group : user.getGroups()) {
|
||||||
|
groups.add(ModelToRepresentation.buildGroupPath(group));
|
||||||
|
}
|
||||||
|
userRep.setGroups(groups);
|
||||||
|
|
||||||
return userRep;
|
return userRep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ module.controller('GroupCreateCtrl', function($scope, $route, realm, parentId, G
|
||||||
console.log('save!!!');
|
console.log('save!!!');
|
||||||
if (parentId == 'realm') {
|
if (parentId == 'realm') {
|
||||||
console.log('realm')
|
console.log('realm')
|
||||||
Groups.save({realm: realm.realm, groupId: parentId}, $scope.group, function(data, headers) {
|
Groups.save({realm: realm.realm}, $scope.group, function(data, headers) {
|
||||||
var l = headers().location;
|
var l = headers().location;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package org.keycloak.admin.client.resource;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
|
||||||
|
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.Produces;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface GroupResource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does not expand hierarchy. Subgroups will not be set.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@NoCache
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public GroupRepresentation toRepresentation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update group
|
||||||
|
*
|
||||||
|
* @param rep
|
||||||
|
*/
|
||||||
|
@PUT
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
public void update(GroupRepresentation rep);
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
public void remove();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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("children")
|
||||||
|
@NoCache
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
public Response subGroup(GroupRepresentation rep);
|
||||||
|
|
||||||
|
|
||||||
|
@Path("role-mappings")
|
||||||
|
public RoleMappingResource roles();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get users
|
||||||
|
* <p/>
|
||||||
|
* Returns a list of users, filtered according to query parameters
|
||||||
|
*
|
||||||
|
* @param firstResult Pagination offset
|
||||||
|
* @param maxResults Pagination size
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@NoCache
|
||||||
|
@Path("/members")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public List<UserRepresentation> members(@QueryParam("first") Integer firstResult,
|
||||||
|
@QueryParam("max") Integer maxResults);
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.keycloak.admin.client.resource;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
|
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface GroupsResource {
|
||||||
|
@GET
|
||||||
|
@NoCache
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public List<GroupRepresentation> groups();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 add(GroupRepresentation rep);
|
||||||
|
|
||||||
|
@Path("{id}")
|
||||||
|
public GroupResource group(@PathParam("id") String id);
|
||||||
|
|
||||||
|
}
|
11
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
Normal file → Executable file
11
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
Normal file → Executable file
|
@ -1,6 +1,8 @@
|
||||||
package org.keycloak.admin.client.resource;
|
package org.keycloak.admin.client.resource;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
|
@ -36,6 +38,15 @@ public interface RealmResource {
|
||||||
@Path("roles")
|
@Path("roles")
|
||||||
RolesResource roles();
|
RolesResource roles();
|
||||||
|
|
||||||
|
@Path("groups")
|
||||||
|
GroupsResource groups();
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("group-by-path/{path: .*}")
|
||||||
|
@NoCache
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public GroupRepresentation getGroupByPath(@PathParam("path") String path);
|
||||||
|
|
||||||
@Path("identity-provider")
|
@Path("identity-provider")
|
||||||
IdentityProvidersResource identityProviders();
|
IdentityProvidersResource identityProviders();
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.admin.client.resource;
|
||||||
|
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||||
|
|
||||||
|
@ -34,6 +35,21 @@ public interface UserResource {
|
||||||
@DELETE
|
@DELETE
|
||||||
public void remove();
|
public void remove();
|
||||||
|
|
||||||
|
@Path("groups")
|
||||||
|
@GET
|
||||||
|
List<GroupRepresentation> groups();
|
||||||
|
|
||||||
|
@Path("groups/{groupId}")
|
||||||
|
@PUT
|
||||||
|
void joinGroup(@PathParam("groupId") String groupId);
|
||||||
|
|
||||||
|
@Path("groups/{groupId}")
|
||||||
|
@DELETE
|
||||||
|
void leaveGroup(@PathParam("groupId") String groupId);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("logout")
|
@Path("logout")
|
||||||
public void logout();
|
public void logout();
|
||||||
|
|
|
@ -330,6 +330,7 @@ public interface RealmModel extends RoleContainerModel {
|
||||||
void setDefaultLocale(String locale);
|
void setDefaultLocale(String locale);
|
||||||
|
|
||||||
GroupModel createGroup(String name);
|
GroupModel createGroup(String name);
|
||||||
|
GroupModel createGroup(String id, String name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move Group to top realm level. Basically just sets group parent to null. You need to call this though
|
* Move Group to top realm level. Basically just sets group parent to null. You need to call this though
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.keycloak.models.utils;
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
import org.bouncycastle.openssl.PEMWriter;
|
import org.bouncycastle.openssl.PEMWriter;
|
||||||
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
|
@ -416,7 +417,7 @@ public final class KeycloakModelUtils {
|
||||||
|
|
||||||
public static List<String> resolveAttribute(GroupModel group, String name) {
|
public static List<String> resolveAttribute(GroupModel group, String name) {
|
||||||
List<String> values = group.getAttribute(name);
|
List<String> values = group.getAttribute(name);
|
||||||
if (!values.isEmpty()) return values;
|
if (values != null && !values.isEmpty()) return values;
|
||||||
if (group.getParentId() == null) return null;
|
if (group.getParentId() == null) return null;
|
||||||
return resolveAttribute(group.getParent(), name);
|
return resolveAttribute(group.getParent(), name);
|
||||||
|
|
||||||
|
@ -434,4 +435,54 @@ public final class KeycloakModelUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static GroupModel findSubGroup(String[] path, int index, GroupModel parent) {
|
||||||
|
for (GroupModel group : parent.getSubGroups()) {
|
||||||
|
if (group.getName().equals(path[index])) {
|
||||||
|
if (path.length == index + 1) {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (index + 1 < path.length) {
|
||||||
|
GroupModel found = findSubGroup(path, index + 1, group);
|
||||||
|
if (found != null) return found;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GroupModel findGroupByPath(RealmModel realm, String path) {
|
||||||
|
if (path == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
path = path.substring(1);
|
||||||
|
}
|
||||||
|
if (path.endsWith("/")) {
|
||||||
|
path = path.substring(0, path.length() - 1);
|
||||||
|
}
|
||||||
|
String[] split = path.split("/");
|
||||||
|
if (split.length == 0) return null;
|
||||||
|
GroupModel found = null;
|
||||||
|
for (GroupModel group : realm.getTopLevelGroups()) {
|
||||||
|
if (group.getName().equals(split[0])) {
|
||||||
|
if (split.length == 1) {
|
||||||
|
found = group;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (split.length > 1) {
|
||||||
|
found = findSubGroup(split, 1, group);
|
||||||
|
if (found != null) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -288,10 +288,16 @@ public class ModelToRepresentation {
|
||||||
if (internal) {
|
if (internal) {
|
||||||
exportAuthenticationFlows(realm, rep);
|
exportAuthenticationFlows(realm, rep);
|
||||||
exportRequiredActions(realm, rep);
|
exportRequiredActions(realm, rep);
|
||||||
|
exportGroups(realm, rep);
|
||||||
}
|
}
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void exportGroups(RealmModel realm, RealmRepresentation rep) {
|
||||||
|
List<GroupRepresentation> groups = toGroupHierarchy(realm, true);
|
||||||
|
rep.setGroups(groups);
|
||||||
|
}
|
||||||
|
|
||||||
public static void exportAuthenticationFlows(RealmModel realm, RealmRepresentation rep) {
|
public static void exportAuthenticationFlows(RealmModel realm, RealmRepresentation rep) {
|
||||||
rep.setAuthenticationFlows(new LinkedList<AuthenticationFlowRepresentation>());
|
rep.setAuthenticationFlows(new LinkedList<AuthenticationFlowRepresentation>());
|
||||||
rep.setAuthenticatorConfig(new LinkedList<AuthenticatorConfigRepresentation>());
|
rep.setAuthenticatorConfig(new LinkedList<AuthenticatorConfigRepresentation>());
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.keycloak.models.BrowserSecurityHeaders;
|
||||||
import org.keycloak.models.ClaimMask;
|
import org.keycloak.models.ClaimMask;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -36,6 +37,7 @@ import org.keycloak.representations.idm.ClaimRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
||||||
|
@ -311,6 +313,11 @@ public class RepresentationToModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rep.getGroups() != null) {
|
||||||
|
importGroups(newRealm, rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// create users and their role mappings and social mappings
|
// create users and their role mappings and social mappings
|
||||||
|
|
||||||
if (rep.getUsers() != null) {
|
if (rep.getUsers() != null) {
|
||||||
|
@ -330,6 +337,59 @@ public class RepresentationToModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void importGroups(RealmModel realm, RealmRepresentation rep) {
|
||||||
|
List<GroupRepresentation> groups = rep.getGroups();
|
||||||
|
if (groups == null) return;
|
||||||
|
|
||||||
|
Map<String, ClientModel> clientMap = realm.getClientNameMap();
|
||||||
|
GroupModel parent = null;
|
||||||
|
for (GroupRepresentation group : groups) {
|
||||||
|
importGroup(realm, clientMap, parent, group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void importGroup(RealmModel realm, Map<String, ClientModel> clientMap, GroupModel parent, GroupRepresentation group) {
|
||||||
|
GroupModel newGroup = realm.createGroup(group.getId(), group.getName());
|
||||||
|
if (group.getAttributes() != null) {
|
||||||
|
for (Map.Entry<String, List<String>> attr : group.getAttributes().entrySet()) {
|
||||||
|
newGroup.setAttribute(attr.getKey(), attr.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
realm.moveGroup(newGroup, parent);
|
||||||
|
|
||||||
|
if (group.getRealmRoles() != null) {
|
||||||
|
for (String roleString : group.getRealmRoles()) {
|
||||||
|
RoleModel role = realm.getRole(roleString.trim());
|
||||||
|
if (role == null) {
|
||||||
|
role = realm.addRole(roleString.trim());
|
||||||
|
}
|
||||||
|
newGroup.grantRole(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (group.getClientRoles() != null) {
|
||||||
|
for (Map.Entry<String, List<String>> entry : group.getClientRoles().entrySet()) {
|
||||||
|
ClientModel client = clientMap.get(entry.getKey());
|
||||||
|
if (client == null) {
|
||||||
|
throw new RuntimeException("Unable to find client role mappings for client: " + entry.getKey());
|
||||||
|
}
|
||||||
|
List<String> roleNames = entry.getValue();
|
||||||
|
for (String roleName : roleNames) {
|
||||||
|
RoleModel role = client.getRole(roleName.trim());
|
||||||
|
if (role == null) {
|
||||||
|
role = client.addRole(roleName.trim());
|
||||||
|
}
|
||||||
|
newGroup.grantRole(role);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (group.getSubGroups() != null) {
|
||||||
|
for (GroupRepresentation subGroup : group.getSubGroups()) {
|
||||||
|
importGroup(realm, clientMap, newGroup, subGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void importAuthenticationFlows(RealmModel newRealm, RealmRepresentation rep) {
|
public static void importAuthenticationFlows(RealmModel newRealm, RealmRepresentation rep) {
|
||||||
if (rep.getAuthenticationFlows() == null) {
|
if (rep.getAuthenticationFlows() == null) {
|
||||||
// assume this is an old version being imported
|
// assume this is an old version being imported
|
||||||
|
@ -999,6 +1059,16 @@ public class RepresentationToModel {
|
||||||
}
|
}
|
||||||
user.setServiceAccountClientLink(client.getId());;
|
user.setServiceAccountClientLink(client.getId());;
|
||||||
}
|
}
|
||||||
|
if (userRep.getGroups() != null) {
|
||||||
|
for (String path : userRep.getGroups()) {
|
||||||
|
GroupModel group = KeycloakModelUtils.findGroupByPath(newRealm, path);
|
||||||
|
if (group == null) {
|
||||||
|
throw new RuntimeException("Unable to find group specified by path: " + path);
|
||||||
|
|
||||||
|
}
|
||||||
|
user.joinGroup(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1833,6 +1833,11 @@ public class RealmAdapter implements RealmModel {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupModel createGroup(String id, String name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTopLevelGroup(GroupModel subGroup) {
|
public void addTopLevelGroup(GroupModel subGroup) {
|
||||||
|
|
||||||
|
|
|
@ -1307,6 +1307,12 @@ public class RealmAdapter implements RealmModel {
|
||||||
return updated.createGroup(name);
|
return updated.createGroup(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupModel createGroup(String id, String name) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
return updated.createGroup(id, name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTopLevelGroup(GroupModel subGroup) {
|
public void addTopLevelGroup(GroupModel subGroup) {
|
||||||
getDelegateForUpdate();
|
getDelegateForUpdate();
|
||||||
|
|
|
@ -321,7 +321,7 @@ public class UserAdapter implements UserModel {
|
||||||
public Set<GroupModel> getGroups() {
|
public Set<GroupModel> getGroups() {
|
||||||
if (updated != null) return updated.getGroups();
|
if (updated != null) return updated.getGroups();
|
||||||
Set<GroupModel> groups = new HashSet<GroupModel>();
|
Set<GroupModel> groups = new HashSet<GroupModel>();
|
||||||
for (String id : cached.getRoleMappings()) {
|
for (String id : cached.getGroups()) {
|
||||||
GroupModel groupModel = keycloakSession.realms().getGroupById(id, realm);
|
GroupModel groupModel = keycloakSession.realms().getGroupById(id, realm);
|
||||||
if (groupModel == null) {
|
if (groupModel == null) {
|
||||||
// chance that role was removed, so just delete to persistence and get user invalidated
|
// chance that role was removed, so just delete to persistence and get user invalidated
|
||||||
|
|
|
@ -99,19 +99,20 @@ public class JpaRealmProvider implements RealmProvider {
|
||||||
|
|
||||||
RealmAdapter adapter = new RealmAdapter(session, em, realm);
|
RealmAdapter adapter = new RealmAdapter(session, em, realm);
|
||||||
session.users().preRemove(adapter);
|
session.users().preRemove(adapter);
|
||||||
for (ClientEntity a : new LinkedList<>(realm.getClients())) {
|
|
||||||
adapter.removeClient(a.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
int num = em.createNamedQuery("deleteGroupRoleMappingsByRealm")
|
int num = em.createNamedQuery("deleteGroupRoleMappingsByRealm")
|
||||||
.setParameter("realm", realm).executeUpdate();
|
.setParameter("realm", realm).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteGroupAttributesByRealm")
|
num = em.createNamedQuery("deleteGroupAttributesByRealm")
|
||||||
.setParameter("realm", realm).executeUpdate();
|
.setParameter("realm", realm).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteGroupsByRealm")
|
num = em.createNamedQuery("deleteGroupsByRealm")
|
||||||
.setParameter("realm", realm).executeUpdate();
|
.setParameter("realm", realm).executeUpdate();
|
||||||
|
for (ClientEntity a : new LinkedList<>(realm.getClients())) {
|
||||||
|
adapter.removeClient(a.getId());
|
||||||
|
}
|
||||||
|
|
||||||
em.remove(realm);
|
em.remove(realm);
|
||||||
|
|
||||||
|
em.flush();
|
||||||
|
em.clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -174,10 +174,10 @@ public class JpaUserProvider implements UserProvider {
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteUserAttributesByRealm")
|
num = em.createNamedQuery("deleteUserAttributesByRealm")
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteUsersByRealm")
|
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
|
||||||
num = em.createNamedQuery("deleteUserGroupMembershipByRealm")
|
num = em.createNamedQuery("deleteUserGroupMembershipByRealm")
|
||||||
.setParameter("realmId", realm.getId()).executeUpdate();
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
|
num = em.createNamedQuery("deleteUsersByRealm")
|
||||||
|
.setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1965,7 +1965,7 @@ public class RealmAdapter implements RealmModel {
|
||||||
@Override
|
@Override
|
||||||
public List<GroupModel> getGroups() {
|
public List<GroupModel> getGroups() {
|
||||||
List<GroupModel> list = new LinkedList<>();
|
List<GroupModel> list = new LinkedList<>();
|
||||||
Collection<GroupEntity> groups = realm.getGroups();
|
Collection<GroupEntity> groups = em.createNamedQuery("getAllGroupsByRealm").setParameter("realm", realm).getResultList();
|
||||||
if (groups == null) return list;
|
if (groups == null) return list;
|
||||||
for (GroupEntity entity : groups) {
|
for (GroupEntity entity : groups) {
|
||||||
list.add(new GroupAdapter(this, em, entity));
|
list.add(new GroupAdapter(this, em, entity));
|
||||||
|
@ -2002,7 +2002,6 @@ public class RealmAdapter implements RealmModel {
|
||||||
|
|
||||||
session.users().preRemove(this, group);
|
session.users().preRemove(this, group);
|
||||||
moveGroup(group, null);
|
moveGroup(group, null);
|
||||||
realm.getGroups().remove(groupEntity);
|
|
||||||
em.createNamedQuery("deleteGroupAttributesByGroup").setParameter("group", groupEntity).executeUpdate();
|
em.createNamedQuery("deleteGroupAttributesByGroup").setParameter("group", groupEntity).executeUpdate();
|
||||||
em.createNamedQuery("deleteGroupRoleMappingsByGroup").setParameter("group", groupEntity).executeUpdate();
|
em.createNamedQuery("deleteGroupRoleMappingsByGroup").setParameter("group", groupEntity).executeUpdate();
|
||||||
em.remove(groupEntity);
|
em.remove(groupEntity);
|
||||||
|
@ -2013,8 +2012,15 @@ public class RealmAdapter implements RealmModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GroupModel createGroup(String name) {
|
public GroupModel createGroup(String name) {
|
||||||
|
String id = KeycloakModelUtils.generateId();
|
||||||
|
return createGroup(id, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupModel createGroup(String id, String name) {
|
||||||
|
if (id == null) id = KeycloakModelUtils.generateId();
|
||||||
GroupEntity groupEntity = new GroupEntity();
|
GroupEntity groupEntity = new GroupEntity();
|
||||||
groupEntity.setId(KeycloakModelUtils.generateId());
|
groupEntity.setId(id);
|
||||||
groupEntity.setName(name);
|
groupEntity.setName(name);
|
||||||
groupEntity.setRealm(realm);
|
groupEntity.setRealm(realm);
|
||||||
em.persist(groupEntity);
|
em.persist(groupEntity);
|
||||||
|
|
|
@ -133,9 +133,6 @@ public class RealmEntity {
|
||||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||||
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
|
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
|
||||||
Collection<GroupEntity> groups = new ArrayList<GroupEntity>();
|
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
@MapKeyColumn(name="NAME")
|
@MapKeyColumn(name="NAME")
|
||||||
@Column(name="VALUE")
|
@Column(name="VALUE")
|
||||||
|
@ -722,20 +719,5 @@ public class RealmEntity {
|
||||||
this.clientAuthenticationFlow = 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -611,8 +611,15 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GroupModel createGroup(String name) {
|
public GroupModel createGroup(String name) {
|
||||||
|
String id = KeycloakModelUtils.generateId();
|
||||||
|
return createGroup(id, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupModel createGroup(String id, String name) {
|
||||||
|
if (id == null) id = KeycloakModelUtils.generateId();
|
||||||
MongoGroupEntity group = new MongoGroupEntity();
|
MongoGroupEntity group = new MongoGroupEntity();
|
||||||
group.setId(KeycloakModelUtils.generateId());
|
group.setId(id);
|
||||||
group.setName(name);
|
group.setName(name);
|
||||||
group.setRealmId(getId());
|
group.setRealmId(getId());
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
package org.keycloak.protocol.saml.mappers;
|
||||||
|
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
||||||
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
|
import org.keycloak.protocol.ProtocolMapper;
|
||||||
|
import org.keycloak.protocol.saml.SamlProtocol;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
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 GroupMembershipMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper {
|
||||||
|
public static final String PROVIDER_ID = "saml-group-membership-mapper";
|
||||||
|
public static final String SINGLE_GROUP_ATTRIBUTE = "single";
|
||||||
|
|
||||||
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
ProviderConfigProperty property;
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAME);
|
||||||
|
property.setLabel("Group attribute name");
|
||||||
|
property.setDefaultValue("member");
|
||||||
|
property.setHelpText("Name of the SAML attribute you want to put your groups into. i.e. 'member', 'memberOf'.");
|
||||||
|
configProperties.add(property);
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(AttributeStatementHelper.FRIENDLY_NAME);
|
||||||
|
property.setLabel(AttributeStatementHelper.FRIENDLY_NAME_LABEL);
|
||||||
|
property.setHelpText(AttributeStatementHelper.FRIENDLY_NAME_HELP_TEXT);
|
||||||
|
configProperties.add(property);
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT);
|
||||||
|
property.setLabel("SAML Attribute NameFormat");
|
||||||
|
property.setHelpText("SAML Attribute NameFormat. Can be basic, URI reference, or unspecified.");
|
||||||
|
List<String> types = new ArrayList(3);
|
||||||
|
types.add(AttributeStatementHelper.BASIC);
|
||||||
|
types.add(AttributeStatementHelper.URI_REFERENCE);
|
||||||
|
types.add(AttributeStatementHelper.UNSPECIFIED);
|
||||||
|
property.setType(ProviderConfigProperty.LIST_TYPE);
|
||||||
|
property.setDefaultValue(types);
|
||||||
|
configProperties.add(property);
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(SINGLE_GROUP_ATTRIBUTE);
|
||||||
|
property.setLabel("Single Group Attribute");
|
||||||
|
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||||
|
property.setDefaultValue("true");
|
||||||
|
property.setHelpText("If true, all groups will be stored under one attribute with multiple attribute values.");
|
||||||
|
configProperties.add(property);
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName("full.path");
|
||||||
|
property.setLabel("Full group path");
|
||||||
|
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||||
|
property.setDefaultValue("true");
|
||||||
|
property.setHelpText("Include full path to group i.e. /top/level1/level2, false will just specify the group name");
|
||||||
|
configProperties.add(property);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayCategory() {
|
||||||
|
return "Group Mapper";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Group list";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Group names are stored in an attribute value. There is either one attribute with multiple attribute values, or an attribute per group name depending on how you configure it. You can also specify the attribute name i.e. 'member' or 'memberOf' being examples.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean useFullPath(ProtocolMapperModel mappingModel) {
|
||||||
|
return "true".equals(mappingModel.getConfig().get("full.path"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
|
String single = mappingModel.getConfig().get(SINGLE_GROUP_ATTRIBUTE);
|
||||||
|
boolean singleAttribute = Boolean.parseBoolean(single);
|
||||||
|
|
||||||
|
boolean fullPath = useFullPath(mappingModel);
|
||||||
|
AttributeType singleAttributeType = null;
|
||||||
|
for (GroupModel group : userSession.getUser().getGroups()) {
|
||||||
|
String groupName;
|
||||||
|
if (fullPath) {
|
||||||
|
groupName = ModelToRepresentation.buildGroupPath(group);
|
||||||
|
} else {
|
||||||
|
groupName = group.getName();
|
||||||
|
}
|
||||||
|
AttributeType attributeType = null;
|
||||||
|
if (singleAttribute) {
|
||||||
|
if (singleAttributeType == null) {
|
||||||
|
singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
|
||||||
|
attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType));
|
||||||
|
}
|
||||||
|
attributeType = singleAttributeType;
|
||||||
|
} else {
|
||||||
|
attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
|
||||||
|
attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
|
||||||
|
}
|
||||||
|
attributeType.addAttributeValue(groupName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProtocolMapperModel create(String name, String samlAttributeName, String nameFormat, String friendlyName, boolean singleAttribute) {
|
||||||
|
ProtocolMapperModel mapper = new ProtocolMapperModel();
|
||||||
|
mapper.setName(name);
|
||||||
|
mapper.setProtocolMapper(PROVIDER_ID);
|
||||||
|
mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
|
||||||
|
mapper.setConsentRequired(false);
|
||||||
|
Map<String, String> config = new HashMap<String, String>();
|
||||||
|
config.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, samlAttributeName);
|
||||||
|
if (friendlyName != null) {
|
||||||
|
config.put(AttributeStatementHelper.FRIENDLY_NAME, friendlyName);
|
||||||
|
}
|
||||||
|
if (nameFormat != null) {
|
||||||
|
config.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT, nameFormat);
|
||||||
|
}
|
||||||
|
config.put(SINGLE_GROUP_ATTRIBUTE, Boolean.toString(singleAttribute));
|
||||||
|
mapper.setConfig(config);
|
||||||
|
|
||||||
|
return mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.keycloak.protocol.saml.mappers;
|
||||||
|
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface SAMLGroupNameMapper {
|
||||||
|
public String mapName(ProtocolMapperModel model, GroupModel group);
|
||||||
|
}
|
|
@ -5,5 +5,6 @@ org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper
|
||||||
org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper
|
org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper
|
||||||
org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper
|
org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper
|
||||||
org.keycloak.protocol.saml.mappers.UserSessionNoteStatementMapper
|
org.keycloak.protocol.saml.mappers.UserSessionNoteStatementMapper
|
||||||
|
org.keycloak.protocol.saml.mappers.GroupMembershipMapper
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
package org.keycloak.protocol.oidc.mappers;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
|
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.representations.IDToken;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps user group membership
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class GroupMembershipMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper {
|
||||||
|
|
||||||
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
ProviderConfigProperty property;
|
||||||
|
ProviderConfigProperty property1;
|
||||||
|
property1 = new ProviderConfigProperty();
|
||||||
|
property1.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
|
||||||
|
property1.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL);
|
||||||
|
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
property1.setDefaultValue("groups");
|
||||||
|
property1.setHelpText(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_TOOLTIP);
|
||||||
|
configProperties.add(property1);
|
||||||
|
property1 = new ProviderConfigProperty();
|
||||||
|
property1.setName("full.path");
|
||||||
|
property1.setLabel("Full group path");
|
||||||
|
property1.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||||
|
property1.setDefaultValue("true");
|
||||||
|
property1.setHelpText("Include full path to group i.e. /top/level1/level2, false will just specify the group name");
|
||||||
|
configProperties.add(property1);
|
||||||
|
|
||||||
|
property1 = new ProviderConfigProperty();
|
||||||
|
property1.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
|
||||||
|
property1.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
|
||||||
|
property1.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||||
|
property1.setDefaultValue("true");
|
||||||
|
property1.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
|
||||||
|
configProperties.add(property1);
|
||||||
|
property1 = new ProviderConfigProperty();
|
||||||
|
property1.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
|
||||||
|
property1.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
|
||||||
|
property1.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
||||||
|
property1.setDefaultValue("true");
|
||||||
|
property1.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
|
||||||
|
configProperties.add(property1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "oidc-group-membership-mapper";
|
||||||
|
|
||||||
|
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Group Membership";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayCategory() {
|
||||||
|
return TOKEN_MAPPER_CATEGORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Map user group membership";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean useFullPath(ProtocolMapperModel mappingModel) {
|
||||||
|
return "true".equals(mappingModel.getConfig().get("full.path"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session,
|
||||||
|
UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
|
if (!OIDCAttributeMapperHelper.includeInAccessToken(mappingModel)) return token;
|
||||||
|
buildMembership(token, mappingModel, userSession);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void buildMembership(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession) {
|
||||||
|
List<String> membership = new LinkedList<>();
|
||||||
|
boolean fullPath = useFullPath(mappingModel);
|
||||||
|
for (GroupModel group : userSession.getUser().getGroups()) {
|
||||||
|
if (fullPath) {
|
||||||
|
membership.add(ModelToRepresentation.buildGroupPath(group));
|
||||||
|
} else {
|
||||||
|
membership.add(group.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String protocolClaim = mappingModel.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
|
||||||
|
|
||||||
|
token.getOtherClaims().put(protocolClaim, membership);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
|
if (!OIDCAttributeMapperHelper.includeInIDToken(mappingModel)) return token;
|
||||||
|
buildMembership(token, mappingModel, userSession);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProtocolMapperModel create(String name,
|
||||||
|
String tokenClaimName,
|
||||||
|
boolean consentRequired, String consentText,
|
||||||
|
boolean accessToken, boolean idToken) {
|
||||||
|
ProtocolMapperModel mapper = new ProtocolMapperModel();
|
||||||
|
mapper.setName(name);
|
||||||
|
mapper.setProtocolMapper(PROVIDER_ID);
|
||||||
|
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
|
mapper.setConsentRequired(consentRequired);
|
||||||
|
mapper.setConsentText(consentText);
|
||||||
|
Map<String, String> config = new HashMap<String, String>();
|
||||||
|
config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, tokenClaimName);
|
||||||
|
if (accessToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
|
||||||
|
if (idToken) config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
|
||||||
|
mapper.setConfig(config);
|
||||||
|
|
||||||
|
return mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -45,123 +45,47 @@ public class GroupResource {
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
private final RealmAuth auth;
|
private final RealmAuth auth;
|
||||||
private final AdminEventBuilder adminEvent;
|
private final AdminEventBuilder adminEvent;
|
||||||
|
private final GroupModel group;
|
||||||
|
|
||||||
public GroupResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) {
|
public GroupResource(RealmModel realm, GroupModel group, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) {
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
this.adminEvent = adminEvent;
|
this.adminEvent = adminEvent;
|
||||||
|
this.group = group;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Context private UriInfo uriInfo;
|
@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
|
* @return
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@NoCache
|
@NoCache
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public List<GroupRepresentation> getGroups() {
|
public GroupRepresentation getGroup() {
|
||||||
this.auth.requireView();
|
this.auth.requireView();
|
||||||
return ModelToRepresentation.toGroupHierarchy(realm, false);
|
return ModelToRepresentation.toGroupHierarchy(group, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* Update group, ignores subgroups.
|
||||||
* 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
|
* @param rep
|
||||||
*/
|
*/
|
||||||
@PUT
|
@PUT
|
||||||
@Path("{id}")
|
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public void updateGroup(@PathParam("id") String id, GroupRepresentation rep) {
|
public void updateGroup(GroupRepresentation rep) {
|
||||||
GroupModel model = realm.getGroupById(id);
|
updateGroup(rep, group);
|
||||||
if (model == null) {
|
|
||||||
throw new NotFoundException("Could not find group by id");
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGroup(rep, model);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
@Path("{id}")
|
public void deleteGroup() {
|
||||||
public void deleteGroup(@PathParam("id") String id) {
|
realm.removeGroup(group);
|
||||||
GroupModel model = realm.getGroupById(id);
|
|
||||||
if (model == null) {
|
|
||||||
throw new NotFoundException("Could not find group by id");
|
|
||||||
}
|
|
||||||
realm.removeGroup(model);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -172,16 +96,12 @@ public class GroupResource {
|
||||||
* @param rep
|
* @param rep
|
||||||
*/
|
*/
|
||||||
@POST
|
@POST
|
||||||
@Path("{id}/children")
|
@Path("children")
|
||||||
@NoCache
|
@NoCache
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public Response addGroup(@PathParam("id") String parentId, GroupRepresentation rep) {
|
public Response addChild(GroupRepresentation rep) {
|
||||||
GroupModel parentModel = realm.getGroupById(parentId);
|
|
||||||
Response.ResponseBuilder builder = Response.status(204);
|
Response.ResponseBuilder builder = Response.status(204);
|
||||||
if (parentModel == null) {
|
|
||||||
throw new NotFoundException("Could not find parent by id");
|
|
||||||
}
|
|
||||||
GroupModel child = null;
|
GroupModel child = null;
|
||||||
if (rep.getId() != null) {
|
if (rep.getId() != null) {
|
||||||
child = realm.getGroupById(rep.getId());
|
child = realm.getGroupById(rep.getId());
|
||||||
|
@ -197,39 +117,12 @@ public class GroupResource {
|
||||||
builder.status(201).location(uri);
|
builder.status(201).location(uri);
|
||||||
|
|
||||||
}
|
}
|
||||||
realm.moveGroup(child, parentModel);
|
realm.moveGroup(child, group);
|
||||||
GroupRepresentation childRep = ModelToRepresentation.toRepresentation(child, true);
|
GroupRepresentation childRep = ModelToRepresentation.toGroupHierarchy(child, true);
|
||||||
return builder.type(MediaType.APPLICATION_JSON_TYPE).entity(childRep).build();
|
return builder.type(MediaType.APPLICATION_JSON_TYPE).entity(childRep).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static void updateGroup(GroupRepresentation rep, GroupModel model) {
|
||||||
* 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.getName() != null) model.setName(rep.getName());
|
||||||
|
|
||||||
if (rep.getAttributes() != null) {
|
if (rep.getAttributes() != null) {
|
||||||
|
@ -245,13 +138,8 @@ public class GroupResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("{id}/role-mappings")
|
@Path("role-mappings")
|
||||||
public RoleMapperResource getRoleMappings(@PathParam("id") String id) {
|
public RoleMapperResource getRoleMappings() {
|
||||||
|
|
||||||
GroupModel group = session.realms().getGroupById(id, realm);
|
|
||||||
if (group == null) {
|
|
||||||
throw new NotFoundException("Group not found");
|
|
||||||
}
|
|
||||||
auth.init(RealmAuth.Resource.USER);
|
auth.init(RealmAuth.Resource.USER);
|
||||||
|
|
||||||
RoleMapperResource resource = new RoleMapperResource(realm, auth, group, adminEvent);
|
RoleMapperResource resource = new RoleMapperResource(realm, auth, group, adminEvent);
|
||||||
|
@ -271,18 +159,11 @@ public class GroupResource {
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@NoCache
|
@NoCache
|
||||||
@Path("{id}/members")
|
@Path("members")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public List<UserRepresentation> getMembers(@PathParam("id") String id,
|
public List<UserRepresentation> getMembers(@QueryParam("first") Integer firstResult,
|
||||||
@QueryParam("first") Integer firstResult,
|
|
||||||
@QueryParam("max") Integer maxResults) {
|
@QueryParam("max") Integer maxResults) {
|
||||||
auth.requireView();
|
auth.requireView();
|
||||||
|
|
||||||
GroupModel group = session.realms().getGroupById(id, realm);
|
|
||||||
if (group == null) {
|
|
||||||
throw new NotFoundException("Group not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
firstResult = firstResult != null ? firstResult : -1;
|
firstResult = firstResult != null ? firstResult : -1;
|
||||||
maxResults = maxResults != null ? maxResults : -1;
|
maxResults = maxResults != null ? maxResults : -1;
|
||||||
|
|
||||||
|
|
120
services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java
Executable file
120
services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java
Executable file
|
@ -0,0 +1,120 @@
|
||||||
|
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 org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
|
||||||
|
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.core.Context;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Bill Burke
|
||||||
|
*/
|
||||||
|
public class GroupsResource {
|
||||||
|
|
||||||
|
private static Logger logger = Logger.getLogger(GroupsResource.class);
|
||||||
|
|
||||||
|
private final RealmModel realm;
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final RealmAuth auth;
|
||||||
|
private final AdminEventBuilder adminEvent;
|
||||||
|
|
||||||
|
public GroupsResource(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 GroupsResource(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does not expand hierarchy. Subgroups will not be set.
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Path("{id}")
|
||||||
|
public GroupResource getGroupById(@PathParam("id") String id) {
|
||||||
|
GroupModel group = realm.getGroupById(id);
|
||||||
|
if (group == null) {
|
||||||
|
throw new NotFoundException("Could not find group by id");
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupResource resource = new GroupResource(realm, group, session, this.auth, adminEvent);
|
||||||
|
ResteasyProviderFactory.getInstance().injectProperties(resource);
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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());
|
||||||
|
GroupResource.updateGroup(rep, child);
|
||||||
|
URI uri = uriInfo.getAbsolutePathBuilder()
|
||||||
|
.path(child.getId()).build();
|
||||||
|
builder.status(201).location(uri);
|
||||||
|
}
|
||||||
|
realm.moveGroup(child, null);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.exportimport.ClientDescriptionConverter;
|
import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||||
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
|
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -23,12 +24,14 @@ import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.cache.CacheRealmProvider;
|
import org.keycloak.models.cache.CacheRealmProvider;
|
||||||
import org.keycloak.models.cache.CacheUserProvider;
|
import org.keycloak.models.cache.CacheUserProvider;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.models.utils.RepresentationToModel;
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
@ -620,10 +623,26 @@ public class RealmAdminResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("groups")
|
@Path("groups")
|
||||||
public GroupResource getGroups() {
|
public GroupsResource getGroups() {
|
||||||
GroupResource resource = new GroupResource(realm, session, this.auth, adminEvent);
|
GroupsResource resource = new GroupsResource(realm, session, this.auth, adminEvent);
|
||||||
ResteasyProviderFactory.getInstance().injectProperties(resource);
|
ResteasyProviderFactory.getInstance().injectProperties(resource);
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("group-by-path/{path: .*}")
|
||||||
|
@NoCache
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public GroupRepresentation getGroupByPath(@PathParam("path") String path) {
|
||||||
|
this.auth.requireView();
|
||||||
|
GroupModel found = KeycloakModelUtils.findGroupByPath(realm, path);
|
||||||
|
if (found == null) {
|
||||||
|
throw new NotFoundException("Group path does not exist");
|
||||||
|
|
||||||
|
}
|
||||||
|
return ModelToRepresentation.toGroupHierarchy(found, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,6 @@ org.keycloak.protocol.oidc.mappers.HardcodedClaim
|
||||||
org.keycloak.protocol.oidc.mappers.HardcodedRole
|
org.keycloak.protocol.oidc.mappers.HardcodedRole
|
||||||
org.keycloak.protocol.oidc.mappers.RoleNameMapper
|
org.keycloak.protocol.oidc.mappers.RoleNameMapper
|
||||||
org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper
|
org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper
|
||||||
|
org.keycloak.protocol.oidc.mappers.GroupMembershipMapper
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.testsuite.keycloaksaml;
|
package org.keycloak.testsuite.keycloaksaml;
|
||||||
|
|
||||||
|
import com.mongodb.util.Hash;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
|
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
@ -22,10 +23,12 @@ import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
|
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
|
||||||
|
import org.keycloak.protocol.saml.mappers.GroupMembershipMapper;
|
||||||
import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper;
|
import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper;
|
||||||
import org.keycloak.protocol.saml.mappers.HardcodedRole;
|
import org.keycloak.protocol.saml.mappers.HardcodedRole;
|
||||||
import org.keycloak.protocol.saml.mappers.RoleListMapper;
|
import org.keycloak.protocol.saml.mappers.RoleListMapper;
|
||||||
import org.keycloak.protocol.saml.mappers.RoleNameMapper;
|
import org.keycloak.protocol.saml.mappers.RoleNameMapper;
|
||||||
|
import org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
@ -53,8 +56,10 @@ import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
@ -202,6 +207,40 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAttributes() throws Exception {
|
public void testAttributes() throws Exception {
|
||||||
|
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
ClientModel app = appRealm.getClientByClientId(APP_SERVER_BASE_URL + "/employee2/");
|
||||||
|
app.addProtocolMapper(GroupMembershipMapper.create("groups", "group", null, null, true));
|
||||||
|
app.addProtocolMapper(UserAttributeStatementMapper.createAttributeMapper("topAttribute", "topAttribute", "topAttribute", "Basic", null, false, null));
|
||||||
|
app.addProtocolMapper(UserAttributeStatementMapper.createAttributeMapper("level2Attribute", "level2Attribute", "level2Attribute", "Basic", null, false, null));
|
||||||
|
}
|
||||||
|
}, "demo");
|
||||||
|
{
|
||||||
|
SendUsernameServlet.sentPrincipal = null;
|
||||||
|
SendUsernameServlet.checkRoles = null;
|
||||||
|
driver.navigate().to(APP_SERVER_BASE_URL + "/employee2/");
|
||||||
|
Assert.assertTrue(driver.getCurrentUrl().startsWith(AUTH_SERVER_URL + "/realms/demo/protocol/saml"));
|
||||||
|
List<String> requiredRoles = new LinkedList<>();
|
||||||
|
requiredRoles.add("manager");
|
||||||
|
requiredRoles.add("user");
|
||||||
|
SendUsernameServlet.checkRoles = requiredRoles;
|
||||||
|
loginPage.login("level2GroupUser", "password");
|
||||||
|
assertEquals(driver.getCurrentUrl(), APP_SERVER_BASE_URL + "/employee2/");
|
||||||
|
SendUsernameServlet.checkRoles = null;
|
||||||
|
SamlPrincipal principal = (SamlPrincipal) SendUsernameServlet.sentPrincipal;
|
||||||
|
Assert.assertNotNull(principal);
|
||||||
|
assertEquals("level2@redhat.com", principal.getAttribute(X500SAMLProfileConstants.EMAIL.get()));
|
||||||
|
assertEquals("true", principal.getAttribute("topAttribute"));
|
||||||
|
assertEquals("true", principal.getAttribute("level2Attribute"));
|
||||||
|
List<String> groups = principal.getAttributes("group");
|
||||||
|
Assert.assertNotNull(groups);
|
||||||
|
Set<String> groupSet = new HashSet<>();
|
||||||
|
assertEquals("level2@redhat.com", principal.getFriendlyAttribute("email"));
|
||||||
|
driver.navigate().to(APP_SERVER_BASE_URL + "/employee2/?GLO=true");
|
||||||
|
checkLoggedOut(APP_SERVER_BASE_URL + "/employee2/");
|
||||||
|
|
||||||
|
}
|
||||||
{
|
{
|
||||||
SendUsernameServlet.sentPrincipal = null;
|
SendUsernameServlet.sentPrincipal = null;
|
||||||
SendUsernameServlet.checkRoles = null;
|
SendUsernameServlet.checkRoles = null;
|
||||||
|
|
252
testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
Executable file
252
testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
Executable file
|
@ -0,0 +1,252 @@
|
||||||
|
package org.keycloak.testsuite.model;
|
||||||
|
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.admin.client.Keycloak;
|
||||||
|
import org.keycloak.admin.client.resource.GroupResource;
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper;
|
||||||
|
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
|
||||||
|
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
|
||||||
|
import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper;
|
||||||
|
import org.keycloak.protocol.saml.mappers.HardcodedRole;
|
||||||
|
import org.keycloak.protocol.saml.mappers.RoleListMapper;
|
||||||
|
import org.keycloak.protocol.saml.mappers.RoleNameMapper;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.representations.RefreshToken;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.services.managers.ClientManager;
|
||||||
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class GroupTest {
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
ClientModel app = new ClientManager(manager).createClient(appRealm, "resource-owner");
|
||||||
|
app.setSecret("secret");
|
||||||
|
|
||||||
|
UserModel user = session.users().addUser(appRealm, "direct-login");
|
||||||
|
user.setEmail("direct-login@localhost");
|
||||||
|
user.setEnabled(true);
|
||||||
|
|
||||||
|
|
||||||
|
session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
|
||||||
|
keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
protected static Keycloak keycloak;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public WebRule webRule = new WebRule(this);
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected WebDriver driver;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected OAuthClient oauth;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createAndTestGroups() throws Exception {
|
||||||
|
RealmResource realm = keycloak.realms().realm("test");
|
||||||
|
{
|
||||||
|
RoleRepresentation groupRole = new RoleRepresentation();
|
||||||
|
groupRole.setName("topRole");
|
||||||
|
realm.roles().create(groupRole);
|
||||||
|
}
|
||||||
|
RoleRepresentation topRole = realm.roles().get("topRole").toRepresentation();
|
||||||
|
{
|
||||||
|
RoleRepresentation groupRole = new RoleRepresentation();
|
||||||
|
groupRole.setName("level2Role");
|
||||||
|
realm.roles().create(groupRole);
|
||||||
|
}
|
||||||
|
RoleRepresentation level2Role = realm.roles().get("level2Role").toRepresentation();
|
||||||
|
{
|
||||||
|
RoleRepresentation groupRole = new RoleRepresentation();
|
||||||
|
groupRole.setName("level3Role");
|
||||||
|
realm.roles().create(groupRole);
|
||||||
|
}
|
||||||
|
RoleRepresentation level3Role = realm.roles().get("level3Role").toRepresentation();
|
||||||
|
|
||||||
|
|
||||||
|
GroupRepresentation topGroup = new GroupRepresentation();
|
||||||
|
topGroup.setName("top");
|
||||||
|
Response response = realm.groups().add(topGroup);
|
||||||
|
response.close();
|
||||||
|
topGroup = realm.getGroupByPath("/top");
|
||||||
|
Assert.assertNotNull(topGroup);
|
||||||
|
List<RoleRepresentation> roles = new LinkedList<>();
|
||||||
|
roles.add(topRole);
|
||||||
|
realm.groups().group(topGroup.getId()).roles().realmLevel().add(roles);
|
||||||
|
|
||||||
|
GroupRepresentation level2Group = new GroupRepresentation();
|
||||||
|
level2Group.setName("level2");
|
||||||
|
response = realm.groups().group(topGroup.getId()).subGroup(level2Group);
|
||||||
|
response.close();
|
||||||
|
level2Group = realm.getGroupByPath("/top/level2");
|
||||||
|
Assert.assertNotNull(level2Group);
|
||||||
|
roles.clear();
|
||||||
|
roles.add(level2Role);
|
||||||
|
realm.groups().group(level2Group.getId()).roles().realmLevel().add(roles);
|
||||||
|
|
||||||
|
GroupRepresentation level3Group = new GroupRepresentation();
|
||||||
|
level3Group.setName("level3");
|
||||||
|
response = realm.groups().group(level2Group.getId()).subGroup(level3Group);
|
||||||
|
response.close();
|
||||||
|
level3Group = realm.getGroupByPath("/top/level2/level3");
|
||||||
|
Assert.assertNotNull(level3Group);
|
||||||
|
roles.clear();
|
||||||
|
roles.add(level3Role);
|
||||||
|
realm.groups().group(level3Group.getId()).roles().realmLevel().add(roles);
|
||||||
|
|
||||||
|
topGroup = realm.getGroupByPath("/top");
|
||||||
|
Assert.assertEquals(1, topGroup.getRealmRoles().size());
|
||||||
|
Assert.assertTrue(topGroup.getRealmRoles().contains("topRole"));
|
||||||
|
Assert.assertEquals(1, topGroup.getSubGroups().size());
|
||||||
|
|
||||||
|
level2Group = topGroup.getSubGroups().get(0);
|
||||||
|
Assert.assertEquals("level2", level2Group.getName());
|
||||||
|
Assert.assertEquals(1, level2Group.getRealmRoles().size());
|
||||||
|
Assert.assertTrue(level2Group.getRealmRoles().contains("level2Role"));
|
||||||
|
Assert.assertEquals(1, level2Group.getSubGroups().size());
|
||||||
|
|
||||||
|
level3Group = level2Group.getSubGroups().get(0);
|
||||||
|
Assert.assertEquals("level3", level3Group.getName());
|
||||||
|
Assert.assertEquals(1, level3Group.getRealmRoles().size());
|
||||||
|
Assert.assertTrue(level3Group.getRealmRoles().contains("level3Role"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
GroupRepresentation notFound = realm.getGroupByPath("/notFound");
|
||||||
|
Assert.fail();
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
GroupRepresentation notFound = realm.getGroupByPath("/top/notFound");
|
||||||
|
Assert.fail();
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UserRepresentation user = realm.users().search("direct-login", -1, -1).get(0);
|
||||||
|
realm.users().get(user.getId()).joinGroup(level3Group.getId());
|
||||||
|
List<GroupRepresentation> membership = realm.users().get(user.getId()).groups();
|
||||||
|
Assert.assertEquals(1, membership.size());
|
||||||
|
Assert.assertEquals("level3", membership.get(0).getName());
|
||||||
|
|
||||||
|
AccessToken token = login("direct-login", "resource-owner", "secret", user.getId());
|
||||||
|
Assert.assertTrue(token.getRealmAccess().getRoles().contains("topRole"));
|
||||||
|
Assert.assertTrue(token.getRealmAccess().getRoles().contains("level2Role"));
|
||||||
|
Assert.assertTrue(token.getRealmAccess().getRoles().contains("level3Role"));
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGroupMappers() throws Exception {
|
||||||
|
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
ClientModel app = appRealm.getClientByClientId("test-app");
|
||||||
|
app.addProtocolMapper(GroupMembershipMapper.create("groups", "groups", false, null, true, true));
|
||||||
|
app.addProtocolMapper(UserAttributeMapper.createClaimMapper("topAttribute", "topAttribute", "topAttribute", ProviderConfigProperty.STRING_TYPE, false, null, true, true, false));
|
||||||
|
app.addProtocolMapper(UserAttributeMapper.createClaimMapper("level2Attribute", "level2Attribute", "level2Attribute", ProviderConfigProperty.STRING_TYPE, false, null, true, true, false));
|
||||||
|
}
|
||||||
|
}, "test");
|
||||||
|
RealmResource realm = keycloak.realms().realm("test");
|
||||||
|
{
|
||||||
|
UserRepresentation user = realm.users().search("topGroupUser", -1, -1).get(0);
|
||||||
|
|
||||||
|
AccessToken token = login(user.getUsername(), "test-app", "password", user.getId());
|
||||||
|
Assert.assertTrue(token.getRealmAccess().getRoles().contains("user"));
|
||||||
|
List<String> groups = (List<String>) token.getOtherClaims().get("groups");
|
||||||
|
Assert.assertNotNull(groups);
|
||||||
|
Assert.assertTrue(groups.size() == 1);
|
||||||
|
Assert.assertEquals("topGroup", groups.get(0));
|
||||||
|
Assert.assertEquals("true", token.getOtherClaims().get("topAttribute"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
UserRepresentation user = realm.users().search("level2GroupUser", -1, -1).get(0);
|
||||||
|
|
||||||
|
AccessToken token = login(user.getUsername(), "test-app", "password", user.getId());
|
||||||
|
Assert.assertTrue(token.getRealmAccess().getRoles().contains("user"));
|
||||||
|
Assert.assertTrue(token.getRealmAccess().getRoles().contains("admin"));
|
||||||
|
Assert.assertTrue(token.getResourceAccess("test-app").getRoles().contains("customer-user"));
|
||||||
|
List<String> groups = (List<String>) token.getOtherClaims().get("groups");
|
||||||
|
Assert.assertNotNull(groups);
|
||||||
|
Assert.assertTrue(groups.size() == 1);
|
||||||
|
Assert.assertEquals("level2group", groups.get(0));
|
||||||
|
Assert.assertEquals("true", token.getOtherClaims().get("topAttribute"));
|
||||||
|
Assert.assertEquals("true", token.getOtherClaims().get("level2Attribute"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AccessToken login(String login, String clientId, String clientSecret, String userId) throws Exception {
|
||||||
|
oauth.clientId(clientId);
|
||||||
|
|
||||||
|
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(clientSecret, login, "password");
|
||||||
|
|
||||||
|
assertEquals(200, response.getStatusCode());
|
||||||
|
|
||||||
|
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||||
|
RefreshToken refreshToken = oauth.verifyRefreshToken(response.getRefreshToken());
|
||||||
|
|
||||||
|
events.expectLogin()
|
||||||
|
.client(clientId)
|
||||||
|
.user(userId)
|
||||||
|
.session(accessToken.getSessionState())
|
||||||
|
.detail(Details.RESPONSE_TYPE, "token")
|
||||||
|
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||||
|
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||||
|
.detail(Details.USERNAME, login)
|
||||||
|
.removeDetail(Details.CODE_ID)
|
||||||
|
.removeDetail(Details.REDIRECT_URI)
|
||||||
|
.removeDetail(Details.CONSENT)
|
||||||
|
.assertEvent();
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -40,6 +40,30 @@
|
||||||
"applicationRoles": {
|
"applicationRoles": {
|
||||||
"account": [ "manage-account" ]
|
"account": [ "manage-account" ]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username" : "topGroupUser",
|
||||||
|
"enabled": true,
|
||||||
|
"email" : "top@redhat.com",
|
||||||
|
"credentials" : [
|
||||||
|
{ "type" : "password",
|
||||||
|
"value" : "password" }
|
||||||
|
],
|
||||||
|
"groups": [
|
||||||
|
"/top"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username" : "level2GroupUser",
|
||||||
|
"enabled": true,
|
||||||
|
"email" : "level2@redhat.com",
|
||||||
|
"credentials" : [
|
||||||
|
{ "type" : "password",
|
||||||
|
"value" : "password" }
|
||||||
|
],
|
||||||
|
"groups": [
|
||||||
|
"/top/level2"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"roles" : {
|
"roles" : {
|
||||||
|
@ -54,6 +78,29 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"groups" : [
|
||||||
|
{
|
||||||
|
"name": "top",
|
||||||
|
"attributes": {
|
||||||
|
"topAttribute": ["true"]
|
||||||
|
|
||||||
|
},
|
||||||
|
"realmRoles": ["user"],
|
||||||
|
"clientRoles": {
|
||||||
|
"account": ["manage-account"]
|
||||||
|
},
|
||||||
|
"subGroups": [
|
||||||
|
{
|
||||||
|
"name": "level2",
|
||||||
|
"realmRoles": ["admin"],
|
||||||
|
"attributes": {
|
||||||
|
"level2Attribute": ["true"]
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"scopeMappings": [
|
"scopeMappings": [
|
||||||
{
|
{
|
||||||
"client": "third-party",
|
"client": "third-party",
|
||||||
|
|
|
@ -40,6 +40,30 @@
|
||||||
{ "type" : "password",
|
{ "type" : "password",
|
||||||
"value" : "password" }
|
"value" : "password" }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username" : "topGroupUser",
|
||||||
|
"enabled": true,
|
||||||
|
"email" : "top@redhat.com",
|
||||||
|
"credentials" : [
|
||||||
|
{ "type" : "password",
|
||||||
|
"value" : "password" }
|
||||||
|
],
|
||||||
|
"groups": [
|
||||||
|
"/top"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username" : "level2GroupUser",
|
||||||
|
"enabled": true,
|
||||||
|
"email" : "level2@redhat.com",
|
||||||
|
"credentials" : [
|
||||||
|
{ "type" : "password",
|
||||||
|
"value" : "password" }
|
||||||
|
],
|
||||||
|
"groups": [
|
||||||
|
"/top/level2"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"applications": [
|
"applications": [
|
||||||
|
@ -347,6 +371,27 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"groups" : [
|
||||||
|
{
|
||||||
|
"name": "top",
|
||||||
|
"attributes": {
|
||||||
|
"topAttribute": ["true"]
|
||||||
|
|
||||||
|
},
|
||||||
|
"realmRoles": ["manager"],
|
||||||
|
"subGroups": [
|
||||||
|
{
|
||||||
|
"name": "level2",
|
||||||
|
"realmRoles": ["user"],
|
||||||
|
"attributes": {
|
||||||
|
"level2Attribute": ["true"]
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
"roles" : {
|
"roles" : {
|
||||||
"realm" : [
|
"realm" : [
|
||||||
{
|
{
|
||||||
|
|
|
@ -61,6 +61,30 @@
|
||||||
"test-app": [ "customer-user" ],
|
"test-app": [ "customer-user" ],
|
||||||
"account": [ "view-profile", "manage-account" ]
|
"account": [ "view-profile", "manage-account" ]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username" : "topGroupUser",
|
||||||
|
"enabled": true,
|
||||||
|
"email" : "top@redhat.com",
|
||||||
|
"credentials" : [
|
||||||
|
{ "type" : "password",
|
||||||
|
"value" : "password" }
|
||||||
|
],
|
||||||
|
"groups": [
|
||||||
|
"/topGroup"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username" : "level2GroupUser",
|
||||||
|
"enabled": true,
|
||||||
|
"email" : "level2@redhat.com",
|
||||||
|
"credentials" : [
|
||||||
|
{ "type" : "password",
|
||||||
|
"value" : "password" }
|
||||||
|
],
|
||||||
|
"groups": [
|
||||||
|
"/topGroup/level2group"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"scopeMappings": [
|
"scopeMappings": [
|
||||||
|
@ -120,6 +144,31 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
"groups" : [
|
||||||
|
{
|
||||||
|
"name": "topGroup",
|
||||||
|
"attributes": {
|
||||||
|
"topAttribute": ["true"]
|
||||||
|
|
||||||
|
},
|
||||||
|
"realmRoles": ["user"],
|
||||||
|
|
||||||
|
"subGroups": [
|
||||||
|
{
|
||||||
|
"name": "level2group",
|
||||||
|
"realmRoles": ["admin"],
|
||||||
|
"clientRoles": {
|
||||||
|
"test-app": ["customer-user"]
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"level2Attribute": ["true"]
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
"clientScopeMappings": {
|
"clientScopeMappings": {
|
||||||
"test-app": [
|
"test-app": [
|
||||||
|
|
Loading…
Reference in a new issue