arcgissamples\soi\OperationAccessSOI.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.File; import java.io.IOException; import java.util.HashSet; import java.util.Map; import java.util.Set; 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.JSONObject; import com.esri.arcgis.system.IEnumBSTR; import com.esri.arcgis.system.ILog; 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.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. * * In this example we try to control access to different operation based on user * roles. * * * Note: Intercepting REST operation calls has been implemented in this example. * All other handlers need to be implemented. * * Note: Limitation with using SOIs in 10.3 is that we cannot intercept the call * to ArcSOC, used to create the Service Directory (or Root resource) HTML page, * as the call is made once and subsequently REST handler caches this * information. We are working on fixing this limitation. */ /* * For an interceptor you need to set additional properties for @ServerObjectExtProperties the annotation. * 1. interceptor = true, is used to identify an SOI * 2. servicetype = MapService | ImageService, can be used to assign an interceptor to an Image or Map Service. */ @ArcGISExtension @ServerObjectExtProperties(displayName = "Operation Access SOI", description = "SOI to control access to different operations", interceptor = true, servicetype = "MapService") public class OperationAccessSOI 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; /* * Load the SOI helper. */ //private SOIHelper soiHelper = new SOIHelper("C:/Program Files/ArcGIS/Server/XmlSchema/MapServer.wsdl"); /** * Default constructor. * * @throws Exception */ public OperationAccessSOI() 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"); 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. * To get schema or root resource for a Map Service the REST handler * calls <code>handleRESTRequest</code> with all arguments as empty. * For a Map Service the supported REST operations are: find, identify, export. * For an Image Service the supported REST operations are: identify, export, etc. * * In this example we demonstrate operation level access. REST operation on * a published Map Service are controlled via the * <code>rolesAuthorizedForFindOperation</code> list. It defines a list of * roles that have access to operations (find, identify, export) on a map * service. For users who do not belong to the authorized roles list we * return a <code>null</code> value. * * * Note: Limitation with using SOIs in 10.3 is that we cannot intercept the call * to ArcSOC, used to create the Service Directory (or Root resource) HTML page, * as the call is made once and subsequently REST handler caches this * information. We are working on fixing this limitation. * * @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. */ serverLog.addMessage(3, 200, "Request received in Operation 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); /* * By default, block access for all users. */ boolean blockAccessToFindOperation = true; /* * List of roles that have access. * * Here we have defined a single list to control access for all * operations but depending on the use case we can create per operation * level lists or even read this information from an external file. */ String[] rolesAuthorizedForFindOperation = {"gold", "platinum"}; /* * Check if the user if authorized to perform the operation. * * Note: Here we are checking for all valid Map Service operations. If * you need to use this SOI for a published Image Service you need to * extend this to cover all Image Service operations. */ if (operationName.equalsIgnoreCase("find") // find operation || operationName.equalsIgnoreCase("identify") // identify operation || operationName.equalsIgnoreCase("export"))// export operation { /* * Get all roles the user belongs to. */ Set<String> userRoleSet = getRoleInformation(); /* * Using 'userRoleSet' and ''rolesAuthorizedForFindOperation' to * decide if user has access to the requested operation. */ for (int i = 0; i < rolesAuthorizedForFindOperation.length; i++) { if(userRoleSet.contains(rolesAuthorizedForFindOperation[i])) { blockAccessToFindOperation = false; } } /* * Block access to the operation if user is not authorized */ if (blockAccessToFindOperation) { return new JSONObject() .put("error", new JSONObject().put("code", 404).put("message", "Not Found")).toString() .getBytes(); } } else if( !operationName.equalsIgnoreCase("")){ /* * We support only operations find, identify, export * for all other operations we return a null or no result. */ 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(4, 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: Intercepting and authorizing 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(String capabilities, String request) throws IOException, AutomationException { // Log message with server serverLog.addMessage(3, 200, "Request received in Server Object Interceptor for handleStringRequest"); /* * Add code to manipulate SOAP requests here */ // Find the correct delegate to forward the request too IRequestHandler requestHandler = soiHelper.findRequestHandlerDelegate(so); if(requestHandler != null) // Return the response return requestHandler.handleStringRequest(capabilities, request); return null; } /** * 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 Server 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 Server 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 { serverLog.addMessage(3, 200, "Request received in Server 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 { serverLog.addMessage(3, 200, "Request received in Server Object Interceptor for handleBinaryRequest"); /* * Add code to manipulate Binary requests from desktop here */ IRequestHandler requestHandler = soiHelper.findRequestHandlerDelegate(so); if(requestHandler != null) return requestHandler.handleBinaryRequest(request); 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; } /** * Get allowed roles for the user making a 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(); } } 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; } }