arcgissamples\soi\LayerAccessSOI.java
/* Copyright 2015 ESRI * * All rights reserved under the copyright laws of the United States * and applicable international laws, treaties, and conventions. * * You may freely redistribute and use this sample code, with or * without modification, provided you include the original copyright * notice and use restrictions. * * See the use restrictions at <your ArcGIS install location>/DeveloperKit10.4/userestrictions.txt. * */ package arcgissamples.soi; /* * COPYRIGHT 1995-2014 ESRI TRADE SECRETS: ESRI PROPRIETARY AND CONFIDENTIAL Unpublished material - * all rights reserved under the Copyright Laws of the United States and applicable international * laws, treaties, and conventions. * * For additional information, contact: Environmental Systems Research Institute, Inc. Attn: * Contracts and Legal Services Department 380 New York Street Redlands, California, 92373 USA * * email: contracts@esri.com */ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.esri.arcgis.carto.ILayerDescription; import com.esri.arcgis.carto.ILayerDescriptions; import com.esri.arcgis.carto.IMapLayerInfos; import com.esri.arcgis.carto.MapDescription; import com.esri.arcgis.carto.MapServer; import com.esri.arcgis.carto.MapServerInfo; import com.esri.arcgis.interop.AutomationException; import com.esri.arcgis.interop.extn.ArcGISExtension; import com.esri.arcgis.interop.extn.ServerObjectExtProperties; import com.esri.arcgis.server.IServerObject; import com.esri.arcgis.server.IServerObjectExtension; import com.esri.arcgis.server.IServerObjectHelper; import com.esri.arcgis.server.SOIHelper; import com.esri.arcgis.server.json.JSONArray; import com.esri.arcgis.server.json.JSONObject; import com.esri.arcgis.system.IEnumBSTR; import com.esri.arcgis.system.ILog; import com.esri.arcgis.system.IMessage; import com.esri.arcgis.system.IRESTRequestHandler; import com.esri.arcgis.system.IRequestHandler; import com.esri.arcgis.system.IRequestHandler2; import com.esri.arcgis.system.IServerUserInfo; import com.esri.arcgis.system.IWebRequestHandler; import com.esri.arcgis.system.IXMLSerializeData; import com.esri.arcgis.system.LongArray; import com.esri.arcgis.system.Message; import com.esri.arcgis.system.ServerUtilities; /* * For an SOE to act as in intercepter, it needs to implement all request handler interfaces * IRESTRequestHandler, IWebRequestHandler, IRequestHandler2, IRequestHandler now the SOE/SOI can * intercept all types of calls to ArcObjects or custom SOEs * * This example demonstrates the power of SOI's. We can log all requests to ArcObjects or custom * SOEs and even control access to different map layers based on user roles. In this example we use * the permission.json file (stores authorized layers for each user role). At runtime we use this * information to control access to different layers. Map Service operations; export, identify and * find are configured to honor layer level access. * * Note: * * 1: Only REST handler calls are being intercepted to provide layer level access. All other handler * need to be implemented. * * 2: This example only implements layer level access on Map Service operations. Image Service * operations need to be implemented. * * 3: This example is incomplete and is intended to teach patters to writing SOIs and is not designed * to work with custom SOE's or with service capabilities (for e.g. Mobile Data Access etc.) enabled. */ @ArcGISExtension @ServerObjectExtProperties(displayName = "Layer Access SOI", description = "SOI to control access to different layers", interceptor = true, servicetype = "MapService") public class LayerAccessSOI implements IServerObjectExtension, IRESTRequestHandler, IWebRequestHandler, IRequestHandler2, IRequestHandler { private static final String ARCGISHOME_ENV = "AGSSERVER"; private static final long serialVersionUID = 1L; private ILog serverLog; private IServerObject so; private SOIHelper soiHelper; /* * Map used to store permission information. Permission rules for each service is read form the * permisson.json file. */ private Map<String, String> servicePermissionMap = null; public LayerAccessSOI() throws Exception { super(); } /** * init() is called once, when the instance of the SOE/SOI is created. * * @param soh the IServerObjectHelper * @throws IOException Signals that an I/O exception has occurred. * @throws AutomationException the automation exception */ public void init(IServerObjectHelper soh) throws IOException, AutomationException { /* * An SOE should retrieve a weak reference to the Server Object from the Server Object Helper in * order to make any method calls on the Server Object and release the reference after making * the method calls. */ this.serverLog = ServerUtilities.getServerLogger(); String arcgisHome = getArcGISHomeDir(); /* Still null - throw exception */ if(arcgisHome == null) { serverLog.addMessage(1, 200,"Could not get ArcGIS home directory. Check if environment variable " + ARCGISHOME_ENV + " is set."); throw new IOException("Could not get ArcGIS home directory. Check if environment variable " + ARCGISHOME_ENV + " is set."); } if(arcgisHome != null && !arcgisHome.endsWith(File.separator)) { arcgisHome += File.separator; } // Set Log Level to 4 to log the detailed message this.serverLog.addMessage(4, 200,"ArcGIS home directory: " + arcgisHome); this.so = soh.getServerObject(); //Load the SOI helper. this.soiHelper = new SOIHelper(arcgisHome + "XmlSchema" + File.separator + "MapServer.wsdl"); getPermissionFromFile(this.so); this.serverLog.addMessage(3, 200, "Initialized " + this.getClass().getName() + " SOE."); } /** * This method is called to handle REST requests. * * SOEs allow the user to extend base functionality for ArvGIS Map Services and Image Services. * For a <b>Map Service</b> the supported REST operations are: find, identify, export. For an * <b>Image Service</b> the supported REST operations are: identify, export, etc. * * In this example we demonstrate layer level access on REST operators for a Map Service. * Authorized layers for each user role is read from the permission.json file. When user queries a * particular operation, the request is manipulated to allow information retrieval only from * authorized layers. * * Note: This example only implements layer level access on Map Service operations. Image Service * operations need to be implemented. * * @param capabilities the capabilities * @param resourceName the resource name * @param operationName the operation name * @param operationInput the operation input * @param outputFormat the output format * @param requestProperties the request properties * @param responseProperties the response properties * @return the response as byte[] * @throws IOException Signals that an I/O exception has occurred. * @throws AutomationException the automation exception */ @Override public byte[] handleRESTRequest(String capabilities, String resourceName, String operationName, String operationInput, String outputFormat, String requestProperties, String[] responseProperties) throws IOException, AutomationException { /* * Log message with server. */ /* * You can use different log codes to set up different log levels. * * For example: * Use log level 1 for Error Messages. * Use log level 2 for Warning Messages. * Use log level 3 for Normal Messages. * Use log level 4 for Detailed Messages. * Use log level 5 for Debug Messages. * * Note: See http://resources.arcgis.com/en/help/arcobjects-java/api/arcobjects/com/esri/arcgis/system/ILog.html for more information. */ serverLog.addMessage(3, 200, "Request received in Layer Access SOI for handleRESTRequest"); serverLog.addMessage(3, 200, "capabilities - " + capabilities); serverLog.addMessage(3, 200, "resourceName - " + resourceName); serverLog.addMessage(3, 200, "operationName - " + operationName); serverLog.addMessage(3, 200, "operationInput - " + operationInput); serverLog.addMessage(3, 200, "outputFormat - " + outputFormat); serverLog.addMessage(3, 200, "requestProperties - " + requestProperties); /* * Perform layer level access on REST operations. * * Note: When resourceName, operationName and operationInput are empty its the Map Service * resource (or getInfo) call */ try { /* * Get roles for the user making the request. */ Set<String> userRoleSet = getRoleInformation(); /* * Generate a set of authorized layers for the user */ HashSet<String> authorizedLayerSet = null; String authorizedLayers = ""; { for (String role : userRoleSet) { if (servicePermissionMap.containsKey(so.getConfigurationName() + "." + so.getTypeName() + "." + role)) { authorizedLayers += "," + servicePermissionMap.get(so.getConfigurationName() + "." + so.getTypeName() + "." + role); } } // remove spaces authorizedLayers = authorizedLayers.replaceAll("\\s+", ""); // Create a set of authorized layers List<String> authorizedLayerList = null; try { authorizedLayerList = Arrays.asList(authorizedLayers.split(",")); authorizedLayerSet = new HashSet<String>(authorizedLayerList); } catch (Exception ignore) { } // remove blank layer form set authorizedLayerSet.remove(""); authorizedLayers = authorizedLayerSet.toString().replaceAll("\\[|]|\\s+",""); } /* * Convert input parameters to JSON for easier manipulation. */ JSONObject operationInputJson = new JSONObject(operationInput); switch (RESTOperations.get(operationName)) { case EXPORT_MAP: { /* * Manipulate request parameters to control access to different layers. * * Syntax to specify which layers appear on the exported map: * "[show | hide | include | exclude]:layerId1,layerId2" * * example: "show:1,2" * * Here we replace user requested layers with layers he has access to achieve layer level * security. */ if(authorizedLayers.isEmpty()) { return new JSONObject().put("error", new JSONObject().put("code", 404).put("message", "Not Found")) .toString().getBytes(); } operationInputJson.put("layers", "show:" + authorizedLayers); } break; case GENERATE_KML: case FIND: { /* * Get the layers value from the input parameters JSON. */ String requestedLayers = operationInputJson.getString("layers"); /* * Syntax to specify which layers to find information from: * "layers=<layerId1>,<layerId2>" * * Manipulate the requested layers by matching against authorized layers. Remove layers * from the request user is not authorized to access. */ operationInputJson.put( "layers", removeUnauthorizedLayersFromRequestedLayers(requestedLayers.replaceAll("\\s+", ""), authorizedLayers)); } break; case IDENTIFY: { /* * Get the layers value from the input parameters JSON. */ String requestedLayers = operationInputJson.getString("layers"); /* * Syntax to specify which layers to query information from: * "layers=[top | visible | all]:layerId1,layerId2" * * Manipulate the requested layers by matching against authorized layers. Remove * layers from the request user is not authorized to access. */ /* * If the requestedLayers value is "top" or "all" replace with all layers user has * access to. * * If the requestedLayers value is "visible" + requested layers filter this based on * what layers user has access to. */ if (requestedLayers == null || requestedLayers.length() == 0 || requestedLayers.startsWith("top") || requestedLayers.startsWith("all")) { /* * Convert request to only authorized layers. */ operationInputJson.put("layers", "visible:" + authorizedLayers); } else if (requestedLayers.startsWith("visible")) { /* * Get all layers user wants to query. */ String[] requestedLayersInParts = requestedLayers.split(":"); /* * Verify user is authorized to access requested layers, if not convert request to * only authorized layers. */ operationInputJson.put( "layers", "visible:" + removeUnauthorizedLayersFromRequestedLayers(requestedLayersInParts[1].replaceAll("\\s+", ""), authorizedLayers)); } else { operationInputJson.put("layers", "visible:" + authorizedLayers); } } break; case GENERATE_RENDERER: case QUERY_RELATED_RECORDS: case QUERY: { /* * Get the requested layer from resource name. example: layers/0 */ String[] resourceNameInParts = resourceName.split("/"); String requestedLayerId = resourceNameInParts[1]; /* * Check if user has access to the requested layer. If not, throw an error */ if (!authorizedLayerSet.contains(requestedLayerId)) { return new JSONObject().put("error", new JSONObject().put("code", 404).put("message", "Not Found")) .toString().getBytes(); } } break; default: { if (resourceName.length() > 0) { /* * If operationName and operationInput are empty and we have a resource name than it * could be Feature or All Layer or Legend operation * * Context : ServiceName/MapServer/Layer/FeatureId * * The feature resource represents a single feature in a layer in a map service. For * feature requests the resource name will get a value like 'layers/0/features/1', using * this value we can perform layer level access. * * Feature resource has two child resources: * 1. Attachment Infos (layers/0/features/1/attachments) * 1.1 Attachment (layers/0/features/1/attachments/0) * 2. HTML Popup (layers/0/features/1/htmlPopup) * * Layer resource has these child resources: * 1. Query * 2. Query Related Records */ /* * Make sure resourceName always starts with '/' for consistency. */ String tempResourceName = ""; if(!resourceName.startsWith("/")) { tempResourceName = "/" + resourceName; } else { tempResourceName = resourceName; } /* * Get the requested layer from resource name. * Example: /layers/0 */ String operationFromResource = ""; String requestedLayerId = ""; try { String[] resourceNameInParts = tempResourceName.split("/"); operationFromResource = resourceNameInParts[1]; requestedLayerId = resourceNameInParts[2]; } catch (Exception ignore) { } if (operationFromResource.equalsIgnoreCase("legend") || operationFromResource.equalsIgnoreCase("layers")) { /* * Authorize request for a particular layer. * Example: /layers/0 */ if (requestedLayerId.length() > 0) { /* * Check if user has access to the requested layer. If not, throw an error. */ if (!authorizedLayerSet.contains(requestedLayerId)) { return new JSONObject() .put("error", new JSONObject().put("code", 404).put("message", "Not Found")).toString() .getBytes(); } } else { /* * Its the GetLegend or GetAllLayerandTables call, perform post processing. * * Find the correct delegate to forward the request to. */ IRESTRequestHandler restRequestHandler = soiHelper.findRestRequestHandlerDelegate(so); if (restRequestHandler != null) { /* * Get the response from Map Server SO. */ byte[] response = restRequestHandler.handleRESTRequest(capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, responseProperties); String responseStr = new String(response); /* * Perform filtering on the response based on access to different layers. */ responseStr = filterJSONGetLegendandGetAllLayersResponse(new String(response), authorizedLayers, operationFromResource); return responseStr.getBytes(); } } } } else { /* * Its the GetInfo call, perform post processing. * * Find the correct delegate to forward the request to. */ IRESTRequestHandler restRequestHandler = soiHelper.findRestRequestHandlerDelegate(so); if (restRequestHandler != null) { /* * Get the response from Map Server SO. */ byte[] response = restRequestHandler.handleRESTRequest(capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, responseProperties); String responseStr = new String(response); /* * Perform filtering on the response based on access to different layers. */ responseStr = filterJSONGetInfoResponse(new String(response), authorizedLayers); serverLog.addMessage(3, 200, "REST getInfo response :: " + responseStr); return responseStr.getBytes(); } } } } /* * Replace with formatted input parameters based on user access to different layers. */ operationInput = operationInputJson.toString(); serverLog.addMessage(3, 200, "modified operation input :: " + operationInput); } catch (Exception e) { // Log error with server serverLog.addMessage(1, 200, "Exception logged in SOI - " + e.toString()); /* * Note: Depending on the use case we can still forward the call to Server Object */ return new JSONObject() .put("error", new JSONObject().put("code", 404).put("message", "Not Found")).toString() .getBytes(); } /* * Find the correct delegate to forward the request to. */ IRESTRequestHandler restRequestHandler = soiHelper.findRestRequestHandlerDelegate(so); if (restRequestHandler != null) { /* * Return the response. */ byte[] response = restRequestHandler.handleRESTRequest(capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, responseProperties); serverLog.addMessage(3, 200, "rest response :: " + new String(response)); return response; } return new JSONObject() .put("error", new JSONObject().put("code", 404).put("message", "Not Found")).toString() .getBytes(); } /** * This method is called to handle SOAP requests. * * Note: Layer level access on SOAP handler operation requests is not implemented. * * @param capabilities the capabilities * @param request the request * @return the response as String * @throws IOException Signals that an I/O exception has occurred. * @throws AutomationException the automation exception */ @Override public String handleStringRequest(final String capabilities, final String request) throws IOException, AutomationException { // Log message with server serverLog.addMessage(3, 200, "Request received in Layer Access SOI for handleStringRequest"); serverLog.addMessage(3, 200, "capabilities :: " + capabilities); serverLog.addMessage(3, 200, "request :: " + request); // Convert the XML request into a generic IMessage IMessage requestMessage = SOIHelper.convertStringRequestToMessage(request); // Intercept the request and perform filtering String filteredRequest = filterRequest(requestMessage, RequestMode.STRING).stringRequest; if (filteredRequest == null) { filteredRequest = request; } // Forward the request to the appropriate delegate/handler IRequestHandler requestHandler = soiHelper.findRequestHandlerDelegate(so); if (requestHandler != null) { // Get the response String response = requestHandler.handleStringRequest(capabilities, filteredRequest); // Perform filtering for GetServerInfoResponse // Convert the XML request into a generic IMessage IMessage responseMessage = SOIHelper.convertStringRequestToMessage(response); // Get operation name String name = responseMessage.getName(); if("GetServerInfoResponse".equalsIgnoreCase(name)) { // Intercept the response and perform filtering String filteredResponse = filterRequest(responseMessage, RequestMode.STRING).stringRequest; if (filteredResponse != null) { response = filteredResponse; } } serverLog.addMessage(3, 200, "soap response :: " + response); return response; } return null; } /** * Apply filter to request parameters * * @param inRequest * @param mode * @return RequestTypes * @throws AutomationException * @throws IOException */ private RequestType filterRequest(IMessage inRequest, RequestMode mode) throws AutomationException, IOException { // 1. Get the name of the operation from inRequest // 2. For the operation that we care about, parse parameters // 3. Manipulate the parameters // 4. Re-construct the IMessage based on manipulated parameters // 5. Serialize the IMessage to original request format // Get operation name String name = inRequest.getName(); // Generate a set of authorized layers for the user HashSet<String> authorizedLayerSet = null; { Set<String> userRoleSet = getRoleInformation(); String authorizedLayers = ""; for (String role : userRoleSet) { if (servicePermissionMap.containsKey(so.getConfigurationName() + "." + so.getTypeName() + "." + role)) { authorizedLayers += "," + servicePermissionMap.get(so.getConfigurationName() + "." + so.getTypeName() + "." + role); } } // remove spaces authorizedLayers = authorizedLayers.replaceAll("\\s+", ""); // Create a set of authorized layers List<String> authorizedLayerList = null; try { authorizedLayerList = Arrays.asList(authorizedLayers.split(",")); authorizedLayerSet = new HashSet<String>(authorizedLayerList); } catch (Exception ignore) { } } // Apply filter only on those operations we care about if ("Find".equalsIgnoreCase(name)) { inRequest = filterLayerIds(inRequest, mode, authorizedLayerSet); } else if ("ExportMapImage".equalsIgnoreCase(name)) { inRequest = filterMapDescription(inRequest, mode, authorizedLayerSet); } else if ("Identify".equalsIgnoreCase(name)) { inRequest = filterLayerIds(inRequest, mode, authorizedLayerSet); } else if ("GetLegendInfo".equalsIgnoreCase(name)) { inRequest = filterLayerIds(inRequest, mode, authorizedLayerSet); } else if ("GetServerInfoResponse".equalsIgnoreCase(name)) { inRequest = filterGetServerInfoResponse(inRequest, mode, authorizedLayerSet); } // Recreate modified request back to the original request format RequestType request = new RequestType(); switch (mode) { case BYTE_ARRAY: request.byteArrayRequest = SOIHelper.convertMessageToBinaryRequest(inRequest); return request; case STRING: request.stringRequest = SOIHelper.convertMessageToStringRequest(inRequest); return request; } return null; } /** * Filter to apply layer level security to MapDescription * * @param inRequest * @param mode * @param authorizedLayerSet * @return * @throws AutomationException * @throws IOException */ private IMessage filterMapDescription(IMessage inRequest, RequestMode mode, HashSet<String> authorizedLayerSet) throws AutomationException, IOException { // 1. Find the index of the MapDescription parameter // 2. Get the value for the interested parameter // 3. Manipulate it // 4. Put it back into IMessage // Get the parameters out from the request object int idx = -1; IXMLSerializeData inRequestData = inRequest.getParameters(); // IXMLSerializeData inRequestData = inRequest.getParameters(); idx = inRequestData.find("MapDescription"); MapDescription md = (MapDescription) inRequestData.getObject(idx, inRequest.getNamespaceURI(), soiHelper.getSoapOperationParameterTypeName(inRequest.getName(), idx)); // Manipulate the MapDescription to perform layer level security ILayerDescriptions lds = md.getLayerDescriptions(); for (int i = 0; i < lds.getCount(); i++) { ILayerDescription ld = lds.getElement(i); if (!authorizedLayerSet.contains(Integer.toString(ld.getID()))) { ld.setVisible(false); } } // If binary request we dont have to create and copy in a new Message object if (mode == RequestMode.BYTE_ARRAY) { return inRequest; } // Create new request message IMessage modifiedInRequest = new Message(); IXMLSerializeData modifiedInRequestData = modifiedInRequest.getParameters(); // Set the correct namespace for outMessage if (inRequest.getNamespaceURI() != null) modifiedInRequest.setNamespaceURI(inRequest.getNamespaceURI()); // Set the correct operation modifiedInRequest.setName(inRequest.getName()); // Put all parameters back in IMessage for (int i = 0; i < inRequestData.getCount(); i++) { if (soiHelper.getSoapOperationParameterName(inRequest.getName(), i).equalsIgnoreCase("MapDescription")) { // Add the modified MapDescription modifiedInRequestData.addObject(soiHelper.getSoapOperationParameterName(inRequest.getName(), i), md); } else { /* * Add other parameters as is. Here we are using the SOI helper to add and get parameters * because we don't care about the type we just want to copy from one IMessage object to * another. */ modifiedInRequestData.addObject( soiHelper.getSoapOperationParameterName(inRequest.getName(), i), inRequestData.getObject(i, inRequest.getNamespaceURI(), soiHelper.getSoapOperationParameterTypeName(inRequest.getName(), i))); } } return modifiedInRequest; } /** * Filter to apply layer level security to LayerIds * * @param inRequest * @param mode * @param authorizedLayerSet * @return * @throws AutomationException * @throws IOException */ private IMessage filterLayerIds(IMessage inRequest, RequestMode mode, HashSet<String> authorizedLayerSet) throws AutomationException, IOException { // 1. Find the index of the layers parameter // 2. Get the value for the interested parameter // 3. Manipulate it // 4. Put it back into IMessage int idx = -1; IXMLSerializeData inRequestData = inRequest.getParameters(); try { idx = inRequestData.find("LayerIDs"); } catch (Exception ignore) { } LongArray layerIdInLA = null; if(idx >= 0) { // Get all the requested layers layerIdInLA = (LongArray) inRequestData.getObject(idx, inRequest.getNamespaceURI(), "ArrayOfInt"); // Perform filtering based on access to different layers for (int i = layerIdInLA.getCount() - 1; i >= 0; i--) { if(!authorizedLayerSet.contains(Integer.toString(layerIdInLA.getElement(i)))) { layerIdInLA.remove(i); } } } else { // No LayerIDs are specified, add authorized layers layerIdInLA = new LongArray(); for (String layerId : authorizedLayerSet) { layerIdInLA.add(Integer.parseInt(layerId)); } inRequestData.addObject("LayerIDs", layerIdInLA); } // If binary request we dont have to create and copy in a new Message object if (mode == RequestMode.BYTE_ARRAY) { return inRequest; } // Create new request message IMessage modifiedInRequest = soiHelper.createNewIMessage(inRequest); IXMLSerializeData modifiedInRequestData = modifiedInRequest.getParameters(); // Put all parameters back in IMessage for (int i = 0; i < inRequestData.getCount(); i++) { if (soiHelper.getSoapOperationParameterName(inRequest.getName(), i).equalsIgnoreCase("LayerIDs")) { // Add the modified MapDescription soiHelper.addObjectToXMLSerializeData(soiHelper.getSoapOperationParameterName(inRequest.getName(), i), layerIdInLA, soiHelper.getSoapOperationParameterTypeName(inRequest.getName(), i), modifiedInRequestData); } else { /* * Add other parameters as is. Here we are using the SOI helper to add and get parameters * because we don't care about the type we just want to copy from one IMessage object to * another. */ soiHelper.addObjectToXMLSerializeData( soiHelper.getSoapOperationParameterName(inRequest.getName(), i), soiHelper.getObjectFromXMLSerializeData(i, inRequest.getNamespaceURI(), soiHelper.getSoapOperationParameterTypeName(inRequest.getName(), i), inRequestData), soiHelper.getSoapOperationParameterTypeName(inRequest.getName(), i), modifiedInRequestData); } } return modifiedInRequest; } /** * Filter to apply layer level security to GetServerInfoResponse * * @param inRequest * @param mode * @param authorizedLayerSet * @return * @throws AutomationException * @throws IOException */ private IMessage filterGetServerInfoResponse(IMessage inRequest, RequestMode mode, HashSet<String> authorizedLayerSet) throws AutomationException, IOException { // 1. Find the index of the Result parameter // 2. Get the value for the MapServerInfo parameter // 3. Manipulate it // 4. Put it back into IMessage int idx = -1; IXMLSerializeData inRequestData = inRequest.getParameters(); idx = inRequestData.find("Result"); // Get MapServerInfo MapServerInfo mapServerInfo = (MapServerInfo) inRequestData.getObject(idx, inRequest.getNamespaceURI(), "MapServerInfo"); // Perform filtering based on access to different layers IMapLayerInfos layerInfos = mapServerInfo.getMapLayerInfos(); for (int i = layerInfos.getCount() - 1; i >= 0; i--) { if(!authorizedLayerSet.contains(Integer.toString(layerInfos.getElement(i).getID()))) { layerInfos.remove(i); } } // Perform filtering based on access to different layers ILayerDescriptions layerDescriptions = mapServerInfo.getDefaultMapDescription().getLayerDescriptions(); for (int i = layerDescriptions.getCount() - 1; i >= 0; i--) { if(!authorizedLayerSet.contains(Integer.toString(layerDescriptions.getElement(i).getID()))) { layerDescriptions.remove(i); } } // If binary request we don't have to create and copy in a new Message object if (mode == RequestMode.BYTE_ARRAY) { return inRequest; } // Create new request message IMessage modifiedInRequest = soiHelper.createNewIMessage(inRequest); IXMLSerializeData modifiedInRequestData = modifiedInRequest.getParameters(); // Put all parameters back in IMessage for (int i = 0; i < inRequestData.getCount(); i++) { if (soiHelper.getSoapOperationParameterName(inRequest.getName(), i).equalsIgnoreCase("Result")) { // Add the modified MapDescription modifiedInRequestData.addObject(soiHelper.getSoapOperationParameterName(inRequest.getName(), i), mapServerInfo); } else { /* * Add other parameters as is. Here we are using the SOI helper to add and get parameters * because we don't care about the type we just want to copy from one IMessage object to * another. */ modifiedInRequestData.addObject( soiHelper.getSoapOperationParameterName(inRequest.getName(), i), inRequestData.getObject(i, inRequest.getNamespaceURI(), soiHelper.getSoapOperationParameterTypeName(inRequest.getName(), i))); } } return modifiedInRequest; } /** * This method is called by SOAP handler to handle OGC requests. * * @param httpMethod * @param requestURL the request URL * @param queryString the query string * @param capabilities the capabilities * @param requestData the request data * @param responseContentType the response content type * @param respDataType the response data type * @return the response as byte[] * @throws IOException Signals that an I/O exception has occurred. * @throws AutomationException the automation exception */ @Override public byte[] handleStringWebRequest(int httpMethod, String requestURL, String queryString, String capabilities, String requestData, String[] responseContentType, int[] respDataType) throws IOException, AutomationException { serverLog.addMessage(3, 200, "Request received in Sample Object Interceptor for handleStringWebRequest"); /* * Add code to manipulate OGC (WMS, WFC, WCS etc) requests here */ IWebRequestHandler webRequestHandler = soiHelper.findWebRequestHandlerDelegate(so); if (webRequestHandler != null) { return webRequestHandler.handleStringWebRequest(httpMethod, requestURL, queryString, capabilities, requestData, responseContentType, respDataType); } return null; } /** * This method is called to handle schema requests for custom SOE's. * * To get schema (or Root resource) for a Map Service, REST handler calls * <code>handleRESTRequest</code> with all arguments as empty. * * @return the schema as String * @throws IOException Signals that an I/O exception has occurred. * @throws AutomationException the automation exception */ @Override public String getSchema() throws IOException, AutomationException { serverLog.addMessage(3, 200, "Request received in Sample Object Interceptor for getSchema"); /* * Add code to manipulate schema requests here */ IRESTRequestHandler restRequestHandler = soiHelper.findRestRequestHandlerDelegate(so); if (restRequestHandler != null) { return restRequestHandler.getSchema(); } return null; } /** * This method is called to handle binary requests from desktop. * * @param capabilities the capabilities * @param request * @return the response as byte[] * @throws IOException Signals that an I/O exception has occurred. * @throws AutomationException the automation exception */ @Override public byte[] handleBinaryRequest2(String capabilities, byte[] request) throws IOException, AutomationException { // Log message with server serverLog.addMessage(3, 200, "Request received in Sample Object Interceptor for handleBinaryRequest2"); /* * Add code to manipulate Binary requests from desktop here */ IRequestHandler2 requestHandler = soiHelper.findRequestHandler2Delegate(so); if (requestHandler != null) { return requestHandler.handleBinaryRequest2(capabilities, request); } return null; } /** * This method is called to handle binary requests from desktop. It calls the * <code>handleBinaryRequest2</code> method with capabilities equal to null. * * @param request * @return the response as the byte[] * @throws IOException Signals that an I/O exception has occurred. * @throws AutomationException the automation exception */ @Override public byte[] handleBinaryRequest(byte[] request) throws IOException, AutomationException { // Log message with server serverLog.addMessage(3, 200, "Request received in Sample Object Interceptor for handleBinaryRequest"); /* * Add code to manipulate Binary requests from desktop here */ // Convert the XML request into a generic IMessage IMessage requestMessage = SOIHelper.convertBinaryRequestToMessage(request); // Intercept the request and perform filtering byte[] filteredRequest = filterRequest(requestMessage, RequestMode.BYTE_ARRAY).byteArrayRequest; if (filteredRequest == null) { filteredRequest = request; } // Forward the request to the appropriate delegate/handler IRequestHandler requestHandler = soiHelper.findRequestHandlerDelegate(so); if (requestHandler != null) { // Get the response byte[] response = requestHandler.handleBinaryRequest(filteredRequest); // Perform filtering for GetServerInfoResponse // Convert the XML request into a generic IMessage IMessage responseMessage = SOIHelper.convertBinaryRequestToMessage(response); // Get operation name String name = responseMessage.getName(); if("GetServerInfoResponse".equalsIgnoreCase(name)) { // Intercept the response and perform filtering byte[] filteredResponse = filterRequest(responseMessage, RequestMode.BYTE_ARRAY).byteArrayRequest; if (filteredResponse != null) { response = filteredResponse; } } return response; } return null; } /** * shutdown() is called once when the Server Object's context is being shut down and is about to * go away. * * @throws IOException Signals that an I/O exception has occurred. * @throws AutomationException the automation exception */ public void shutdown() throws IOException, AutomationException { /* * The SOE should release its reference on the Server Object Helper. */ this.serverLog.addMessage(3, 200, "Shutting down " + this.getClass().getName() + " SOI."); this.serverLog = null; this.so = null; this.soiHelper = null; } /** * Remove unauthorized layers from request. * * @param requestedLayers layer user is requesting information from * @param authorizedLayers layers user is authorized to fetch information from * @return */ private static String removeUnauthorizedLayersFromRequestedLayers(String requestedLayers, String authorizedLayers) { // authorized layers List<String> authorizedLayerList = null; HashSet<String> authorizedLayerSet = null; try { authorizedLayerList = Arrays.asList(authorizedLayers.split(",")); authorizedLayerSet = new HashSet<String>(authorizedLayerList); } catch (Exception ignore) { } // requested layers List<String> requestedLayersList = null; List<String> requestedLayersArrayList = null; try { requestedLayersList = Arrays.asList(requestedLayers.split(",")); requestedLayersArrayList = new ArrayList<String>(requestedLayersList); } catch (Exception ignore) { } if (requestedLayersArrayList != null && authorizedLayerSet != null) { // filter out unauthorized layers for (String layer : requestedLayersList) { if (!authorizedLayerSet.contains(layer)) { requestedLayersArrayList.remove(layer); } } // if nothing was common return -1 if (requestedLayersArrayList.size() == 0) return "-1"; else return join(requestedLayersArrayList, ","); } return "-1"; } /** * Read permission information from disk * * @param fileName path and name of the file to read permissions from * @return */ private Map<String, String> readPermissionFile(String fileName) { // read the permissions file BufferedReader reader; Map<String, String> permissionMap = new HashMap<String, String>(); try { reader = new BufferedReader(new FileReader(fileName)); String line = null; String permissionFileDataString = ""; while ((line = reader.readLine()) != null) { permissionFileDataString += line; } JSONObject permissionsJSON = new JSONObject(permissionFileDataString); // create a map of permissions // read the permissions array JSONArray permissionsArray = permissionsJSON.getJSONArray("permissions"); // add to map for (int i = 0; i < permissionsArray.length(); i++) { // get the fqsn or service name String fqsn = permissionsArray.getJSONObject(i).getString("fqsn"); // read the permission for that service JSONArray permissionArray = permissionsArray.getJSONObject(i).getJSONArray("permission"); for (int j = 0; j < permissionArray.length(); j++) { String role = permissionArray.getJSONObject(j).getString("role"); // read and get all authorized layers String authorizedLayers = permissionArray.getJSONObject(j).getString("authorizedLayers"); permissionMap.put(fqsn + "." + role, authorizedLayers); } } reader.close(); } catch (Exception ignore) { } return permissionMap; } /** * Joins the specified parts separating each from one another with the specified delimiter. * * @param parts the strings to be joined * @param delim the char(s) that should separate the parts in the result * @return a string representing the joined parts. */ private static String join(List<String> parts, String delim) { StringBuilder result = new StringBuilder(); for (int i = 0; i < parts.size(); i++) { String part = parts.get(i); result.append(part); if (delim != null && i < parts.size() - 1) { result.append(delim); } } return result.toString(); } /** * Filter REST GetLegend response. * * @param originalJSONRes * @param authorizedLayers * @param operationFromResource * @return */ private String filterJSONGetLegendandGetAllLayersResponse( final String originalJSONRes, final String authorizedLayers, final String operationFromResource) { // Decide what tag to use, based on the operation String layerIdJSONKey = null; if(operationFromResource.equalsIgnoreCase("legend")) { layerIdJSONKey = "layerId"; } else if (operationFromResource.equalsIgnoreCase("layers")) { layerIdJSONKey = "id"; } // Authorized layer set List<String> authorizedLayerList = null; HashSet<String> authorizedLayerSet = null; try { authorizedLayerList = Arrays.asList(authorizedLayers.split(",")); authorizedLayerSet = new HashSet<String>(authorizedLayerList); } catch (Exception ignore) { } // Perform JSON filtering // Note: The JSON syntax can change and you might have to adjust this piece of code accordingly if (authorizedLayerSet != null) { JSONObject jsonResObj = new JSONObject(originalJSONRes); // Filter for layers' tag JSONArray legendLayerJA = jsonResObj.getJSONArray("layers"); for (int i = legendLayerJA.length() - 1; i >= 0; i--) { if (!authorizedLayerSet.contains(Integer.toString(legendLayerJA.getJSONObject(i).getInt(layerIdJSONKey)))) { legendLayerJA.remove(i); } } // Return the filter response return jsonResObj.toString(); } return null; } /** * Filter REST GetInfo response. * * @param originalJSONRes * @param authorizedLayers * @return */ private static String filterJSONGetInfoResponse(final String originalJSONRes, final String authorizedLayers) { // Authorized layer set List<String> authorizedLayerList = null; HashSet<String> authorizedLayerSet = null; try { authorizedLayerList = Arrays.asList(authorizedLayers.split(",")); authorizedLayerSet = new HashSet<String>(authorizedLayerList); } catch (Exception ignore) { } // Perform JSON filtering // Note: The JSON syntax can change and you might have to adjust this piece of code accordingly if (authorizedLayerSet != null) { /* * Remove unauthorized layer information from * 1. Under 'contents' tag * 2. Under 'resources' tag * 2.1 'layers' * 2.2 'tables' * 2.3 'legend' */ JSONObject jsonResObj = new JSONObject(originalJSONRes); // Filter for 'contents' tag JSONArray layersJA = jsonResObj.getJSONObject("contents").getJSONArray("layers"); for (int i = layersJA.length() - 1; i >= 0; i--) { if (!authorizedLayerSet.contains(Integer.toString(layersJA.getJSONObject(i).getInt("id")))) { layersJA.remove(i); } } // Filter for 'resources' tag JSONArray resourcesJA = jsonResObj.getJSONArray("resources"); // Filter for 'resources -> layers -> resources' tag JSONArray layerResourceJA = resourcesJA.getJSONObject(0).getJSONArray("resources"); for (int i = layerResourceJA.length() - 1; i >= 0; i--) { if (!authorizedLayerSet.contains(layerResourceJA.getJSONObject(i).getString("name"))) { layerResourceJA.remove(i); } } // Filter for 'resources -> tables -> resources' tag JSONArray tableResourceJA = resourcesJA.getJSONObject(1).getJSONArray("resources"); for (int i = tableResourceJA.length() - 1; i >= 0; i--) { if (!authorizedLayerSet.contains(tableResourceJA.getJSONObject(i).getString("name"))) { tableResourceJA.remove(i); } } // Filter for 'resources -> legend -> layers' tag JSONArray legendLayerJA = resourcesJA.getJSONObject(2).getJSONObject("contents").getJSONArray("layers"); for (int i = legendLayerJA.length() - 1; i >= 0; i--) { if (!authorizedLayerSet.contains(Integer.toString(legendLayerJA.getJSONObject(i).getInt("layerId")))) { legendLayerJA.remove(i); } } // Return the filter response return jsonResObj.toString(); } return null; } /** * Get allowed roles for user making the request * * @return * @throws Exception */ private Set<String> getRoleInformation() { // Roles set Set<String> roles = new HashSet<String>(); try { /* * Get the user information. */ IServerUserInfo userInfo = ServerUtilities.getServerUserInfo(); /* * Get information on the user making the call. */ String userName = userInfo.getName(); /* * Get all roles user belongs to. */ IEnumBSTR rolesEnum = userInfo.getRoles(); if (rolesEnum != null) { String role = rolesEnum.next(); while (role != null && !role.isEmpty()) { roles.add(role); role = rolesEnum.next(); } } rolesEnum.reset(); return roles; } catch (Exception ignore) { } return roles; } /** * Returns the ArcGIS home directory path. * * @return * @throws Exception */ private String getArcGISHomeDir() throws IOException { String arcgisHome = null; /* Not found in env, check system property */ if (System.getProperty(ARCGISHOME_ENV) != null) { arcgisHome = System.getProperty(ARCGISHOME_ENV); } if(arcgisHome == null) { /* To make env lookup case insensitive */ Map<String, String> envs = System.getenv(); for (String envName : envs.keySet()) { if (envName.equalsIgnoreCase(ARCGISHOME_ENV)) { arcgisHome = envs.get(envName); } } } if(arcgisHome != null && !arcgisHome.endsWith(File.separator)) { arcgisHome += File.separator; } return arcgisHome; } /** * Reads a permission file and return the defined permissions. * * @param serverobject * @throws IOException */ private void getPermissionFromFile(IServerObject serverobject) throws IOException { String serverDir = null; MapServer mapserver= (MapServer)serverobject; String physicalOutputDir= mapserver.getPhysicalOutputDirectory(); int index = physicalOutputDir.indexOf(File.separator + "directories" + File.separator + "arcgisoutput"); if(index > 0) { serverLog.addMessage(4, 200, "The physical directory for output files: " + physicalOutputDir); serverDir = physicalOutputDir.substring(0,index); } else { serverLog.addMessage(1, 200,"Incorrect physical directory for output files: " + physicalOutputDir); throw new IOException("Incorrect physical directory for output files: " + physicalOutputDir); } /* * Permission are read from this external file. Advantage of an external file is that same SOI can * be used for multiple services and permission for all of these services is read from the * permission.json file. */ String permssionFilePath = serverDir + File.separator + "permission.json"; // Read the permissions file if (new File(permssionFilePath).exists()) { serverLog.addMessage(4, 200, "The permission file is located at : " + permssionFilePath); servicePermissionMap = readPermissionFile(permssionFilePath); } else { serverLog.addMessage(1, 200,"Cannot find the permission file at " + permssionFilePath); throw new IOException("Cannot find the permission file at " + permssionFilePath); } } /** * String or binary request mode */ private enum RequestMode { STRING, BYTE_ARRAY } /** * String or binary request type */ private class RequestType { public String stringRequest; public byte[] byteArrayRequest; } /** * List of operations supported by REST handler */ private enum RESTOperations { // Supported operations FIND("find"), IDENTIFY("identify"), EXPORT_MAP("export"), GENERATE_KML("generateKml"), GENERATE_RENDERER( "generateRenderer"), QUERY("query"), QUERY_RELATED_RECORDS("queryRelatedRecords"), DEFAULT("default"); private String value; private static final Map<String, RESTOperations> lookup = new HashMap<String, RESTOperations>(); static { for (RESTOperations operation : RESTOperations.values()) lookup.put(operation.getValue(), operation); } private RESTOperations(String value) { this.value = value; } public String getValue() { return value; } /** * Get RESTOperation from a String representation * * @param value * @return */ public static RESTOperations get(final String value) { if (lookup.get(value) != null) return lookup.get(value); else return RESTOperations.DEFAULT; } }; }