/* * Copyright 2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.keycloak.services.resources.admin; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.Constants; 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.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; import org.keycloak.services.ErrorResponse; /** * @resource Groups * @author Bill Burke */ public class GroupResource { private final RealmModel realm; private final KeycloakSession session; private final RealmAuth auth; private final AdminEventBuilder adminEvent; private final GroupModel group; public GroupResource(RealmModel realm, GroupModel group, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) { this.realm = realm; this.session = session; this.auth = auth; this.adminEvent = adminEvent.resource(ResourceType.GROUP); this.group = group; } @Context private UriInfo uriInfo; /** * * * @return */ @GET @NoCache @Produces(MediaType.APPLICATION_JSON) public GroupRepresentation getGroup() { this.auth.requireView(); if (group == null) { throw new NotFoundException("Could not find group by id"); } return ModelToRepresentation.toGroupHierarchy(group, true); } /** * Update group, ignores subgroups. * * @param rep */ @PUT @Consumes(MediaType.APPLICATION_JSON) public void updateGroup(GroupRepresentation rep) { this.auth.requireManage(); if (group == null) { throw new NotFoundException("Could not find group by id"); } updateGroup(rep, group); adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); } @DELETE public void deleteGroup() { this.auth.requireManage(); if (group == null) { throw new NotFoundException("Could not find group by id"); } realm.removeGroup(group); adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); } /** * 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 addChild(GroupRepresentation rep) { this.auth.requireManage(); if (group == null) { throw new NotFoundException("Could not find group by id"); } for (GroupModel group : group.getSubGroups()) { if (group.getName().equals(rep.getName())) { return ErrorResponse.exists("Parent already contains subgroup named '" + rep.getName() + "'"); } } Response.ResponseBuilder builder = Response.status(204); GroupModel child = null; if (rep.getId() != null) { child = realm.getGroupById(rep.getId()); if (child == null) { throw new NotFoundException("Could not find child by id"); } adminEvent.operation(OperationType.UPDATE); } else { child = realm.createGroup(rep.getName()); updateGroup(rep, child); URI uri = uriInfo.getBaseUriBuilder() .path(uriInfo.getMatchedURIs().get(2)) .path(child.getId()).build(); builder.status(201).location(uri); rep.setId(child.getId()); adminEvent.operation(OperationType.CREATE); } realm.moveGroup(child, group); adminEvent.resourcePath(uriInfo).representation(rep).success(); GroupRepresentation childRep = ModelToRepresentation.toGroupHierarchy(child, true); return builder.type(MediaType.APPLICATION_JSON_TYPE).entity(childRep).build(); } public static 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("role-mappings") public RoleMapperResource getRoleMappings() { auth.init(RealmAuth.Resource.USER); RoleMapperResource resource = new RoleMapperResource(realm, auth, group, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(resource); return resource; } /** * Get users * * Returns a list of users, filtered according to query parameters * * @param firstResult Pagination offset * @param maxResults Maximum results size (defaults to 100) * @return */ @GET @NoCache @Path("members") @Produces(MediaType.APPLICATION_JSON) public List<UserRepresentation> getMembers(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) { auth.requireView(); if (group == null) { throw new NotFoundException("Could not find group by id"); } firstResult = firstResult != null ? firstResult : 0; maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS; List<UserRepresentation> results = new ArrayList<UserRepresentation>(); List<UserModel> userModels = session.users().getGroupMembers(realm, group, firstResult, maxResults); for (UserModel user : userModels) { results.add(ModelToRepresentation.toRepresentation(session, realm, user)); } return results; } }