/* * Copyright 2016-2017 the original author or authors. * * 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.glowroot.ui; import java.util.Collection; import java.util.List; import java.util.Map.Entry; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Optional; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.Ordering; import io.netty.handler.codec.http.HttpResponseStatus; import org.immutables.value.Value; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.glowroot.common.config.ImmutableRoleConfig; import org.glowroot.common.config.PermissionParser; import org.glowroot.common.config.RoleConfig; import org.glowroot.common.repo.AgentRepository; import org.glowroot.common.repo.AgentRepository.AgentRollup; import org.glowroot.common.repo.ConfigRepository; import org.glowroot.common.repo.ConfigRepository.CannotDeleteLastRoleException; import org.glowroot.common.repo.ConfigRepository.DuplicateRoleNameException; import org.glowroot.common.util.ObjectMappers; import static io.netty.handler.codec.http.HttpResponseStatus.CONFLICT; @JsonService class RoleConfigJsonService { private static final Logger logger = LoggerFactory.getLogger(RoleConfigJsonService.class); private static final ObjectMapper mapper = ObjectMappers.create(); private static final Ordering<RoleConfig> orderingByName = new Ordering<RoleConfig>() { @Override public int compare(RoleConfig left, RoleConfig right) { return left.name().compareToIgnoreCase(right.name()); } }; private final boolean central; private final ConfigRepository configRepository; private final AgentRepository agentRepository; RoleConfigJsonService(boolean central, ConfigRepository configRepository, AgentRepository agentRepository) { this.central = central; this.configRepository = configRepository; this.agentRepository = agentRepository; } @GET(path = "/backend/admin/roles", permission = "admin:view:role") String getRoleConfig(@BindRequest RoleConfigRequest request) throws Exception { Optional<String> name = request.name(); if (name.isPresent()) { return getRoleConfigInternal(name.get()); } else { List<RoleConfigListDto> responses = Lists.newArrayList(); List<RoleConfig> roleConfigs = configRepository.getRoleConfigs(); roleConfigs = orderingByName.immutableSortedCopy(roleConfigs); for (RoleConfig roleConfig : roleConfigs) { responses.add(RoleConfigListDto.create(roleConfig)); } return mapper.writeValueAsString(responses); } } @GET(path = "/backend/admin/all-agent-rollups", permission = "admin:edit:role") String getAllAgentRollups() throws Exception { return mapper.writeValueAsString(getFlattenedAgentRollups()); } @POST(path = "/backend/admin/roles/add", permission = "admin:edit:role") String addRole(@BindRequest RoleConfigDto roleConfigDto) throws Exception { RoleConfig roleConfig = roleConfigDto.convert(central); try { configRepository.insertRoleConfig(roleConfig); } catch (DuplicateRoleNameException e) { // log exception at debug level logger.debug(e.getMessage(), e); throw new JsonServiceException(CONFLICT, "name"); } return getRoleConfigInternal(roleConfig.name()); } @POST(path = "/backend/admin/roles/update", permission = "admin:edit:role") String updateRole(@BindRequest RoleConfigDto roleConfigDto) throws Exception { RoleConfig roleConfig = roleConfigDto.convert(central); String version = roleConfigDto.version().get(); try { configRepository.updateRoleConfig(roleConfig, version); } catch (DuplicateRoleNameException e) { // log exception at debug level logger.debug(e.getMessage(), e); throw new JsonServiceException(CONFLICT, "name"); } return getRoleConfigInternal(roleConfig.name()); } @POST(path = "/backend/admin/roles/remove", permission = "admin:edit:role") String removeRole(@BindRequest RoleConfigRequest request) throws Exception { try { configRepository.deleteRoleConfig(request.name().get()); } catch (CannotDeleteLastRoleException e) { logger.debug(e.getMessage(), e); return "{\"errorCannotDeleteLastRole\":true}"; } return "{}"; } private String getRoleConfigInternal(String name) throws Exception { RoleConfig roleConfig = configRepository.getRoleConfig(name); if (roleConfig == null) { throw new JsonServiceException(HttpResponseStatus.NOT_FOUND); } ImmutableRoleConfigResponse.Builder response = ImmutableRoleConfigResponse.builder() .config(RoleConfigDto.create(roleConfig, central)); if (central) { response.allAgentRollups(getFlattenedAgentRollups()); } return mapper.writeValueAsString(response.build()); } private List<FlattenedAgentRollup> getFlattenedAgentRollups() { List<AgentRollup> agentRollups = agentRepository.readAgentRollups(); List<FlattenedAgentRollup> flattenedAgentRollups = Lists.newArrayList(); for (AgentRollup agentRollup : agentRollups) { flattenedAgentRollups.addAll(getFlattenedAgentRollups(agentRollup, 0)); } return flattenedAgentRollups; } private static List<FlattenedAgentRollup> getFlattenedAgentRollups(AgentRollup agentRollup, int depth) { List<FlattenedAgentRollup> flattenedAgentRollups = Lists.newArrayList(); flattenedAgentRollups.add(ImmutableFlattenedAgentRollup.builder() .id(agentRollup.id()) .display(agentRollup.display()) .depth(depth) .build()); for (AgentRollup childAgentRollup : agentRollup.children()) { flattenedAgentRollups.addAll(getFlattenedAgentRollups(childAgentRollup, depth + 1)); } return flattenedAgentRollups; } @Value.Immutable interface RoleConfigRequest { Optional<String> name(); } @Value.Immutable interface RoleConfigResponse { RoleConfigDto config(); ImmutableList<FlattenedAgentRollup> allAgentRollups(); } @Value.Immutable interface FlattenedAgentRollup { int depth(); String id(); String display(); } @Value.Immutable abstract static class RoleConfigListDto { abstract String name(); abstract String version(); private static RoleConfigListDto create(RoleConfig roleConfig) { return ImmutableRoleConfigListDto.builder() .name(roleConfig.name()) .version(roleConfig.version()) .build(); } } @Value.Immutable abstract static class RoleConfigDto { abstract String name(); abstract ImmutableList<String> permissions(); abstract ImmutableList<ImmutableRolePermissionBlock> permissionBlocks(); abstract Optional<String> version(); // absent for insert operations private RoleConfig convert(boolean central) { ImmutableRoleConfig.Builder builder = ImmutableRoleConfig.builder() .central(central) .name(name()); if (central) { for (String permission : permissions()) { if (permission.startsWith("agent:")) { builder.addPermissions( "agent:*:" + permission.substring("agent:".length())); } else { builder.addPermissions(permission); } } } else { builder.addAllPermissions(permissions()); } for (RolePermissionBlock permissionBlock : permissionBlocks()) { String agentIds = PermissionParser.quoteIfNeededAndJoin(permissionBlock.agentRollupIds()); for (String permission : permissionBlock.permissions()) { builder.addPermissions( "agent:" + agentIds + ":" + permission.substring("agent:".length())); } } return builder.build(); } private static RoleConfigDto create(RoleConfig roleConfig, boolean central) { ImmutableRoleConfigDto.Builder builder = ImmutableRoleConfigDto.builder() .name(roleConfig.name()); if (central) { Multimap<List<String>, String> permissionBlocks = HashMultimap.create(); for (String permission : roleConfig.permissions()) { if (permission.startsWith("agent:")) { PermissionParser parser = new PermissionParser(permission); parser.parse(); if (parser.getAgentRollupIds().size() == 1 && parser.getAgentRollupIds().get(0).equals("*")) { builder.addPermissions(parser.getPermission()); } else { // sorting in order to combine agent:a,b:... and agent:b,a:... List<String> agentIds = Ordering.natural().sortedCopy(parser.getAgentRollupIds()); permissionBlocks.put(agentIds, parser.getPermission()); } } else { builder.addPermissions(permission); } } for (Entry<List<String>, Collection<String>> entry : permissionBlocks.asMap() .entrySet()) { builder.addPermissionBlocks(ImmutableRolePermissionBlock.builder() .addAllAgentRollupIds(entry.getKey()) .addAllPermissions(entry.getValue()) .build()); } } else { builder.addAllPermissions(roleConfig.permissions()); } return builder.version(roleConfig.version()) .build(); } } @Value.Immutable interface RolePermissionBlock { ImmutableList<String> agentRollupIds(); ImmutableList<String> permissions(); } }