NetLayerAccessSOI\NetLayerAccessSOI.cs
// 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.3/userestrictions.txt. // using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections.Specialized; using System.Runtime.InteropServices; using ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS.Server; using ESRI.ArcGIS.Geometry; using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.SOESupport; using System.IO; using System.Text.RegularExpressions; using System.Web.Script.Serialization; using NetLayerAccessSOI.SOISupport; //TODO: sign the project (project properties > signing tab > sign the assembly) // this is strongly suggested if the dll will be registered using regasm.exe <your>.dll /codebase namespace NetLayerAccessSOI { [ComVisible(true)] [Guid("0b8694f2-1730-453d-b645-7022dfc24bef")] [ClassInterface(ClassInterfaceType.None)] [ServerObjectInterceptor("MapServer", Description = "SOI to control access to different layers of a service.", DisplayName = "DotNet Layer Access SOI Example", Properties = "")] public class NetLayerAccessSOI : IServerObjectExtension, IRESTRequestHandler, IWebRequestHandler, IRequestHandler2, IRequestHandler { private string _soiName; private IServerObjectHelper _soHelper; private ServerLogger _serverLog; private IServerObject _serverObject; RestServiceSOI _restServiceSOI; SoapServiceSOI _soapServiceSOI; private Dictionary<RESTHandlerOpCode, RestFilterOperation> _operationMap; /* * Map used to store permission information. Permission rules for each * service is read form the permisson.json file. */ private Dictionary<String, String> _servicePermissionMap = null; /* * 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. * */ private String _permissionFilePath = "C:\\arcgisserver\\permission.json"; //default path private String _wsdlFilePath = "C:\\Program Files\\ArcGIS\\Server\\XmlSchema\\MapServer.wsdl"; //default path public NetLayerAccessSOI () { _soiName = this.GetType().Name; } private void InitFiltering () { _operationMap = new Dictionary<RESTHandlerOpCode, RestFilterOperation> { { RESTHandlerOpCode.root, new RestFilterOperation { PreFilter = null, PostFilter = PostFilterRESTRoot } }, { RESTHandlerOpCode.rootExport, new RestFilterOperation { PreFilter = PreFilterExport, PostFilter = null } }, { RESTHandlerOpCode.rootFind, new RestFilterOperation { PreFilter = PreFilterFindAndKml, PostFilter = null } }, { RESTHandlerOpCode.rootGenerateKml, new RestFilterOperation { PreFilter = PreFilterFindAndKml, PostFilter = null } }, { RESTHandlerOpCode.rootIdentify, new RestFilterOperation { PreFilter = PreFilterIdentify, PostFilter = null } }, { RESTHandlerOpCode.rootLayers, new RestFilterOperation { PreFilter = null, PostFilter = PostFilterRootLayers } }, { RESTHandlerOpCode.rootLegend, new RestFilterOperation { PreFilter = null, PostFilter = PostFilterRootLegend } }, { RESTHandlerOpCode.layerGenerateRenderer, new RestFilterOperation { PreFilter = PreFilterLayerQuery, PostFilter = null } }, { RESTHandlerOpCode.layerQuery, new RestFilterOperation { PreFilter = PreFilterLayerQuery, PostFilter = null } }, { RESTHandlerOpCode.layerQueryRelatedRecords, new RestFilterOperation { PreFilter = PreFilterLayerQuery, PostFilter = null } } }; } public void Init ( IServerObjectHelper pSOH ) { try { _soHelper = pSOH; _serverLog = new ServerLogger(); _serverObject = pSOH.ServerObject; _restServiceSOI = new RestServiceSOI(_soHelper); _soapServiceSOI = new SoapServiceSOI(_soHelper, _wsdlFilePath); if (File.Exists(_permissionFilePath)) { //TODO REMOVE _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".init()", 200, "Reading permissions from " + _permissionFilePath); _servicePermissionMap = ReadPermissionFile(_permissionFilePath); //TODO REMOVE _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".init()", 200, "Total permission entries: " + _servicePermissionMap.Count()); } else { _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".init()", 500, "Permission file does not exist at " + _permissionFilePath); } InitFiltering(); _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".init()", 200, "Initialized " + _soiName + " SOI."); } catch (Exception e) { _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".init()", 500, "Exception: " + e.Message + " in " + e.StackTrace); } } public void Shutdown () { _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".init()", 200, "Shutting down " + _soiName + " SOE."); } #region REST interceptors public string GetSchema () { try { IRESTRequestHandler restRequestHandler = _restServiceSOI.FindRequestHandlerDelegate<IRESTRequestHandler>(); if (restRequestHandler == null) return null; return restRequestHandler.GetSchema(); } catch (Exception e) { _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".GetSchema()", 500, "Exception: " + e.Message + " in " + e.StackTrace); return null; } } private byte[] FilterRESTRequest ( RestFilterOperation restFilterOp, RESTRequestParameters restInput, HashSet<string> authorizedLayers, out string responseProperties ) { try { responseProperties = null; IRESTRequestHandler restRequestHandler = _restServiceSOI.FindRequestHandlerDelegate<IRESTRequestHandler>(); if (restRequestHandler == null) throw new RESTErrorException("Service handler not found"); if (null != restFilterOp && null != restFilterOp.PreFilter) restInput = restFilterOp.PreFilter(restInput, authorizedLayers); byte[] response = restRequestHandler.HandleRESTRequest(restInput.Capabilities, restInput.ResourceName, restInput.OperationName, restInput.OperationInput, restInput.OutputFormat, restInput.RequestProperties, out responseProperties); if (null == restFilterOp || null == restFilterOp.PostFilter) return response; String responseStr = System.Text.Encoding.UTF8.GetString(response); responseStr = restFilterOp.PostFilter(restInput, responseStr, authorizedLayers); // TODO: remove ------------------------------------ _serverLog.LogMessage(ServerLogger.msgType.infoDetailed, "HandleRESTRequest.FilterRESTRequest", 0, "Filtered REST resource response :: " + responseStr); // TODO: remove ------------------------------------ return System.Text.Encoding.UTF8.GetBytes(responseStr); } catch (RESTErrorException restException) { // pre- or post- filters can throw restException with the error JSON output in the Message property. // we catch them here and return JSON response. responseProperties = "{\"Content-Type\":\"text/plain;charset=utf-8\"}"; //catch and return a JSON error from the pre- or postFilter. return System.Text.Encoding.UTF8.GetBytes(restException.Message); } } private static RESTHandlerOpCode GetHandlerOpCode ( RESTRequestParameters restInput ) { var resName = restInput.ResourceName.TrimStart('/'); //remove leading '/' to prevent empty string at index 0 var resourceTokens = (resName ?? "").ToLower().Split('/'); string opName = (restInput.OperationName ?? "").ToLower(); switch (resourceTokens[0]) { case "": switch (opName) { case "": return RESTHandlerOpCode.root; case "export": return RESTHandlerOpCode.rootExport; case "find": return RESTHandlerOpCode.rootFind; case "identify": return RESTHandlerOpCode.rootIdentify; case "generatekml": return RESTHandlerOpCode.rootGenerateKml; default: return RESTHandlerOpCode.defaultNoOp; } case "layers": { var tokenCount = resourceTokens.GetLength(0); if (1 == tokenCount) return RESTHandlerOpCode.rootLayers; if (2 == tokenCount) switch (opName) { case "": return RESTHandlerOpCode.layerRoot; case "query": return RESTHandlerOpCode.layerQuery; case "queryRelatedRecords": return RESTHandlerOpCode.layerQueryRelatedRecords; default: return RESTHandlerOpCode.defaultNoOp; } } break; case "legend": return RESTHandlerOpCode.rootLegend; default: return RESTHandlerOpCode.defaultNoOp; } return RESTHandlerOpCode.defaultNoOp; } public byte[] HandleRESTRequest ( string capabilities, string resourceName, string operationName, string operationInput, string outputFormat, string requestProperties, out string responseProperties ) { try { responseProperties = null; _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleRESTRequest()", 200, "Request received in Layer Access SOI for handleRESTRequest"); var authorizedLayerSet = GetAuthorizedLayers(_restServiceSOI); var restInput = new RESTRequestParameters { Capabilities = capabilities, ResourceName = resourceName, OperationName = operationName, OperationInput = operationInput, OutputFormat = outputFormat, RequestProperties = requestProperties }; var opCode = GetHandlerOpCode(restInput); RestFilterOperation filterOp = _operationMap.ContainsKey(opCode) ? _operationMap[opCode] : null; return FilterRESTRequest(filterOp, restInput, authorizedLayerSet, out responseProperties); } catch (Exception e) { _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".HandleRESTRequest()", 500, "Exception: " + e.Message + " in " + e.StackTrace); throw; } } #endregion #region REST Pre-filters private static RESTRequestParameters PreFilterExport ( RESTRequestParameters restInput, HashSet<string> authorizedLayers ) { JavaScriptSerializer sr = new JavaScriptSerializer { MaxJsonLength = int.MaxValue }; var operationInputJson = sr.DeserializeObject(restInput.OperationInput) as IDictionary<string, object>; operationInputJson["layers"] = "show:" + String.Join(",", authorizedLayers); restInput.OperationInput = sr.Serialize(operationInputJson); return restInput; } private static RESTRequestParameters PreFilterFindAndKml ( RESTRequestParameters restInput, HashSet<string> authorizedLayers ) { JavaScriptSerializer sr = new JavaScriptSerializer { MaxJsonLength = int.MaxValue }; var operationInputJson = sr.DeserializeObject(restInput.OperationInput) as IDictionary<string, object>; var removeSpacesRegEx = new Regex("\\s+"); String requestedLayers = operationInputJson.ContainsKey("layers") ? operationInputJson["layers"].ToString() : ""; requestedLayers = removeSpacesRegEx.Replace(requestedLayers, ""); operationInputJson["layers"] = RemoveUnauthorizedLayersFromRequestedLayers(requestedLayers, authorizedLayers); restInput.OperationInput = sr.Serialize(operationInputJson); return restInput; } private static RESTRequestParameters PreFilterIdentify ( RESTRequestParameters restInput, HashSet<string> authorizedLayers ) { JavaScriptSerializer sr = new JavaScriptSerializer { MaxJsonLength = int.MaxValue }; var operationInputJson = sr.DeserializeObject(restInput.OperationInput) as IDictionary<string, object>; String requestedLayers = operationInputJson.ContainsKey("layers") ? operationInputJson["layers"].ToString() : ""; if (string.IsNullOrEmpty(requestedLayers) || requestedLayers.StartsWith("top") || requestedLayers.StartsWith("all")) { operationInputJson["layers"] = "visible:" + authorizedLayers; } else if (requestedLayers.StartsWith("visible")) { var reqLayerParts = requestedLayers.Split(':'); var removeSpacesRegEx = new Regex("\\s+"); operationInputJson["layers"] = "visible:" + RemoveUnauthorizedLayersFromRequestedLayers( removeSpacesRegEx.Replace(reqLayerParts[1], ""), authorizedLayers); } else { operationInputJson["layers"] = "visible:" + authorizedLayers; } restInput.OperationInput = sr.Serialize(operationInputJson); return restInput; } private static RESTRequestParameters PreFilterLayerQuery ( RESTRequestParameters restInput, HashSet<string> authorizedLayers ) { JavaScriptSerializer sr = new JavaScriptSerializer { MaxJsonLength = int.MaxValue }; var operationInputJson = sr.DeserializeObject(restInput.OperationInput) as IDictionary<string, object>; var resName = restInput.ResourceName.TrimStart('/'); var rnameParts = resName.Split('/'); var requestedLayerId = rnameParts[1]; if (!authorizedLayers.Contains(requestedLayerId)) { var errorReturn = new Dictionary<string, object> { {"error", new Dictionary<string, object> { {"code", 404}, {"message", "Access Denied"} } } }; throw new RESTErrorException(sr.Serialize(errorReturn)); } restInput.OperationInput = sr.Serialize(operationInputJson); return restInput; } #endregion #region REST Post-filters /// <summary> /// /// Filter REST root service info response. /// IMPORTANT: the JSON structure returned by the REST handler root resource /// differs from what you usually see as the response from the service REST endpoint. /// /// </summary> /// <param name="restInput"></param> /// <param name="originalResponse"></param> /// <param name="authorizedLayerSet"></param> /// <returns>Filtered json</returns> private static String PostFilterRESTRoot ( RESTRequestParameters restInput, String originalResponse, HashSet<String> authorizedLayerSet ) { if (null == authorizedLayerSet) return null; //restInput is not used here, but may be used later as needed try { // Perform JSON filtering // Note: The JSON syntax can change and you might have to adjust this piece of code accordingly if (authorizedLayerSet == null) return null; /* * Remove unauthorized layer information from 1. Under 'contents' tag 2. Under 'resources' tag * 2.1 'layers' 2.2 'tables' 2.3 'legend' */ JavaScriptSerializer sr = new JavaScriptSerializer { MaxJsonLength = int.MaxValue }; var jsonResObj = sr.DeserializeObject(originalResponse) as IDictionary<string, object>; // Filter for 'contents' tag var contentsJO = jsonResObj["contents"] as IDictionary<string, object>; var layersJA = contentsJO["layers"] as object[]; var updatedLayers = new List<object>(); foreach (var layerO in layersJA) { var layerJO = layerO as IDictionary<string, object>; var id = layerJO["id"].ToString(); if (authorizedLayerSet.Contains(id)) { updatedLayers.Add(layerJO); } } contentsJO["layers"] = updatedLayers.ToArray(); // Filter for 'resources' tag, very simplified filtering, may fail if ordering changes var allResourcesJA = jsonResObj["resources"] as object[]; var layersRJO = allResourcesJA.FirstOrDefault(e => { var jo = e as IDictionary<string, object>; if (!jo.ContainsKey("name")) return false; var name = jo["name"].ToString(); return ("layers" == name); }) as IDictionary<string, object>; var tablesRJO = allResourcesJA.FirstOrDefault(e => { var jo = e as IDictionary<string, object>; if (!jo.ContainsKey("name")) return false; var name = jo["name"].ToString(); return ("tables" == name); }) as IDictionary<string, object>; var legendRJO = allResourcesJA.FirstOrDefault(e => { var jo = e as IDictionary<string, object>; if (!jo.ContainsKey("name")) return false; var name = jo["name"].ToString(); return ("legend" == name); }) as IDictionary<string, object>; //filter and replace layers var filteredLayerResourceJA = new List<object>(); if (null != layersRJO) { // Filter for 'resources -> layers -> resources' tag var layerResourceJA = layersRJO["resources"] as object[]; foreach (var lrJO in layerResourceJA) { var lrJODict = lrJO as IDictionary<string, object>; if (authorizedLayerSet.Contains(lrJODict["name"].ToString())) { filteredLayerResourceJA.Add(lrJO); } } } layersRJO["resources"] = filteredLayerResourceJA.ToArray(); //filter and replace tables var filteredTableResourceJA = new List<object>(); if (null != tablesRJO) { // Filter for 'resources -> tables -> resources' tag var tableResourceJA = tablesRJO["resources"] as object[]; foreach (var tbJO in tableResourceJA) { var tbJODict = tbJO as IDictionary<string, object>; if (authorizedLayerSet.Contains(tbJODict["name"].ToString())) { filteredTableResourceJA.Add(tbJO); } } } tablesRJO["resources"] = filteredTableResourceJA.ToArray(); //filter and replace legend layers var filteredLegendLayersRJA = new List<object>(); if (null != legendRJO) { // Filter for 'resources -> legend -> contents->layers' tag var legendContentsJO = legendRJO["contents"] as IDictionary<string, object>; var legendLayersJA = legendContentsJO["layers"] as object[]; foreach (var lgJO in legendLayersJA) { var lgJODict = lgJO as IDictionary<string, object>; if (authorizedLayerSet.Contains(lgJODict["layerId"].ToString())) { filteredLegendLayersRJA.Add(lgJO); } } legendContentsJO["layers"] = filteredLegendLayersRJA.ToArray(); } // Return the filter response return sr.Serialize(jsonResObj); } catch (Exception ignore) { return null; } } private static string PostFilterRootLayers ( RESTRequestParameters restInput, String originalResponse, HashSet<String> authorizedLayerSet ) { if (null == authorizedLayerSet) return null; //restInput is not used here, but may be used later as needed try { // Perform JSON filtering // Note: The JSON syntax can change and you might have to adjust this piece of code accordingly if (authorizedLayerSet == null) return null; /* * Remove unauthorized layer information from 1. Under 'contents' tag 2. Under 'resources' tag * 2.1 'layers' 2.2 'tables' 2.3 'legend' */ JavaScriptSerializer sr = new JavaScriptSerializer { MaxJsonLength = int.MaxValue }; var jsonResObj = sr.DeserializeObject(originalResponse) as IDictionary<string, object>; // Filter for 'contents' tag var layersJA = jsonResObj["layers"] as object[]; var updatedLayers = new List<object>(); foreach (var layerO in layersJA) { var layerJO = layerO as IDictionary<string, object>; var id = layerJO["id"].ToString(); if (authorizedLayerSet.Contains(id)) { updatedLayers.Add(layerJO); } } jsonResObj["layers"] = updatedLayers.ToArray(); // Return the filter response return sr.Serialize(jsonResObj); } catch (Exception ignore) { return null; } } private static string PostFilterRootLegend ( RESTRequestParameters restInput, String originalResponse, HashSet<String> authorizedLayerSet ) { if (null == authorizedLayerSet) return null; //restInput is not used here, but may be used later as needed try { // Perform JSON filtering // Note: The JSON syntax can change and you might have to adjust this piece of code accordingly if (authorizedLayerSet == null) return null; /* * Remove unauthorized layer information from 1. Under 'contents' tag 2. Under 'resources' tag * 2.1 'layers' 2.2 'tables' 2.3 'legend' */ JavaScriptSerializer sr = new JavaScriptSerializer { MaxJsonLength = int.MaxValue }; var jsonResObj = sr.DeserializeObject(originalResponse) as IDictionary<string, object>; // Filter for 'contents' tag var layersJA = jsonResObj["layers"] as object[]; var updatedLayers = new List<object>(); foreach (var layerO in layersJA) { var layerJO = layerO as IDictionary<string, object>; var id = layerJO["layerId"].ToString(); if (authorizedLayerSet.Contains(id)) { updatedLayers.Add(layerJO); } } jsonResObj["layers"] = updatedLayers.ToArray(); // Return the filter response return sr.Serialize(jsonResObj); } catch (Exception ignore) { return null; } } #endregion #region SOAP interceptors public byte[] HandleStringWebRequest ( esriHttpMethod httpMethod, string requestURL, string queryString, string Capabilities, string requestData, out string responseContentType, out esriWebResponseDataType respDataType ) { _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleStringWebRequest()", 200, "Request received in Layer Access SOI for HandleStringWebRequest"); /* * Add code to manipulate OGC (WMS, WFC, WCS etc) requests here */ IWebRequestHandler webRequestHandler = _restServiceSOI.FindRequestHandlerDelegate<IWebRequestHandler>(); if (webRequestHandler != null) { var response = webRequestHandler.HandleStringWebRequest( httpMethod, requestURL, queryString, Capabilities, requestData, out responseContentType, out respDataType); return response; } responseContentType = null; respDataType = esriWebResponseDataType.esriWRDTPayload; //Insert error response here. return null; } public byte[] HandleBinaryRequest ( ref byte[] request ) { _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleBinaryRequest()", 200, "Request received in Layer Access SOI for HandleBinaryRequest"); /* * Add code to manipulate Binary requests from desktop here */ // Generate a set of authorized layers for the user var authorizedLayerSet = GetAuthorizedLayers(_soapServiceSOI); IMessage requestMessage = SoapServiceSOI.ConvertBinaryRequestToMessage(request); SoapBinaryRequest filteredRequest = FilterSoapRequest(requestMessage, SoapRequestMode.Binary, authorizedLayerSet) as SoapBinaryRequest; if (null == filteredRequest) { filteredRequest = new SoapBinaryRequest { Body = request }; } IRequestHandler requestHandler = _restServiceSOI.FindRequestHandlerDelegate<IRequestHandler>(); if (requestHandler != null) { var response = requestHandler.HandleBinaryRequest(filteredRequest.Body); // Perform filtering for GetServerInfoResponse // Convert the XML request into a generic IMessage IMessage responseMessage = SoapServiceSOI.ConvertBinaryRequestToMessage(response); // Get operation name String name = responseMessage.Name; if ("GetServerInfoResponse".Equals(name, StringComparison.CurrentCultureIgnoreCase)) { // Intercept the response and perform filtering var filteredResponse = FilterSoapRequest(responseMessage, SoapRequestMode.Binary, authorizedLayerSet) as SoapBinaryRequest; if (filteredResponse != null) { response = filteredResponse.Body; } } return response; } //Insert error response here. return null; } public byte[] HandleBinaryRequest2 ( string Capabilities, ref byte[] request ) { _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleBinaryRequest2()", 200, "Request received in Layer Access SOI for HandleBinaryRequest2"); IMessage requestMessage = SoapServiceSOI.ConvertBinaryRequestToMessage(request); var authorizedLayerSet = GetAuthorizedLayers(_soapServiceSOI); SoapBinaryRequest filteredRequest = FilterSoapRequest(requestMessage, SoapRequestMode.Binary, authorizedLayerSet) as SoapBinaryRequest; if (null == filteredRequest) { filteredRequest = new SoapBinaryRequest { Body = request }; } IRequestHandler2 requestHandler = _restServiceSOI.FindRequestHandlerDelegate<IRequestHandler2>(); if (requestHandler != null) { var response = requestHandler.HandleBinaryRequest2(Capabilities, filteredRequest.Body); // Perform filtering for GetServerInfoResponse // Convert the XML request into a generic IMessage IMessage responseMessage = SoapServiceSOI.ConvertBinaryRequestToMessage(response); // Get operation name String name = responseMessage.Name; if ("GetServerInfoResponse".Equals(name, StringComparison.CurrentCultureIgnoreCase)) { // Intercept the response and perform filtering var filteredResponse = FilterSoapRequest(responseMessage, SoapRequestMode.Binary, authorizedLayerSet) as SoapBinaryRequest; if (filteredResponse != null) { response = filteredResponse.Body; } } return response; } //Insert error response here. return null; } public string HandleStringRequest ( string Capabilities, string request ) { _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleStringRequest()", 200, "Request received in Layer Access SOI for HandleStringRequest"); // Convert the XML request into a generic IMessage IMessage requestMessage = SoapServiceSOI.ConvertStringRequestToMessage(request); // Generate a set of authorized layers for the user var authorizedLayerSet = GetAuthorizedLayers(_soapServiceSOI); // Intercept the request and perform filtering var filteredRequest = FilterSoapRequest(requestMessage, SoapRequestMode.Xml, authorizedLayerSet) as SoapStringRequest; if (filteredRequest == null) { filteredRequest = new SoapStringRequest { Body = request }; } IRequestHandler requestHandler = _restServiceSOI.FindRequestHandlerDelegate<IRequestHandler>(); if (requestHandler != null) { var response = requestHandler.HandleStringRequest(Capabilities, filteredRequest.Body); // Perform filtering for GetServerInfoResponse // Convert the XML request into a generic IMessage IMessage responseMessage = SoapServiceSOI.ConvertStringRequestToMessage(response); // Get operation name String name = responseMessage.Name; if ("GetServerInfoResponse".Equals(name, StringComparison.CurrentCultureIgnoreCase)) { // Intercept the response and perform filtering var filteredResponse = FilterSoapRequest(responseMessage, SoapRequestMode.Xml, authorizedLayerSet) as SoapStringRequest; if (filteredResponse != null) { response = filteredResponse.Body; } } return response; } //Insert error response here. return null; } /// <summary> /// /// </summary> /// <param name="inRequest"></param> /// <param name="mode"></param> /// <returns></returns> private SoapRequest FilterSoapRequest ( IMessage inRequest, SoapRequestMode mode, HashSet<string> authorizedLayerSet ) { // Get operation name String name = inRequest.Name; // Apply filter only on those operations we care about var nameInLowerCase = name.ToLower(); switch (nameInLowerCase) { case "find": inRequest = FilterLayerIds(inRequest, mode, authorizedLayerSet); break; case "exportmapimage": inRequest = FilterMapDescription(inRequest, mode, authorizedLayerSet); break; case "identify": inRequest = FilterLayerIds(inRequest, mode, authorizedLayerSet); break; case "getlegendinfo": inRequest = FilterLayerIds(inRequest, mode, authorizedLayerSet); break; case "getserverinforesponse": inRequest = FilterGetServerInfoResponse(inRequest, mode, authorizedLayerSet); break; } return SoapRequestFactory.Create(mode, inRequest); } /// <summary> /// /// </summary> /// <param name="inRequest"></param> /// <param name="mode"></param> /// <param name="authorizedLayerSet"></param> /// <returns></returns> private IMessage FilterLayerIds ( IMessage inRequest, SoapRequestMode mode, HashSet<String> authorizedLayerSet ) { // 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.Parameters; 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.NamespaceURI, "ArrayOfInt"); // Perform filtering based on access to different layers. // Remove restricted ids in-place for (int i = layerIdInLA.Count - 1; i >= 0; i--) { if (!authorizedLayerSet.Contains(layerIdInLA.Element[i].ToString())) { layerIdInLA.Remove(i); } } } else //no LayerIDs specified, attaching authorized layer list instead { layerIdInLA = new LongArrayClass(); foreach (var goodLayerId in authorizedLayerSet) { layerIdInLA.Add(Int32.Parse(goodLayerId)); } inRequestData.AddObject("LayerIDs", layerIdInLA); } // If binary request we dont have to create and copy in a new Message object if (mode == SoapRequestMode.Binary) { return inRequest; } // Create new request message IMessage modifiedInRequest = _soapServiceSOI.CreateNewIMessage(inRequest); IXMLSerializeData modifiedInRequestData = modifiedInRequest.Parameters; // Put all parameters back in IMessage for (int i = 0; i < inRequestData.Count; i++) { if (_soapServiceSOI.GetSoapOperationParameterName(inRequest.Name, i).Equals("LayerIDs", StringComparison.CurrentCultureIgnoreCase)) { // Add the modified MapDescription _soapServiceSOI.AddObjectToXMLSerializeData(_soapServiceSOI.GetSoapOperationParameterName(inRequest.Name, i), layerIdInLA, _soapServiceSOI.GetSoapOperationParameterTypeName(inRequest.Name, 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. */ _soapServiceSOI.AddObjectToXMLSerializeData( _soapServiceSOI.GetSoapOperationParameterName(inRequest.Name, i), _soapServiceSOI.GetObjectFromXMLSerializeData(i, inRequest.NamespaceURI, _soapServiceSOI.GetSoapOperationParameterTypeName(inRequest.Name, i), inRequestData), _soapServiceSOI.GetSoapOperationParameterTypeName(inRequest.Name, i), modifiedInRequestData); } } return modifiedInRequest; } private IMessage FilterMapDescription ( IMessage inRequest, SoapRequestMode mode, HashSet<string> authorizedLayerSet ) { // 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.Parameters; idx = inRequestData.Find("MapDescription"); MapDescription md = (MapDescription)inRequestData.GetObject(idx, inRequest.NamespaceURI, _soapServiceSOI.GetSoapOperationParameterTypeName(inRequest.Name, idx)); // Manipulate the MapDescription to perform layer level security ILayerDescriptions lds = md.LayerDescriptions; for (int i = 0; i < lds.Count; i++) { ILayerDescription ld = lds.Element[i]; if (!authorizedLayerSet.Contains(ld.ID.ToString())) { ld.Visible = false; } } // If binary request we dont have to create and copy in a new Message object if (mode == SoapRequestMode.Binary) { return inRequest; } // Create new request message IMessage modifiedInRequest = _soapServiceSOI.CreateNewIMessage(inRequest); IXMLSerializeData modifiedInRequestData = modifiedInRequest.Parameters; // Put all parameters back in IMessage for (int i = 0; i < inRequestData.Count; i++) { if (_soapServiceSOI.GetSoapOperationParameterName(inRequest.Name, i).Equals("MapDescription", StringComparison.CurrentCultureIgnoreCase)) { // Add the modified MapDescription modifiedInRequestData.AddObject(_soapServiceSOI.GetSoapOperationParameterName(inRequest.Name, 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( _soapServiceSOI.GetSoapOperationParameterName(inRequest.Name, i), inRequestData.GetObject(i, inRequest.NamespaceURI, _soapServiceSOI.GetSoapOperationParameterTypeName(inRequest.Name, i))); } } return modifiedInRequest; } private IMessage FilterGetServerInfoResponse ( IMessage inRequest, SoapRequestMode mode, HashSet<string> authorizedLayerSet ) { int idx = -1; IXMLSerializeData inRequestData = inRequest.Parameters; idx = inRequestData.Find("Result"); // Get MapServerInfo MapServerInfo mapServerInfo = (MapServerInfo)inRequestData.GetObject(idx, inRequest.NamespaceURI, "MapServerInfo"); // Perform filtering based on access to different layers IMapLayerInfos layerInfos = mapServerInfo.MapLayerInfos; for (int i = layerInfos.Count - 1; i >= 0; i--) { if (!authorizedLayerSet.Contains(layerInfos.Element[i].ID.ToString())) { layerInfos.Remove(i); } } IMapDescription mapDescription = mapServerInfo.DefaultMapDescription; ILayerDescriptions lds = mapDescription.LayerDescriptions; for (int i = lds.Count - 1; i >= 0; i--) { if (!authorizedLayerSet.Contains(lds.Element[i].ID.ToString())) { lds.Remove(i); } } // If binary request we don't have to create and copy in a new Message object if (mode == SoapRequestMode.Binary) { return inRequest; } // Create new request message IMessage modifiedInRequest = _soapServiceSOI.CreateNewIMessage(inRequest); IXMLSerializeData modifiedInRequestData = modifiedInRequest.Parameters; // Put all parameters back in IMessage for (int i = 0; i < inRequestData.Count; i++) { if (_soapServiceSOI.GetSoapOperationParameterName(inRequest.Name, i).Equals("Result", StringComparison.CurrentCultureIgnoreCase)) { // Add the modified MapDescription modifiedInRequestData.AddObject(_soapServiceSOI.GetSoapOperationParameterName(inRequest.Name, 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( _soapServiceSOI.GetSoapOperationParameterName(inRequest.Name, i), inRequestData.GetObject(i, inRequest.NamespaceURI, _soapServiceSOI.GetSoapOperationParameterTypeName(inRequest.Name, i))); } } return modifiedInRequest; } #endregion #region Utility code /// <summary> /// Remove unauthorized layers from request. /// </summary> /// <param name="requestedLayers">layer user is requesting information from</param> /// <param name="authorizedLayers">layers user is authorized to fetch information from</param> /// <returns></returns> private static String RemoveUnauthorizedLayersFromRequestedLayers ( String requestedLayers, HashSet<String> authorizedLayers ) { if (0 == authorizedLayers.Count) return "-1"; // requested layers IEnumerable<String> requestedLayersList = null; try { requestedLayersList = requestedLayers.Split(new[] { ',' }).Select(e => e.Trim()); } catch (Exception ignore) { } if (authorizedLayers != null) { var filteredLayers = requestedLayersList.Where(e => authorizedLayers.Contains(e)); if (!filteredLayers.Any()) return "-1"; return String.Join(",", filteredLayers.ToArray()); } return "-1"; } /// <summary> /// Read permission information from disk /// </summary> /// <param name="fileName">path and name of the file to read permissions from</param> /// <returns></returns> private Dictionary<String, String> ReadPermissionFile ( String fileName ) { // read the permissions file Dictionary<String, String> permissionMap = new Dictionary<String, String>(); try { if (!File.Exists(fileName)) return permissionMap; String jsonStr = File.ReadAllText(fileName); var json = new ESRI.ArcGIS.SOESupport.JsonObject(jsonStr); System.Object[] permissions = null; // create a map of permissions // read the permissions array if (!json.TryGetArray("permissions", out permissions) || permissions == null) return permissionMap; // add to map foreach (var permsObj in permissions) { ESRI.ArcGIS.SOESupport.JsonObject permsJO = permsObj as ESRI.ArcGIS.SOESupport.JsonObject; if (null == permsJO) continue; // get the fqsn or service name String fqsn = string.Empty; permsJO.TryGetString("fqsn", out fqsn); // read the permission for that service System.Object[] permArray = null; if (!permsJO.TryGetArray("permission", out permArray) || permArray == null) continue; foreach (var permObj in permArray) { ESRI.ArcGIS.SOESupport.JsonObject permJO = permObj as ESRI.ArcGIS.SOESupport.JsonObject; String role = string.Empty; if (!permJO.TryGetString("role", out role)) continue; String authorizedLayers = string.Empty; permJO.TryGetString("authorizedLayers", out authorizedLayers); //may be empty or null permissionMap.Add(fqsn + "." + role, authorizedLayers); } } } catch (Exception ignore) { //TODO error handling } return permissionMap; } private HashSet<string> GetAuthorizedLayers ( ServiceSOIBase soi ) { var userRoleSet = soi.GetRoleInformation(soi.ServerEnvironment.UserInfo); HashSet<String> authorizedLayerSet = null; if (null == userRoleSet) return authorizedLayerSet; /* * Generate a set of authorized layers for the user */ var fullServiceName = _serverObject.ConfigurationName + "." + _serverObject.TypeName; var removeSpacesRegEx = new Regex("\\s+"); var authorizedRoles = userRoleSet.Where(role=>_servicePermissionMap.ContainsKey(fullServiceName + "." + role)); var authorizedLayerList = authorizedRoles.SelectMany(role => removeSpacesRegEx. Replace(_servicePermissionMap[fullServiceName + "." + role], ""). Split(',')); return new HashSet<string>(authorizedLayerList); } #endregion } }