/* * Copyright 2013-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.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import org.immutables.value.Value; 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.RollupConfig; import org.glowroot.common.repo.TraceAttributeNameRepository; import org.glowroot.common.repo.TransactionTypeRepository; import org.glowroot.common.util.ObjectMappers; import org.glowroot.common.util.Versions; import org.glowroot.ui.HttpSessionManager.Authentication; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.UiConfig; import static java.util.concurrent.TimeUnit.HOURS; class LayoutService { private static final String AGENT_ID = ""; private static final ObjectMapper mapper = ObjectMappers.create(); private final boolean central; private final boolean servlet; private final boolean offline; private final String version; private final ConfigRepository configRepository; private final AgentRepository agentRepository; private final TransactionTypeRepository transactionTypeRepository; private final TraceAttributeNameRepository traceAttributeNameRepository; LayoutService(boolean central, boolean servlet, boolean offline, String version, ConfigRepository configRepository, AgentRepository agentRepository, TransactionTypeRepository transactionTypeRepository, TraceAttributeNameRepository traceAttributeNameRepository) { this.central = central; this.servlet = servlet; this.offline = offline; this.version = version; this.configRepository = configRepository; this.agentRepository = agentRepository; this.transactionTypeRepository = transactionTypeRepository; this.traceAttributeNameRepository = traceAttributeNameRepository; } String getLayoutJson(Authentication authentication) throws Exception { Layout layout = buildLayout(authentication); return mapper.writeValueAsString(layout); } String getLayoutVersion(Authentication authentication) throws Exception { Layout layout = buildLayout(authentication); return layout.version(); } private Layout buildLayout(Authentication authentication) throws Exception { if (central) { return buildLayoutCentral(authentication); } else { return buildLayoutEmbedded(authentication); } } private Layout buildLayoutEmbedded(Authentication authentication) throws Exception { Permissions permissions = getPermissions(authentication, AGENT_ID, true); boolean hasSomeAccess = permissions.hasSomeAccess() || authentication.isAdminPermitted("admin:view"); if (!hasSomeAccess) { return createNoAccessLayout(authentication); } boolean showNavbarTransaction = permissions.transaction().hasSomeAccess(); boolean showNavbarError = permissions.error().hasSomeAccess(); boolean showNavbarJvm = permissions.jvm().hasSomeAccess(); boolean showNavbarAlert = permissions.alert() && !configRepository.getAlertConfigs(AGENT_ID).isEmpty(); boolean showNavbarConfig = permissions.config().view(); // a couple of special cases for embedded ui UiConfig uiConfig = configRepository.getUiConfig(AGENT_ID); String defaultDisplayedTransactionType = uiConfig.getDefaultDisplayedTransactionType(); Set<String> transactionTypes = Sets.newTreeSet(); List<String> storedTransactionTypes = transactionTypeRepository.read().get(AGENT_ID); if (storedTransactionTypes != null) { transactionTypes.addAll(storedTransactionTypes); } transactionTypes.add(defaultDisplayedTransactionType); Map<String, List<String>> traceAttributeNames = traceAttributeNameRepository.read().get(AGENT_ID); if (traceAttributeNames == null) { traceAttributeNames = ImmutableMap.of(); } Map<String, AgentRollupLayout> agentRollups = Maps.newLinkedHashMap(); agentRollups.put(AGENT_ID, ImmutableAgentRollupLayout.builder() .display(AGENT_ID) .depth(0) .agent(true) .permissions(permissions) .addAllTransactionTypes(transactionTypes) .putAllTraceAttributeNames(traceAttributeNames) .defaultDisplayedTransactionType(defaultDisplayedTransactionType) .defaultDisplayedPercentiles(uiConfig.getDefaultDisplayedPercentileList()) .build()); return createLayout(authentication, agentRollups, showNavbarTransaction, showNavbarError, showNavbarJvm, false, showNavbarAlert, false, showNavbarConfig); } private Layout buildLayoutCentral(Authentication authentication) throws Exception { List<FilteredAgentRollup> agentRollups = filter(agentRepository.readAgentRollups(), authentication); CentralLayoutBuilder centralLayoutBuilder = new CentralLayoutBuilder(authentication); for (FilteredAgentRollup agentRollup : agentRollups) { centralLayoutBuilder.process(agentRollup, 0); } return centralLayoutBuilder.build(authentication); } private ImmutableLayout createNoAccessLayout(Authentication authentication) { return ImmutableLayout.builder() .central(central) .servlet(servlet) .offline(offline) .glowrootVersion(version) .loginEnabled(true) .gaugeCollectionIntervalMillis(0) .showNavbarTransaction(false) .showNavbarError(false) .showNavbarJvm(false) .showNavbarSyntheticMonitor(false) .showNavbarAlert(false) .showNavbarReport(false) .showNavbarConfig(false) .adminView(false) .adminEdit(false) .loggedIn(!authentication.anonymous()) .ldap(authentication.ldap()) .redirectToLogin(true) .defaultTimeZoneId(TimeZone.getDefault().getID()) .build(); } private ImmutableLayout createLayout(Authentication authentication, Map<String, AgentRollupLayout> agentRollups, boolean showNavbarTransaction, boolean showNavbarError, boolean showNavbarJvm, boolean showNavbarSyntheticMonitor, boolean showNavbarAlert, boolean showNavbarReport, boolean showNavbarConfig) throws Exception { List<Long> rollupExpirationMillis = Lists.newArrayList(); for (long hours : configRepository.getStorageConfig().rollupExpirationHours()) { rollupExpirationMillis.add(HOURS.toMillis(hours)); } return ImmutableLayout.builder() .central(central) .servlet(servlet) .offline(offline) .glowrootVersion(version) .loginEnabled(offline ? false : configRepository.namedUsersExist() || !configRepository.getLdapConfig().host().isEmpty()) .addAllRollupConfigs(configRepository.getRollupConfigs()) .addAllRollupExpirationMillis(rollupExpirationMillis) .gaugeCollectionIntervalMillis(configRepository.getGaugeCollectionIntervalMillis()) .agentRollups(agentRollups) .showNavbarTransaction(showNavbarTransaction) .showNavbarError(showNavbarError) .showNavbarJvm(showNavbarJvm) .showNavbarSyntheticMonitor(showNavbarSyntheticMonitor) .showNavbarAlert(showNavbarAlert) .showNavbarReport(showNavbarReport) .showNavbarConfig(showNavbarConfig) .adminView(authentication.isAdminPermitted("admin:view")) .adminEdit(authentication.isAdminPermitted("admin:edit")) .loggedIn(!authentication.anonymous()) .ldap(authentication.ldap()) .redirectToLogin(false) .defaultTimeZoneId(TimeZone.getDefault().getID()) .addAllTimeZoneIds(Arrays.asList(TimeZone.getAvailableIDs())) .build(); } // need to filter out agent rollups with no access rights, and move children up if needed private List<FilteredAgentRollup> filter(List<AgentRollup> agentRollups, Authentication authentication) throws Exception { List<FilteredAgentRollup> filtered = Lists.newArrayList(); for (AgentRollup agentRollup : agentRollups) { Permissions permissions = getPermissions(authentication, agentRollup.id(), agentRollup.agent()); if (permissions.hasSomeAccess()) { filtered.add(ImmutableFilteredAgentRollup.builder() .id(agentRollup.id()) .display(agentRollup.display()) .agent(agentRollup.agent()) .addAllChildren(filter(agentRollup.children(), authentication)) .permissions(permissions) .build()); } else { // move children (if they are accessible themselves) up to this level filtered.addAll(filter(agentRollup.children(), authentication)); } } // re-sort in case any children were moved up to this level return new FilteredAgentRollupOrdering().sortedCopy(filtered); } private static Permissions getPermissions(Authentication authentication, String agentRollupId, boolean agent) throws Exception { return ImmutablePermissions.builder() .transaction(ImmutableTransactionPermissions.builder() .overview(authentication.isAgentPermitted(agentRollupId, "agent:transaction:overview")) .traces(authentication.isAgentPermitted(agentRollupId, "agent:transaction:traces")) .queries(authentication.isAgentPermitted(agentRollupId, "agent:transaction:queries")) .serviceCalls(authentication.isAgentPermitted(agentRollupId, "agent:transaction:serviceCalls")) .profile(authentication.isAgentPermitted(agentRollupId, "agent:transaction:profile")) .build()) .error(ImmutableErrorPermissions.builder() .overview(authentication.isAgentPermitted(agentRollupId, "agent:error:overview")) .traces(authentication.isAgentPermitted(agentRollupId, "agent:error:traces")) .build()) .jvm(ImmutableJvmPermissions.builder() .gauges(authentication.isAgentPermitted(agentRollupId, "agent:jvm:gauges")) .threadDump(agent && authentication.isAgentPermitted(agentRollupId, "agent:jvm:threadDump")) .heapDump(agent && authentication.isAgentPermitted(agentRollupId, "agent:jvm:heapDump")) .heapHistogram(agent && authentication.isAgentPermitted(agentRollupId, "agent:jvm:heapHistogram")) .gc(agent && authentication.isAgentPermitted(agentRollupId, "agent:jvm:gc")) .mbeanTree(agent && authentication.isAgentPermitted(agentRollupId, "agent:jvm:mbeanTree")) .systemProperties(agent && authentication.isAgentPermitted(agentRollupId, "agent:jvm:systemProperties")) .environment(agent && authentication.isAgentPermitted(agentRollupId, "agent:jvm:environment")) .capabilities(agent && authentication.isAgentPermitted(agentRollupId, "agent:jvm:capabilities")) .build()) .syntheticMonitor( authentication.isAgentPermitted(agentRollupId, "agent:syntheticMonitor")) .alert(authentication.isAgentPermitted(agentRollupId, "agent:alert")) .config(ImmutableConfigPermissions.builder() // central supports alert configs and ui config on rollups .view(authentication.isAgentPermitted(agentRollupId, "agent:config:view")) .edit(ImmutableEditConfigPermissions.builder() .transaction(agent && authentication.isAgentPermitted(agentRollupId, "agent:config:edit:transaction")) .gauge(agent && authentication.isAgentPermitted(agentRollupId, "agent:config:edit:gauge")) // central supports synthetic monitor configs on rollups .syntheticMonitor(authentication.isAgentPermitted(agentRollupId, "agent:config:edit:syntheticMonitor")) // central supports alert configs on rollups .alert(authentication.isAgentPermitted(agentRollupId, "agent:config:edit:alert")) // central supports ui config on rollups .ui(authentication.isAgentPermitted(agentRollupId, "agent:config:edit:ui")) .plugin(agent && authentication.isAgentPermitted(agentRollupId, "agent:config:edit:plugin")) .instrumentation(agent && authentication.isAgentPermitted( agentRollupId, "agent:config:edit:instrumentation")) // central supports advanced config on rollups // (maxAggregateQueriesPerType and maxAggregateServiceCallsPerType) .advanced(authentication.isAgentPermitted(agentRollupId, "agent:config:edit:advanced")) .userRecording( agent && authentication.isAgentPermitted(agentRollupId, "agent:config:edit:userRecording")) .build()) .build()) .build(); } private class CentralLayoutBuilder { // linked hash map to preserve ordering private final Map<String, AgentRollupLayout> agentRollups = Maps.newLinkedHashMap(); private final Map<String, List<String>> transactionTypesMap; private final Map<String, Map<String, List<String>>> traceAttributeNamesMap; private boolean hasSomeAccess = false; private boolean showNavbarTransaction = false; private boolean showNavbarError = false; private boolean showNavbarJvm = false; private boolean showNavbarSyntheticMonitor = false; private boolean showNavbarAlert = false; private boolean showNavbarReport = false; private boolean showNavbarConfig = false; private CentralLayoutBuilder(Authentication authentication) throws Exception { transactionTypesMap = transactionTypeRepository.read(); traceAttributeNamesMap = traceAttributeNameRepository.read(); // "*" is to check permissions for "all agents" Permissions permissions = getPermissions(authentication, "*", true); hasSomeAccess = permissions.hasSomeAccess() || authentication.isAdminPermitted("admin:view"); showNavbarTransaction = permissions.transaction().hasSomeAccess(); showNavbarError = permissions.error().hasSomeAccess(); showNavbarJvm = permissions.jvm().hasSomeAccess(); showNavbarSyntheticMonitor = permissions.syntheticMonitor(); showNavbarAlert = permissions.alert(); // for now (for simplicity) reporting requires permission for ALL reportable metrics // (currently transaction:overview and jvm:gauges) showNavbarReport = permissions.transaction().overview() && permissions.jvm().gauges(); showNavbarConfig = permissions.config().view(); } private void process(FilteredAgentRollup agentRollup, int depth) throws Exception { Permissions permissions = agentRollup.permissions(); hasSomeAccess = true; showNavbarTransaction = showNavbarTransaction || permissions.transaction().hasSomeAccess(); showNavbarError = showNavbarError || permissions.error().hasSomeAccess(); showNavbarJvm = showNavbarJvm || permissions.jvm().hasSomeAccess(); showNavbarSyntheticMonitor = showNavbarSyntheticMonitor || permissions.syntheticMonitor(); showNavbarAlert = showNavbarAlert || permissions.alert(); // for now (for simplicity) reporting requires permission for ALL reportable metrics // (currently transaction:overview and jvm:gauges) showNavbarReport = showNavbarReport || (permissions.transaction().overview() && permissions.jvm().gauges()); showNavbarConfig = showNavbarConfig || permissions.config().view(); UiConfig uiConfig = configRepository.getUiConfig(agentRollup.id()); String defaultDisplayedTransactionType = uiConfig.getDefaultDisplayedTransactionType(); List<Double> defaultDisplayedPercentiles = uiConfig.getDefaultDisplayedPercentileList(); Set<String> transactionTypes = Sets.newTreeSet(); List<String> storedTransactionTypes = transactionTypesMap.get(agentRollup.id()); if (storedTransactionTypes != null) { transactionTypes.addAll(storedTransactionTypes); } transactionTypes.add(defaultDisplayedTransactionType); Map<String, List<String>> traceAttributeNames = traceAttributeNamesMap.get(agentRollup.id()); if (traceAttributeNames == null) { traceAttributeNames = ImmutableMap.of(); } agentRollups.put(agentRollup.id(), ImmutableAgentRollupLayout.builder() .display(agentRollup.display()) .depth(depth) .agent(agentRollup.agent()) .permissions(permissions) .addAllTransactionTypes(transactionTypes) .putAllTraceAttributeNames(traceAttributeNames) .defaultDisplayedTransactionType(defaultDisplayedTransactionType) .defaultDisplayedPercentiles(defaultDisplayedPercentiles) .build()); for (FilteredAgentRollup childAgentRollup : agentRollup.children()) { process(childAgentRollup, depth + 1); } } private ImmutableLayout build(Authentication authentication) throws Exception { if (hasSomeAccess) { return createLayout(authentication, agentRollups, showNavbarTransaction, showNavbarError, showNavbarJvm, showNavbarSyntheticMonitor, showNavbarAlert, showNavbarReport, showNavbarConfig); } else { return createNoAccessLayout(authentication); } } } @Value.Immutable interface FilteredAgentRollup { String id(); String display(); boolean agent(); Permissions permissions(); List<FilteredAgentRollup> children(); } @Value.Immutable abstract static class Layout { abstract boolean central(); abstract boolean servlet(); abstract boolean offline(); abstract String glowrootVersion(); abstract boolean loginEnabled(); abstract ImmutableList<RollupConfig> rollupConfigs(); abstract ImmutableList<Long> rollupExpirationMillis(); abstract long gaugeCollectionIntervalMillis(); abstract ImmutableMap<String, AgentRollupLayout> agentRollups(); abstract boolean showNavbarTransaction(); abstract boolean showNavbarError(); abstract boolean showNavbarJvm(); abstract boolean showNavbarSyntheticMonitor(); abstract boolean showNavbarAlert(); abstract boolean showNavbarReport(); abstract boolean showNavbarConfig(); abstract boolean adminView(); abstract boolean adminEdit(); abstract boolean loggedIn(); abstract boolean ldap(); abstract boolean redirectToLogin(); abstract String defaultTimeZoneId(); abstract List<String> timeZoneIds(); @Value.Derived public String version() { return Versions.getJsonVersion(this); } } @Value.Immutable interface AgentRollupLayout { String display(); int depth(); boolean agent(); Permissions permissions(); List<String> transactionTypes(); Map<String, List<String>> traceAttributeNames(); // key is transaction type String defaultDisplayedTransactionType(); List<Double> defaultDisplayedPercentiles(); } @Value.Immutable static abstract class Permissions { abstract TransactionPermissions transaction(); abstract ErrorPermissions error(); abstract JvmPermissions jvm(); abstract boolean syntheticMonitor(); abstract boolean alert(); abstract ConfigPermissions config(); private boolean hasSomeAccess() { return transaction().hasSomeAccess() || error().hasSomeAccess() || jvm().hasSomeAccess() || config().view(); } } @Value.Immutable static abstract class TransactionPermissions { abstract boolean overview(); abstract boolean traces(); abstract boolean queries(); abstract boolean serviceCalls(); abstract boolean profile(); private boolean hasSomeAccess() { return overview() || traces() || queries() || serviceCalls() || profile(); } } @Value.Immutable static abstract class ErrorPermissions { abstract boolean overview(); abstract boolean traces(); private boolean hasSomeAccess() { return overview() || traces(); } } @Value.Immutable static abstract class JvmPermissions { abstract boolean gauges(); abstract boolean threadDump(); abstract boolean heapDump(); abstract boolean heapHistogram(); abstract boolean gc(); abstract boolean mbeanTree(); abstract boolean systemProperties(); abstract boolean environment(); abstract boolean capabilities(); private boolean hasSomeAccess() { // capabilities is not in sidebar, so not included here return gauges() || threadDump() || heapDump() || heapHistogram() || gc() || mbeanTree() || systemProperties() || environment(); } } @Value.Immutable interface ConfigPermissions { boolean view(); EditConfigPermissions edit(); } @Value.Immutable interface EditConfigPermissions { boolean transaction(); boolean gauge(); boolean syntheticMonitor(); boolean alert(); boolean ui(); boolean plugin(); boolean instrumentation(); boolean userRecording(); boolean advanced(); } private static class FilteredAgentRollupOrdering extends Ordering<FilteredAgentRollup> { @Override public int compare(FilteredAgentRollup left, FilteredAgentRollup right) { return left.display().compareToIgnoreCase(right.display()); } } }