From 7315c8138dea24beab2f4a3109b02681df609b19 Mon Sep 17 00:00:00 2001 From: Minwoo Jung Date: Thu, 23 Jun 2016 14:31:15 +0900 Subject: [PATCH] [#1867] add interface for data filtering (#1868) * [#1867] add interface for data filtering --- .../common/server/bo/AnnotationBo.java | 12 +- .../pinpoint/common/trace/ServiceType.java | 2 + web/pom.xml | 13 ++ .../pinpoint/web/applicationmap/Link.java | 4 +- .../pinpoint/web/applicationmap/Node.java | 8 +- .../web/controller/AgentStatController.java | 2 + .../ControllerExceptionHandler.java | 6 + .../pinpoint/web/security/MetaDataFilter.java | 40 ++++++ .../web/security/ServerMapDataFilter.java | 27 ++++ .../pinpoint/web/service/BFSLinkSelector.java | 19 ++- .../web/service/FilteredMapServiceImpl.java | 11 +- .../web/service/LinkVisitChecker.java | 4 +- .../pinpoint/web/service/MapServiceImpl.java | 6 +- .../pinpoint/web/service/SpanServiceImpl.java | 11 ++ .../service/TransactionInfoServiceImpl.java | 21 ++- .../pinpoint/web/view/LinkSerializer.java | 131 +++++++++++++----- .../pinpoint/web/view/NodeSerializer.java | 68 ++++++--- .../TransactionInfoCallStackSerializer.java | 3 +- .../web/view/TransactionInfoViewModel.java | 48 +++---- .../pinpoint/web/vo/callstacks/Record.java | 33 ++--- .../web/vo/callstacks/RecordFactory.java | 61 +++++++- .../main/resources/pinpoint-web.properties | 5 + .../main/webapp/images/icons/UNAUTHORIZED.png | Bin 0 -> 49941 bytes .../web/service/BFSLinkSelectorTest.java | 2 +- 24 files changed, 410 insertions(+), 127 deletions(-) create mode 100644 web/src/main/java/com/navercorp/pinpoint/web/security/MetaDataFilter.java create mode 100644 web/src/main/java/com/navercorp/pinpoint/web/security/ServerMapDataFilter.java create mode 100644 web/src/main/webapp/images/icons/UNAUTHORIZED.png diff --git a/commons-server/src/main/java/com/navercorp/pinpoint/common/server/bo/AnnotationBo.java b/commons-server/src/main/java/com/navercorp/pinpoint/common/server/bo/AnnotationBo.java index 40211c10d..a72406d3e 100644 --- a/commons-server/src/main/java/com/navercorp/pinpoint/common/server/bo/AnnotationBo.java +++ b/commons-server/src/main/java/com/navercorp/pinpoint/common/server/bo/AnnotationBo.java @@ -37,7 +37,8 @@ public class AnnotationBo { private byte valueType; private byte[] byteValue; private Object value; - + private boolean isAuthorized = true; + public AnnotationBo() { } @@ -113,6 +114,15 @@ public class AnnotationBo { this.value = value; } + public boolean isAuthorized() { + return isAuthorized; + } + + public void setAuthorized(boolean isAuthorized) { + this.isAuthorized = isAuthorized; + } + + @Deprecated public void writeValue(Buffer buffer) { // long timestamp; // required 8 diff --git a/commons/src/main/java/com/navercorp/pinpoint/common/trace/ServiceType.java b/commons/src/main/java/com/navercorp/pinpoint/common/trace/ServiceType.java index bab41838d..66a854e1a 100644 --- a/commons/src/main/java/com/navercorp/pinpoint/common/trace/ServiceType.java +++ b/commons/src/main/java/com/navercorp/pinpoint/common/trace/ServiceType.java @@ -207,6 +207,8 @@ public interface ServiceType { // Java applications, WAS ServiceType STAND_ALONE = of(1000, "STAND_ALONE", RECORD_STATISTICS); ServiceType TEST_STAND_ALONE = of(1005, "TEST_STAND_ALONE", RECORD_STATISTICS); + ServiceType UNAUTHORIZED = of(1007, "UNAUTHORIZED", RECORD_STATISTICS); + /** diff --git a/web/pom.xml b/web/pom.xml index cb4b236ad..f5bc89e2e 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -16,6 +16,7 @@ ${env.JAVA_7_HOME} yyyyMMddHHmmss java17 + 4.0.4.RELEASE @@ -141,6 +142,18 @@ org.springframework spring-context-support + + + org.springframework.security + spring-security-web + ${spring.security} + + + + org.springframework.security + spring-security-config + ${spring.security} + diff --git a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/Link.java b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/Link.java index 716fc7fac..7e187c3da 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/Link.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/Link.java @@ -113,8 +113,8 @@ public class Link { return toNode; } - public String getLinkName() { - return fromNode.getNodeName() + LINK_DELIMITER + toNode.getNodeName(); + public String getLinkName(boolean isAuthFromApp, boolean isAuthToApp) { + return fromNode.getNodeName(isAuthFromApp) + LINK_DELIMITER + toNode.getNodeName(isAuthToApp); } public LinkCallDataMap getSourceLinkCallDataMap() { diff --git a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/Node.java b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/Node.java index 04dec561c..0c68abfe3 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/Node.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/Node.java @@ -84,8 +84,12 @@ public class Node { } - public String getNodeName() { - return application.getName() + NODE_DELIMITER + application.getServiceType(); + public String getNodeName(boolean isAuthorized) { + if (isAuthorized) { + return application.getName() + NODE_DELIMITER + application.getServiceType(); + } + + return application.getName() + NODE_DELIMITER + ServiceType.UNAUTHORIZED.toString(); } public ServiceType getServiceType() { diff --git a/web/src/main/java/com/navercorp/pinpoint/web/controller/AgentStatController.java b/web/src/main/java/com/navercorp/pinpoint/web/controller/AgentStatController.java index 7f681d59d..424ac07d6 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/controller/AgentStatController.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/controller/AgentStatController.java @@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.util.StopWatch; import org.springframework.web.bind.annotation.RequestMapping; @@ -134,6 +135,7 @@ public class AgentStatController { return this.agentInfoService.getApplicationAgentList(ApplicationAgentList.Key.HOST_NAME, applicationName); } + @PreAuthorize("hasPermission(#applicationName, 'application', 'inspector')") @RequestMapping(value = "/getAgentList", method = RequestMethod.GET, params = {"application", "from", "to"}) @ResponseBody public ApplicationAgentList getAgentList( diff --git a/web/src/main/java/com/navercorp/pinpoint/web/controller/ControllerExceptionHandler.java b/web/src/main/java/com/navercorp/pinpoint/web/controller/ControllerExceptionHandler.java index ba031a20f..95c2e525d 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/controller/ControllerExceptionHandler.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/controller/ControllerExceptionHandler.java @@ -28,6 +28,7 @@ import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; @@ -53,6 +54,11 @@ public class ControllerExceptionHandler { mav.setViewName(DEFAULT_ERROR_VIEW); return mav; } + + @ExceptionHandler(value = AccessDeniedException.class) + public ModelAndView accessDeniedExceptionHandler(HttpServletRequest request, Exception exception) throws Exception { + throw exception; + } private Map createExceptionResource(HttpServletRequest request, Throwable throwable) { Map exceptionMap = new HashMap<>(); diff --git a/web/src/main/java/com/navercorp/pinpoint/web/security/MetaDataFilter.java b/web/src/main/java/com/navercorp/pinpoint/web/security/MetaDataFilter.java new file mode 100644 index 000000000..72545b611 --- /dev/null +++ b/web/src/main/java/com/navercorp/pinpoint/web/security/MetaDataFilter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014 NAVER Corp. + * + * 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 com.navercorp.pinpoint.web.security; + +import com.navercorp.pinpoint.common.server.bo.AnnotationBo; +import com.navercorp.pinpoint.web.calltree.span.CallTreeNode; +import com.navercorp.pinpoint.web.calltree.span.SpanAlign; +import com.navercorp.pinpoint.web.vo.callstacks.Record; +import com.navercorp.pinpoint.web.vo.callstacks.RecordFactory; + +/** + * @author minwoo.jung + */ +public interface MetaDataFilter { + + public enum MetaData { + API, SQL, PARAM + } + + boolean filter(SpanAlign spanAlign, MetaData metaData); + + AnnotationBo createAnnotationBo(SpanAlign spanAlign, MetaData metaData); + + Record createRecord(CallTreeNode node, RecordFactory factory); + + void replaceAnnotationBo(SpanAlign align, MetaData param); +} diff --git a/web/src/main/java/com/navercorp/pinpoint/web/security/ServerMapDataFilter.java b/web/src/main/java/com/navercorp/pinpoint/web/security/ServerMapDataFilter.java new file mode 100644 index 000000000..5fa5f7637 --- /dev/null +++ b/web/src/main/java/com/navercorp/pinpoint/web/security/ServerMapDataFilter.java @@ -0,0 +1,27 @@ +/* + * Copyright 2014 NAVER Corp. + * + * 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 com.navercorp.pinpoint.web.security; + +import com.navercorp.pinpoint.web.vo.Application; + +/** + * @author minwoo.jung + */ +public interface ServerMapDataFilter { + + boolean filter(Application application); + +} diff --git a/web/src/main/java/com/navercorp/pinpoint/web/service/BFSLinkSelector.java b/web/src/main/java/com/navercorp/pinpoint/web/service/BFSLinkSelector.java index 19a6a0253..c8abe46fc 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/service/BFSLinkSelector.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/service/BFSLinkSelector.java @@ -22,6 +22,7 @@ import com.navercorp.pinpoint.web.applicationmap.rawdata.*; import com.navercorp.pinpoint.web.dao.HostApplicationMapDao; import com.navercorp.pinpoint.web.dao.MapStatisticsCalleeDao; import com.navercorp.pinpoint.web.dao.MapStatisticsCallerDao; +import com.navercorp.pinpoint.web.security.ServerMapDataFilter; import com.navercorp.pinpoint.web.service.map.AcceptApplication; import com.navercorp.pinpoint.web.service.map.AcceptApplicationLocalCache; import com.navercorp.pinpoint.web.service.map.RpcApplication; @@ -40,6 +41,7 @@ import java.util.*; * Breadth-first link search * not thread safe * @author emeroad + * @author minwoo.jung */ public class BFSLinkSelector implements LinkSelector { @@ -58,8 +60,10 @@ public class BFSLinkSelector implements LinkSelector { private final Set emulationLinkMarker = new HashSet<>(); private final Queue nextQueue = new Queue(); + + private ServerMapDataFilter serverMapDataFilter; - public BFSLinkSelector(MapStatisticsCallerDao mapStatisticsCallerDao, MapStatisticsCalleeDao mapStatisticsCalleeDao, HostApplicationMapDao hostApplicationMapDao) { + public BFSLinkSelector(MapStatisticsCallerDao mapStatisticsCallerDao, MapStatisticsCalleeDao mapStatisticsCalleeDao, HostApplicationMapDao hostApplicationMapDao, ServerMapDataFilter serverMapDataFilter) { if (mapStatisticsCalleeDao == null) { throw new NullPointerException("mapStatisticsCalleeDao must not be null"); } @@ -72,6 +76,7 @@ public class BFSLinkSelector implements LinkSelector { this.mapStatisticsCalleeDao = mapStatisticsCalleeDao; this.mapStatisticsCallerDao = mapStatisticsCallerDao; this.hostApplicationMapDao = hostApplicationMapDao; + this.serverMapDataFilter = serverMapDataFilter; } /** @@ -115,6 +120,7 @@ public class BFSLinkSelector implements LinkSelector { logger.debug("Found Callee. count={}, callee={}, depth={}", callee.size(), targetApplication, calleeDepth.getDepth()); } for (LinkData stat : callee.getLinkDataList()) { + searchResult.addTargetLinkData(stat); final Application fromApplication = stat.getFromApplication(); @@ -144,6 +150,14 @@ public class BFSLinkSelector implements LinkSelector { return false; } + return filter(targetApplication); + } + + private boolean filter(Application targetApplication) { + if (serverMapDataFilter != null && serverMapDataFilter.filter(targetApplication)) { + return false; + } + return true; } @@ -153,12 +167,13 @@ public class BFSLinkSelector implements LinkSelector { return false; } + if (linkVisitChecker.visitCallee(targetApplication)) { logger.debug("already visited callee:{}", targetApplication); return false; } - return true; + return filter(targetApplication); } diff --git a/web/src/main/java/com/navercorp/pinpoint/web/service/FilteredMapServiceImpl.java b/web/src/main/java/com/navercorp/pinpoint/web/service/FilteredMapServiceImpl.java index 36200cfb9..e62c14f8e 100755 --- a/web/src/main/java/com/navercorp/pinpoint/web/service/FilteredMapServiceImpl.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/service/FilteredMapServiceImpl.java @@ -31,6 +31,7 @@ import com.navercorp.pinpoint.web.applicationmap.rawdata.LinkDataMap; import com.navercorp.pinpoint.web.dao.ApplicationTraceIndexDao; import com.navercorp.pinpoint.web.dao.TraceDao; import com.navercorp.pinpoint.web.filter.Filter; +import com.navercorp.pinpoint.web.security.ServerMapDataFilter; import com.navercorp.pinpoint.web.util.TimeWindow; import com.navercorp.pinpoint.web.util.TimeWindowDownSampler; import com.navercorp.pinpoint.web.vo.Application; @@ -80,6 +81,9 @@ public class FilteredMapServiceImpl implements FilteredMapService { @Autowired private ApplicationFactory applicationFactory; + + @Autowired(required=false) + private ServerMapDataFilter serverMapDataFilter; private static final Object V = new Object(); @@ -338,11 +342,14 @@ public class FilteredMapServiceImpl implements FilteredMapService { targetLinkDataMap.addLinkData(parentApplication, span.getAgentId(), spanApplication, span.getAgentId(), timestamp, slotTime, 1); } - + if (serverMapDataFilter != null && serverMapDataFilter.filter(spanApplication)) { + continue; + } + addNodeFromSpanEvent(span, window, linkDataDuplexMap, transactionSpanMap); } } - + ApplicationMapBuilder applicationMapBuilder = new ApplicationMapBuilder(range); mapHistogramSummary.build(); ApplicationMap map = applicationMapBuilder.build(linkDataDuplexMap, agentInfoService, mapHistogramSummary); diff --git a/web/src/main/java/com/navercorp/pinpoint/web/service/LinkVisitChecker.java b/web/src/main/java/com/navercorp/pinpoint/web/service/LinkVisitChecker.java index 9521c92c6..996b5c570 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/service/LinkVisitChecker.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/service/LinkVisitChecker.java @@ -52,9 +52,9 @@ public class LinkVisitChecker { final boolean alreadyVisited = !visitedSet.add(application); if (logger.isDebugEnabled()) { if (alreadyVisited) { - logger.debug("Finding {}. {}={}", type, type, application); - } else { logger.debug("LinkData exists. Skip finding {}. {} ", type, application); + } else { + logger.debug("Finding {}. {}={}", type, type, application); } } return alreadyVisited; diff --git a/web/src/main/java/com/navercorp/pinpoint/web/service/MapServiceImpl.java b/web/src/main/java/com/navercorp/pinpoint/web/service/MapServiceImpl.java index 3f5db0392..0eb82d58d 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/service/MapServiceImpl.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/service/MapServiceImpl.java @@ -24,6 +24,7 @@ import com.navercorp.pinpoint.web.dao.HostApplicationMapDao; import com.navercorp.pinpoint.web.dao.MapResponseDao; import com.navercorp.pinpoint.web.dao.MapStatisticsCalleeDao; import com.navercorp.pinpoint.web.dao.MapStatisticsCallerDao; +import com.navercorp.pinpoint.web.security.ServerMapDataFilter; import com.navercorp.pinpoint.web.view.ApplicationTimeHistogramViewModel; import com.navercorp.pinpoint.web.vo.Application; import com.navercorp.pinpoint.web.vo.Range; @@ -64,6 +65,9 @@ public class MapServiceImpl implements MapService { @Autowired private ApplicationFactory applicationFactory; + + @Autowired(required=false) + private ServerMapDataFilter serverMapDataFilter; /** * Used in the main UI - draws the server map by querying the timeslot by time. @@ -81,7 +85,7 @@ public class MapServiceImpl implements MapService { StopWatch watch = new StopWatch("ApplicationMap"); watch.start("ApplicationMap Hbase Io Fetch(Caller,Callee) Time"); - LinkSelector linkSelector = new BFSLinkSelector(this.mapStatisticsCallerDao, this.mapStatisticsCalleeDao, hostApplicationMapDao); + LinkSelector linkSelector = new BFSLinkSelector(this.mapStatisticsCallerDao, this.mapStatisticsCalleeDao, hostApplicationMapDao, serverMapDataFilter); LinkDataDuplexMap linkDataDuplexMap = linkSelector.select(sourceApplication, range, searchOption); watch.stop(); diff --git a/web/src/main/java/com/navercorp/pinpoint/web/service/SpanServiceImpl.java b/web/src/main/java/com/navercorp/pinpoint/web/service/SpanServiceImpl.java index cee5153e1..081279279 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/service/SpanServiceImpl.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/service/SpanServiceImpl.java @@ -39,6 +39,8 @@ import com.navercorp.pinpoint.web.dao.ApiMetaDataDao; import com.navercorp.pinpoint.web.dao.SqlMetaDataDao; import com.navercorp.pinpoint.web.dao.StringMetaDataDao; import com.navercorp.pinpoint.web.dao.TraceDao; +import com.navercorp.pinpoint.web.security.MetaDataFilter; +import com.navercorp.pinpoint.web.security.MetaDataFilter.MetaData; import com.navercorp.pinpoint.web.vo.TransactionId; import org.apache.commons.lang3.StringUtils; @@ -49,6 +51,7 @@ import org.springframework.beans.factory.annotation.Autowired; /** * @author emeroad * @author jaehong.kim + * @author minwoo.jung */ //@Service public class SpanServiceImpl implements SpanService { @@ -60,6 +63,9 @@ public class SpanServiceImpl implements SpanService { // @Autowired private SqlMetaDataDao sqlMetaDataDao; + + @Autowired(required=false) + private MetaDataFilter metaDataFilter; @Autowired private ApiMetaDataDao apiMetaDataDao; @@ -124,6 +130,11 @@ public class SpanServiceImpl implements SpanService { if (sqlIdAnnotation == null) { return; } + if (metaDataFilter != null && metaDataFilter.filter(spanAlign, MetaData.SQL)) { + AnnotationBo annotationBo = metaDataFilter.createAnnotationBo(spanAlign, MetaData.SQL); + annotationBoList.add(annotationBo); + return; + } final AgentKey agentKey = getAgentKey(spanAlign); diff --git a/web/src/main/java/com/navercorp/pinpoint/web/service/TransactionInfoServiceImpl.java b/web/src/main/java/com/navercorp/pinpoint/web/service/TransactionInfoServiceImpl.java index 1a3b16af7..139012219 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/service/TransactionInfoServiceImpl.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/service/TransactionInfoServiceImpl.java @@ -33,6 +33,8 @@ import com.navercorp.pinpoint.web.calltree.span.CallTreeNode; import com.navercorp.pinpoint.web.calltree.span.SpanAlign; import com.navercorp.pinpoint.web.dao.TraceDao; import com.navercorp.pinpoint.web.filter.Filter; +import com.navercorp.pinpoint.web.security.MetaDataFilter; +import com.navercorp.pinpoint.web.security.MetaDataFilter.MetaData; import com.navercorp.pinpoint.web.vo.BusinessTransactions; import com.navercorp.pinpoint.web.vo.Range; import com.navercorp.pinpoint.web.vo.TransactionId; @@ -65,6 +67,9 @@ public class TransactionInfoServiceImpl implements TransactionInfoService { @Autowired private AnnotationKeyRegistryService annotationKeyRegistryService; + + @Autowired(required=false) + private MetaDataFilter metaDataFilter; // Temporarily disabled Because We need to solve authentication problem inter system. // @Value("#{pinpointWebProps['log.enable'] ?: false}") @@ -254,7 +259,7 @@ public class TransactionInfoServiceImpl implements TransactionInfoService { firstSpan = spanBo; } } - } + }; // return firstSpan when focus Span could not be found. return firstSpan; } @@ -322,6 +327,19 @@ public class TransactionInfoServiceImpl implements TransactionInfoService { throw new IllegalStateException("CallTree corrupted"); } final SpanAlign align = node.getValue(); + + if (metaDataFilter != null && metaDataFilter.filter(align, MetaData.API)) { + if (align.isSpan()) { + Record record = metaDataFilter.createRecord(node, factory); + recordList.add(record); + } + continue; + } + + if (metaDataFilter != null && metaDataFilter.filter(align, MetaData.PARAM)) { + metaDataFilter.replaceAnnotationBo(align, MetaData.PARAM); + } + final String argument = getArgument(align); final Record record = factory.get(node, argument); recordList.add(record); @@ -334,6 +352,7 @@ public class TransactionInfoServiceImpl implements TransactionInfoService { } } + // add annotation record. if(!align.getAnnotationBoList().isEmpty()) { final List annotations = factory.getAnnotations(record.getTab() + 1, record.getId(), align); diff --git a/web/src/main/java/com/navercorp/pinpoint/web/view/LinkSerializer.java b/web/src/main/java/com/navercorp/pinpoint/web/view/LinkSerializer.java index 383a1886d..7efe48b91 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/view/LinkSerializer.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/view/LinkSerializer.java @@ -16,12 +16,14 @@ package com.navercorp.pinpoint.web.view; +import com.navercorp.pinpoint.common.trace.ServiceType; import com.navercorp.pinpoint.web.applicationmap.Link; import com.navercorp.pinpoint.web.applicationmap.Node; import com.navercorp.pinpoint.web.applicationmap.ServerInstanceList; import com.navercorp.pinpoint.web.applicationmap.histogram.Histogram; import com.navercorp.pinpoint.web.applicationmap.rawdata.AgentHistogram; import com.navercorp.pinpoint.web.applicationmap.rawdata.AgentHistogramList; +import com.navercorp.pinpoint.web.security.ServerMapDataFilter; import com.navercorp.pinpoint.web.vo.Application; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; @@ -32,33 +34,40 @@ import java.io.IOException; import java.util.Collection; import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; + /** * @author emeroad * @author netspider */ public class LinkSerializer extends JsonSerializer { + + @Autowired(required=false) + private ServerMapDataFilter serverMapDataFilter; + @Override public void serialize(Link link, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + final boolean isAuthFromApp = check(link.getFrom().getApplication()); + final boolean isAuthToApp = check(link.getTo().getApplication()); + jgen.writeStartObject(); - jgen.writeStringField("key", link.getLinkName()); // for servermap + jgen.writeStringField("key", link.getLinkName(isAuthFromApp, isAuthToApp)); // for servermap - jgen.writeStringField("from", link.getFrom().getNodeName()); // necessary for go.js - jgen.writeStringField("to", link.getTo().getNodeName()); // necessary for go.js + jgen.writeStringField("from", link.getFrom().getNodeName(isAuthFromApp)); // necessary for go.js + jgen.writeStringField("to", link.getTo().getNodeName(isAuthToApp)); // necessary for go.js // for FilterWizard. from, to agent mapping data - writeAgentId("fromAgent", link.getFrom(), jgen); - writeAgentId("toAgent", link.getTo(), jgen); + writeAgentId("fromAgent", link.getFrom(), jgen, isAuthFromApp); + writeAgentId("toAgent", link.getTo(), jgen, isAuthToApp); - writeSimpleNode("sourceInfo", link.getFrom(), jgen); - writeSimpleNode("targetInfo", link.getTo(), jgen); + writeSimpleNode("sourceInfo", link.getFrom(), jgen, isAuthFromApp); + writeSimpleNode("targetInfo", link.getTo(), jgen, isAuthToApp); - Application filterApplication = link.getFilterApplication(); - jgen.writeStringField("filterApplicationName", filterApplication.getName()); - jgen.writeNumberField("filterApplicationServiceTypeCode", filterApplication.getServiceTypeCode()); - jgen.writeStringField("filterApplicationServiceTypeName", filterApplication.getServiceType().getName()); + writeFilterApplicationInfo(link, jgen); + if (link.isWasToWasLink()) { - writeWasToWasTargetRpcList(link, jgen); + writeWasToWasTargetRpcList(link, jgen, isAuthToApp); } Histogram histogram = link.getHistogram(); @@ -69,11 +78,11 @@ public class LinkSerializer extends JsonSerializer { jgen.writeObjectField("histogram", histogram); // data showing how agents call each of their respective links - writeAgentHistogram("sourceHistogram", link.getSourceList(), jgen); - writeAgentHistogram("targetHistogram", link.getTargetList(), jgen); + writeAgentHistogram("sourceHistogram", link.getSourceList(), jgen, isAuthFromApp); + writeAgentHistogram("targetHistogram", link.getTargetList(), jgen, isAuthToApp); writeTimeSeriesHistogram(link, jgen); - writeSourceAgentTimeSeriesHistogram(link, jgen); + writeSourceAgentTimeSeriesHistogram(link, jgen, isAuthFromApp); // String state = link.getLinkState(); @@ -82,31 +91,61 @@ public class LinkSerializer extends JsonSerializer { jgen.writeEndObject(); } + + private void writeFilterApplicationInfo(Link link, JsonGenerator jgen) throws IOException { + Application application = link.getFilterApplication(); + jgen.writeStringField("filterApplicationName", application.getName()); + if (check(application)) { + jgen.writeNumberField("filterApplicationServiceTypeCode", application.getServiceTypeCode()); + jgen.writeStringField("filterApplicationServiceTypeName", application.getServiceType().getName()); + } else { + jgen.writeNumberField("filterApplicationServiceTypeCode", ServiceType.UNAUTHORIZED.getCode()); + jgen.writeStringField("filterApplicationServiceTypeName", ServiceType.UNAUTHORIZED.getName()); + } + } - private void writeAgentId(String fieldName, Node node, JsonGenerator jgen) throws IOException { + private boolean check(Application application) { + if (serverMapDataFilter != null && serverMapDataFilter.filter(application)) { + return false; + } + return true; + } + + private void writeAgentId(String fieldName, Node node, JsonGenerator jgen, boolean isAuthorized) throws IOException { if (node.getServiceType().isWas()) { jgen.writeFieldName(fieldName); jgen.writeStartArray(); - ServerInstanceList serverInstanceList = node.getServerInstanceList(); - if (serverInstanceList!= null) { - for (String agentId : serverInstanceList.getAgentIdList()) { - jgen.writeObject(agentId); + if (isAuthorized) { + ServerInstanceList serverInstanceList = node.getServerInstanceList(); + if (serverInstanceList!= null) { + for (String agentId : serverInstanceList.getAgentIdList()) { + jgen.writeObject(agentId); + } } + } else { + jgen.writeString("UNKNOWN_AGENT"); } jgen.writeEndArray(); } } - private void writeWasToWasTargetRpcList(Link link, JsonGenerator jgen) throws IOException { + private void writeWasToWasTargetRpcList(Link link, JsonGenerator jgen, boolean isAuthorized) throws IOException { // write additional information to be used for filtering failed WAS -> WAS call events. jgen.writeFieldName("filterTargetRpcList"); jgen.writeStartArray(); - Collection sourceLinkTargetAgentList = link.getSourceLinkTargetAgentList(); - for (Application application : sourceLinkTargetAgentList) { - jgen.writeStartObject(); - jgen.writeStringField("rpc", application.getName()); - jgen.writeNumberField("rpcServiceTypeCode", application.getServiceTypeCode()); - jgen.writeEndObject(); + if (isAuthorized) { + Collection sourceLinkTargetAgentList = link.getSourceLinkTargetAgentList(); + for (Application application : sourceLinkTargetAgentList) { + jgen.writeStartObject(); + jgen.writeStringField("rpc", application.getName()); + if (check(application)) { + jgen.writeNumberField("rpcServiceTypeCode", application.getServiceTypeCode()); + } + else { + + } + jgen.writeEndObject(); + } } jgen.writeEndArray(); @@ -122,30 +161,50 @@ public class LinkSerializer extends JsonSerializer { } - private void writeAgentHistogram(String fieldName, AgentHistogramList agentHistogramList, JsonGenerator jgen) throws IOException { + private void writeAgentHistogram(String fieldName, AgentHistogramList agentHistogramList, JsonGenerator jgen, boolean isAuthorized) throws IOException { jgen.writeFieldName(fieldName); jgen.writeStartObject(); - for (AgentHistogram agentHistogram : agentHistogramList.getAgentHistogramList()) { - jgen.writeFieldName(agentHistogram.getId()); - jgen.writeObject(agentHistogram.getHistogram()); + if (isAuthorized) { + for (AgentHistogram agentHistogram : agentHistogramList.getAgentHistogramList()) { + jgen.writeFieldName(agentHistogram.getId()); + jgen.writeObject(agentHistogram.getHistogram()); + } + } else { + AgentHistogram mergeHistogram = new AgentHistogram(new Application("UNKONWN_AGENT", ServiceType.UNAUTHORIZED)); + for (AgentHistogram agentHistogram : agentHistogramList.getAgentHistogramList()) { + mergeHistogram.addTimeHistogram(agentHistogram.getTimeHistogram()); + } + jgen.writeFieldName(mergeHistogram.getId()); + jgen.writeObject(mergeHistogram.getHistogram()); } jgen.writeEndObject(); } - private void writeSourceAgentTimeSeriesHistogram(Link link, JsonGenerator jgen) throws IOException { + private void writeSourceAgentTimeSeriesHistogram(Link link, JsonGenerator jgen, boolean isAuthorized) throws IOException { AgentResponseTimeViewModelList sourceAgentTimeSeriesHistogram = link.getSourceAgentTimeSeriesHistogram(); sourceAgentTimeSeriesHistogram.setFieldName("sourceTimeSeriesHistogram"); - jgen.writeObject(sourceAgentTimeSeriesHistogram); + if (isAuthorized) { + jgen.writeObject(sourceAgentTimeSeriesHistogram); + } else { + jgen.writeFieldName("sourceTimeSeriesHistogram"); + jgen.writeStartObject(); + jgen.writeEndObject(); + } } - private void writeSimpleNode(String fieldName, Node node, JsonGenerator jgen) throws IOException { + private void writeSimpleNode(String fieldName, Node node, JsonGenerator jgen, boolean isAuthorized) throws IOException { jgen.writeFieldName(fieldName); jgen.writeStartObject(); Application application = node.getApplication(); jgen.writeStringField("applicationName", application.getName()); - jgen.writeStringField("serviceType", application.getServiceType().toString()); - jgen.writeNumberField("serviceTypeCode", application.getServiceTypeCode()); + if (isAuthorized) { + jgen.writeStringField("serviceType", application.getServiceType().toString()); + jgen.writeNumberField("serviceTypeCode", application.getServiceTypeCode()); + } else { + jgen.writeStringField("serviceType", ServiceType.UNAUTHORIZED.toString()); + jgen.writeNumberField("serviceTypeCode", ServiceType.UNAUTHORIZED.getCode()); + } jgen.writeBooleanField("isWas", application.getServiceType().isWas()); jgen.writeEndObject(); } diff --git a/web/src/main/java/com/navercorp/pinpoint/web/view/NodeSerializer.java b/web/src/main/java/com/navercorp/pinpoint/web/view/NodeSerializer.java index 63f06a462..c825864f6 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/view/NodeSerializer.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/view/NodeSerializer.java @@ -21,6 +21,8 @@ import com.navercorp.pinpoint.web.applicationmap.Node; import com.navercorp.pinpoint.web.applicationmap.ServerInstanceList; import com.navercorp.pinpoint.web.applicationmap.histogram.Histogram; import com.navercorp.pinpoint.web.applicationmap.histogram.NodeHistogram; +import com.navercorp.pinpoint.web.security.ServerMapDataFilter; +import com.navercorp.pinpoint.web.vo.Application; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; @@ -30,22 +32,28 @@ import java.io.IOException; import java.util.List; import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; + /** * @author emeroad + * @author minwoo.jung */ public class NodeSerializer extends JsonSerializer { + + @Autowired(required=false) + private ServerMapDataFilter serverMapDataFilter; + @Override public void serialize(Node node, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + final boolean isAuthorized = check(node.getApplication()); + jgen.writeStartObject(); // jgen.writeStringField("id", node.getNodeName()); - jgen.writeStringField("key", node.getNodeName()); // necessary for go.js + jgen.writeStringField("key", node.getNodeName(isAuthorized)); // necessary for go.js jgen.writeStringField("applicationName", node.getApplicationTextName()); // for go.js - - jgen.writeStringField("category", node.getServiceType().toString()); // necessary for go.js - jgen.writeStringField("serviceType", node.getServiceType().toString()); - - final ServiceType serviceType = node.getApplication().getServiceType(); + jgen.writeBooleanField("isAuthorized", isAuthorized); + writeServiceTypeInfo(jgen, node, isAuthorized); // if (serviceType.isUser()) { // jgen.writeStringField("fig", "Ellipse"); // } else if(serviceType.isWas()) { @@ -54,14 +62,18 @@ public class NodeSerializer extends JsonSerializer { // jgen.writeStringField("fig", "Rectangle"); // } - jgen.writeStringField("serviceTypeCode", Short.toString(serviceType.getCode())); // jgen.writeStringField("terminal", Boolean.toString(serviceType.isTerminal())); - jgen.writeBooleanField("isWas", serviceType.isWas()); // for go.js - jgen.writeBooleanField("isQueue", serviceType.isQueue()); + writeHistogram(jgen, node, isAuthorized); + writeServerList(jgen, node, isAuthorized); - - - writeHistogram(jgen, node); + jgen.writeEndObject(); + } + + private void writeServerList(JsonGenerator jgen, Node node, boolean isAuthorized) throws IOException { + if (isAuthorized == false) { + return; + } + if (node.getServiceType().isUnknown()) { writeEmptyObject(jgen, "serverList"); jgen.writeNumberField("instanceCount", 0); @@ -75,11 +87,28 @@ public class NodeSerializer extends JsonSerializer { jgen.writeNumberField("instanceCount", 0); } } + } + + private void writeServiceTypeInfo(JsonGenerator jgen, Node node, boolean isAuthorized) throws IOException { + if (isAuthorized) { + jgen.writeStringField("category", node.getServiceType().toString()); // necessary for go.js + jgen.writeStringField("serviceType", node.getServiceType().toString()); + jgen.writeStringField("serviceTypeCode", Short.toString(node.getServiceType().getCode())); + } else { + jgen.writeStringField("category", ServiceType.UNAUTHORIZED.toString()); // necessary for go.js + jgen.writeStringField("serviceType", ServiceType.UNAUTHORIZED.toString()); + jgen.writeStringField("serviceTypeCode", Short.toString(ServiceType.UNAUTHORIZED.getCode())); + } - jgen.writeEndObject(); + jgen.writeBooleanField("isWas", node.getServiceType().isWas()); // for go.js + jgen.writeBooleanField("isQueue", node.getServiceType().isQueue()); } - private void writeHistogram(JsonGenerator jgen, Node node) throws IOException { + private void writeHistogram(JsonGenerator jgen, Node node, boolean isAuthorized) throws IOException { + if (isAuthorized == false) { + return; + } + final ServiceType serviceType = node.getServiceType(); final NodeHistogram nodeHistogram = node.getNodeHistogram(); // FIXME isn't this all ServiceTypes that can be a node? @@ -107,7 +136,8 @@ public class NodeSerializer extends JsonSerializer { } Map agentHistogramMap = nodeHistogram.getAgentHistogramMap(); - if(agentHistogramMap == null) { + + if(agentHistogramMap == null || isAuthorized) { writeEmptyObject(jgen, "agentHistogram"); } else { jgen.writeObjectField("agentHistogram", agentHistogramMap); @@ -123,7 +153,6 @@ public class NodeSerializer extends JsonSerializer { } else { jgen.writeObjectField("timeSeriesHistogram", applicationTimeSeriesHistogram); } - AgentResponseTimeViewModelList agentTimeSeriesHistogram = nodeHistogram.getAgentTimeHistogram(); jgen.writeObject(agentTimeSeriesHistogram); } @@ -140,6 +169,13 @@ public class NodeSerializer extends JsonSerializer { jgen.writeStartObject(); jgen.writeEndObject(); } + + private boolean check(Application application) { + if (serverMapDataFilter != null && serverMapDataFilter.filter(application)) { + return false; + } + return true; + } } diff --git a/web/src/main/java/com/navercorp/pinpoint/web/view/TransactionInfoCallStackSerializer.java b/web/src/main/java/com/navercorp/pinpoint/web/view/TransactionInfoCallStackSerializer.java index eb5788f52..2b2e8654b 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/view/TransactionInfoCallStackSerializer.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/view/TransactionInfoCallStackSerializer.java @@ -53,8 +53,7 @@ public class TransactionInfoCallStackSerializer extends JsonSerializer 0) { - try { - URL url = new URL(record.getLogPageUrl()); - StringBuilder sb = new StringBuilder(); - sb.append("transactionId=").append(record.getTransactionId()); - sb.append("&spanId=").append(record.getSpanId()); - sb.append("&time=").append(record.getBegin()); - if (url.getQuery() != null) { - logPageUrl = record.getLogPageUrl() + "&" + sb.toString(); - } else { - logPageUrl = record.getLogPageUrl() + "?" + sb.toString(); - } - } catch (MalformedURLException ignored) { - } - } + isAuthorized = record.isAuthorized(); } public String getDepth() { @@ -366,13 +354,9 @@ public class TransactionInfoViewModel { public boolean isHasException() { return hasException; } - - public String getLogButtonName() { - return logButtonName; - } - - public String getLogPageUrl() { - return logPageUrl; + + public boolean isAuthorized() { + return isAuthorized; } } } \ No newline at end of file diff --git a/web/src/main/java/com/navercorp/pinpoint/web/vo/callstacks/Record.java b/web/src/main/java/com/navercorp/pinpoint/web/vo/callstacks/Record.java index 4ff89bf30..f9ac471bb 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/vo/callstacks/Record.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/vo/callstacks/Record.java @@ -24,6 +24,7 @@ import com.navercorp.pinpoint.common.trace.ServiceType; * @author netspider * @author emeroad * @author jaehong.kim + * @author minwoo.jung */ public class Record { private final int tab; @@ -52,11 +53,10 @@ public class Record { private boolean focused; private boolean hasChild; private boolean hasException; - private String logPageUrl; - private String logButtonName; private int methodType; + private boolean isAuthorized; - public Record(int tab, int id, int parentId, boolean method, String title, String arguments, long begin, long elapsed, long gap, String agent, String applicationName, ServiceType serviceType, String destinationId, boolean hasChild, boolean hasException, String transactionId, long spanId, long executionMilliseconds, int methodType) { + public Record(int tab, int id, int parentId, boolean method, String title, String arguments, long begin, long elapsed, long gap, String agent, String applicationName, ServiceType serviceType, String destinationId, boolean hasChild, boolean hasException, String transactionId, long spanId, long executionMilliseconds, int methodType, boolean isAuthorized) { this.tab = tab; this.id = id; this.parentId = parentId; @@ -82,6 +82,7 @@ public class Record { this.executionMilliseconds = executionMilliseconds; this.methodType = methodType; + this.isAuthorized = isAuthorized; } public int getId() { @@ -198,22 +199,6 @@ public class Record { return spanId; } - public void setLogPageUrl(String logPageUrl) { - this.logPageUrl = logPageUrl; - } - - public void setLogButtonName(String logButtonName) { - this.logButtonName = logButtonName; - } - - public String getLogPageUrl() { - return this.logPageUrl; - } - - public String getLogButtonName() { - return this.logButtonName; - } - public long getExecutionMilliseconds() { return executionMilliseconds; } @@ -221,6 +206,10 @@ public class Record { public int getMethodType() { return methodType; } + + public boolean isAuthorized() { + return this.isAuthorized; + } @Override public String toString() { @@ -269,12 +258,10 @@ public class Record { builder.append(hasChild); builder.append(", hasException="); builder.append(hasException); - builder.append(", logPageUrl="); - builder.append(logPageUrl); - builder.append(", logButtonName="); - builder.append(logButtonName); builder.append(", methodType="); builder.append(methodType); + builder.append(", isAuthorized="); + builder.append(isAuthorized); builder.append("}"); return builder.toString(); } diff --git a/web/src/main/java/com/navercorp/pinpoint/web/vo/callstacks/RecordFactory.java b/web/src/main/java/com/navercorp/pinpoint/web/vo/callstacks/RecordFactory.java index c3cb5eef0..ac9579975 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/vo/callstacks/RecordFactory.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/vo/callstacks/RecordFactory.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 NAVER Corp. + * + * 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 com.navercorp.pinpoint.web.vo.callstacks; import java.util.ArrayList; @@ -8,6 +23,7 @@ import com.navercorp.pinpoint.common.server.bo.ApiMetaDataBo; import com.navercorp.pinpoint.common.service.AnnotationKeyRegistryService; import com.navercorp.pinpoint.common.service.ServiceTypeRegistryService; import com.navercorp.pinpoint.common.trace.AnnotationKey; +import com.navercorp.pinpoint.common.trace.ServiceType; import com.navercorp.pinpoint.common.server.util.AnnotationUtils; import com.navercorp.pinpoint.common.util.ApiDescription; import com.navercorp.pinpoint.common.server.util.ApiDescriptionParser; @@ -16,6 +32,10 @@ import com.navercorp.pinpoint.web.calltree.span.SpanAlign; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +/** + * @author minwoo.jung + */ public class RecordFactory { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @@ -55,13 +75,45 @@ public class RecordFactory { align.getTransactionId(), align.getSpanId(), align.getExecutionMilliseconds(), - api.type); + api.type, + true); record.setSimpleClassName(api.className); record.setFullApiDescription(api.description); return record; } + public Record getFilteredRecord(final CallTreeNode node, String apiTitle) { + final SpanAlign align = node.getValue(); + align.setId(getNextId()); + + final int parentId = getParentId(node); + Api api = getApi(align); + + final Record record = new Record(align.getDepth(), + align.getId(), + parentId, + true, + apiTitle, + "", + align.getStartTime(), + align.getElapsed(), + align.getGap(), + "UNKNOWN", + align.getApplicationId(), + ServiceType.UNKNOWN, + "", + false, + false, + align.getTransactionId(), + align.getSpanId(), + align.getExecutionMilliseconds(), + 0, + false); + + return record; + } + public Record getException(final int depth, final int parentId, final SpanAlign align) { if(!align.hasException()) { return null; @@ -77,7 +129,8 @@ public class RecordFactory { align.getTransactionId(), align.getSpanId(), align.getExecutionMilliseconds(), - 0); + 0, + true); return record; } @@ -105,7 +158,7 @@ public class RecordFactory { false, key.getName(), annotation.getValue().toString(), - 0L, 0L, 0, null, null, null, null, false, false, null, 0, 0, 0); + 0L, 0L, 0, null, null, null, null, false, false, null, 0, 0, 0, annotation.isAuthorized()); list.add(record); } } @@ -120,7 +173,7 @@ public class RecordFactory { false, method, argument, - 0L, 0L, 0, null, null, null, null, false, false, null, 0, 0, 0); + 0L, 0L, 0, null, null, null, null, false, false, null, 0, 0, 0, true); } diff --git a/web/src/main/resources/pinpoint-web.properties b/web/src/main/resources/pinpoint-web.properties index 285eca761..12fce4b25 100644 --- a/web/src/main/resources/pinpoint-web.properties +++ b/web/src/main/resources/pinpoint-web.properties @@ -1,3 +1,4 @@ +<<<<<<< Upstream, based on remote/master # ------------------------------------------------------------------------------------------------- # The cluster related options are used to establish connections between the agent, collector, and web in order to send/receive data between them in real time. # You may enable additional features using this option (Ex : RealTime Active Thread Chart). @@ -12,6 +13,10 @@ #2. We recommend using the cluster.web.tcp.port option. However, in cases where the collector is unable to establish connection to the web, you may reverse this and make the web establish connection to the collector. # In this case, you must set cluster.connect.address (pinpoint-web.properties); and cluster.listen.ip, cluster.listen.port (pinpoint-collector.properties) accordingly. cluster.enable=true +======= +#local +cluster.enable=false +>>>>>>> d039eb5 [#639] add data filter logic cluster.web.tcp.port=9997 cluster.zookeeper.address=localhost cluster.zookeeper.sessiontimeout=30000 diff --git a/web/src/main/webapp/images/icons/UNAUTHORIZED.png b/web/src/main/webapp/images/icons/UNAUTHORIZED.png new file mode 100644 index 0000000000000000000000000000000000000000..e56982ae3ddb4649d05ad14ad0998dbc0c8b310c GIT binary patch literal 49941 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
    #uETNv zS;S(6jbzOg8DJyRrM(AY;vJ~VPt|e}(VnJuF<;07{=lkrMIrUZN>ID9E?hWj>k7KQ z{mJ(*Fvgmd$V2IUPQstDt( zj|accl~9yuL~#th�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