Giusto per fare un paragone, i SOI possiamo pensarli come le server filter.
Ma come mai sono stati introdotti i SOI? Precedentemente non era possibile modificare il comportamento delle chiamate standard dei servizi GIS e alcune funzionalità personalizzate erano piuttosto complicate e richiedevano l'utilizzo di proxy - comunque non erano soluzioni perfettamente integrate nei servizi GIS.
I potenziali use case per lo sviluppo di SOI sono:
Sicurezza
- controllo di accesso a livello di layer e di operation
- extent/AOI basato su masking
- risposte di query modificate/perfezionate sia negli attributi che nella geometria
- watermark, classificazioni e personalizzazioni
- auditing e logging
- integrazione con dati esterni e propri sistemi
- validazione degli input e output (post processing)
- inserimenti avanzati (in input e output), funzionalità personalizzate (reindirizzare specifiche operazioni ad entità esterne)
I servizi di tipo Map e Image, con incluse le capability (ad esempio i feature servizi) supportano tre diversi tipi di richieste:
- richieste REST API
- richieste SOAP API
- richieste OGC
Come possiamo vedere dalle immagini il SOI si frappone tra i client e SO/SOE
I client fanno delle richieste che possono essere gestite o meno sia prima di inviarle all'handler responsabile che dopo l'elaborazione (ma prima di inviarle al client).
Qui la risposta post process può anche essere reindirizzata altrove oltre che al client
Qui la richiesta può essere sempre inviata altrove oltre che agli oggetti sottostanti al servizio
Affinché un SOI possa intercettare queste richieste, è necessario implementare le seguenti interfacce:
- IRESTRequestHandler - per la gestione delle richieste API REST
- IRequestHandler2 - per la gestione delle richieste SOAP API, tra le quali anche le richieste dai clienti desktop quali ArcMap
- IWebRequestHandler - per la gestione delle richieste OGC
Tutte queste interfacce si trovano nello spazio dei nomi Esri.ArcGIS.esriSystem.
Anche se una configurazione del servizio non supporta le richieste OGC (ad esempio WMS, WFS ecc.), è necessario gestire tutte le interfacce di cui sopra.
A seconda della logica di business che si desidera di implementare, si può scegliere tra due approcci generali. Se si implementa un SOI che gestirà funzioni di sicurezza, allora è consigliabile iniziare ad implementare tutte le interfacce e bloccare tutte le richieste. Una volta implementato il codice personalizzato, è possibile consentire l'accesso tramite le interfacce. Se non adottiamo questo metodo non bloccando tutte le richieste in arrivo e consentendo l'accesso successivamente, c'è maggiore rischio ad esporsi a 'buchi' nella sicurezza. Mentre, se non si sta sviluppando funzionalità di sicurezza, è possibile implementare le tre interfacce facendo passare tutte le richieste con l'implementazione standard di base, al fine di consentire le funzionalità già presenti e successivamente aggiungere la logica di business per una o più operazioni che si desidera modificare.
A seconda della logica di business che si desidera di implementare, si può scegliere tra due approcci generali. Se si implementa un SOI che gestirà funzioni di sicurezza, allora è consigliabile iniziare ad implementare tutte le interfacce e bloccare tutte le richieste. Una volta implementato il codice personalizzato, è possibile consentire l'accesso tramite le interfacce. Se non adottiamo questo metodo non bloccando tutte le richieste in arrivo e consentendo l'accesso successivamente, c'è maggiore rischio ad esporsi a 'buchi' nella sicurezza. Mentre, se non si sta sviluppando funzionalità di sicurezza, è possibile implementare le tre interfacce facendo passare tutte le richieste con l'implementazione standard di base, al fine di consentire le funzionalità già presenti e successivamente aggiungere la logica di business per una o più operazioni che si desidera modificare.
Quando un servizio GIS è stato configurato con un SOI, il server inoltra tutte le richieste del servizio al SOI. Ovviamente è responsabilità del SOI elaborare le richieste, delegare la richiesta ai correnti oggetti del servizio Map o Image, se applicabile, e poi eventuali ulteriori elaborazioni sulla risposta prima di restituirla al client.
Implementare l'interfaccia IRESTRequestHandler
La gestione delle API REST è il modo più semplice per iniziare con lo sviluppo di un SOI, dal momento che tutti i parametri e le risposte dell'operazione sono text /JSON e possono essere facilmente gestiti.
La principale funzione di questa interfaccia è:
public byte [] HandleRESTRequest (string capabilities, string resourceName, string
operationName, string operationInput, string outputFormat, string
requestProperties, ref string responseProperties);
La funzione modella le REST API in termini di risorse e di operazioni e le sue corrispondenti proprietà della richiesta.
Il parametro operationInput contiene tipicamente un oggetto JSON che rappresenta la richiesta. Si può facilmente gestire questo oggetto utilizzando la libreria JSON disponibile nella piattaforma di vostra scelta (NET o Java). Al fine di ottenere la configurazione di default per il servizio, il REST Services Directory invoca questa operazione passando oggetti JSON vuoti per tutti gli argomenti. Delegando questa chiamata al servizio sottostante e poi manipolando l'output nel SOI, si può vedere quali risorse (layers) e operazioni (export, find, identity, ecc) vengono presentati al Services Directory e ai client.
Il parametro operationInput contiene tipicamente un oggetto JSON che rappresenta la richiesta. Si può facilmente gestire questo oggetto utilizzando la libreria JSON disponibile nella piattaforma di vostra scelta (NET o Java). Al fine di ottenere la configurazione di default per il servizio, il REST Services Directory invoca questa operazione passando oggetti JSON vuoti per tutti gli argomenti. Delegando questa chiamata al servizio sottostante e poi manipolando l'output nel SOI, si può vedere quali risorse (layers) e operazioni (export, find, identity, ecc) vengono presentati al Services Directory e ai client.
Un'altra funzione che deve essere gestita è:
public string getSchema ()
Questa funzione informa il REST Services Directory su come rappresentare la configurazione del servizio. In genere, l'implementazione del SOI dovrebbe delegare questa chiamata all'oggetto sottostante del servizio di Map o Image e poi eventualmente, se lo desideri, manipolare l'output.
Implementare l'interfaccia IRequestHandler2
L'implementazione di questa interfaccia è più complessa in quanto gestisce le richieste binarie (da ArcGIS for Desktop ed altre applicazioni basate su ArcObjects) e le richieste SOAP (da client SOAP personalizzati). In entrambi i casi, è necessario utilizzare le API ArcObjects per decodificare la richiesta in entrata, eventualmente modificare i parametri della richiesta, e quindi ricodificare la richiesta da inviare agli oggetti sottostanti dei servizi Map o Image. Anche per il post-process delle risposte, è sempre necessario utilizzare le API ArcObjects per gestire le risposte prima di inviarle al client. L'SDK contiene le classi di helper per gestire le richieste/risposte.
Questa interfaccia contiene due metodi che occorre implementare:
public byte [] HandleBinaryRequest2 (string capabilities, byte [] request)
La funzione di cui sopra viene richiamata quando il server riceve richieste binarie (da ArcGIS for Desktop) per un servizio. Il parametro di richiesta contiene la richiesta binaria che deve essere gestita utilizzando le API ArcObjects.
public string HandleStringRequest (string capabilities, string request)
Questa funzione viene richiamata quando il server riceve una richiesta SOAP (XML). I client SOAP in genere fanno queste richieste. Il parametro di richiesta contiene la richiesta XML che deve essere gestita utilizzando le API ArcObjects.
Implementare l'interfaccia IWebRequestHandler
Richieste KML e richieste di servizi OGC (WMS e WFS) possono essere intercettate implementando l'interfaccia IWebRequestHandler nel SOI. Le funzioni in questa interfaccia rappresentano una tipica richiesta OGC che è una richiesta HTTP con query string di parametri. È necessario implementare la seguente funzione:
public byte [] HandleStringWebRequest (esriHttpMethod httpMethod, string requestURL,
stringa queryString, string capabilities, string requestData, ref string responseContentType, ref int respDataType)
Il parametro requestData è generalmente un text/XML e contiene l'input per la richiesta. Tuttavia, i parametri vengono spesso inviati tramite il parametro queryString. Il parametro respDataType informa il tuo codice su come il valore byte[] restituito deve essere interpretato. Se il respDataType indica un file, il tuo codice deve inviare i contenuti del file al percorso restituito da byte []. Il responseContentType informa l'handler web il tipo di contenuto da impostare mentre risponde alla richiesta HTTP.
Una classe di helper chiamata SOISupport è parte dell' esempio 'Layer Access' presente nell'SDK ArcObjects . Questa classe di helper contiene metodi per interagire con il servizio per delegare le richieste in arrivo, metodi per la conversione tra oggetti ArcObjects di risposta e molto altro. Inoltre nell'esempio 'Layer Access' è possibile vedere come utilizzare questi metodi in particolare quando si tratta di richieste SOAP e binarie. Altre classi nell'assembly ESRI.ArcGIS.SOESupport forniscono funzionalità per manipolare i dati e gli oggetti JSON.
Ed ora passiamo alla pratica vedendo un po' di esempi.
Come abbiamo accennato precedentemente lo sviluppo di un SOI è analogo a quello delle SOE.
Una volta installato l'SDK ArcObjects è possibile utilizzare il template disponibile nel proprio ambiente di sviluppo.
Il template genererà il seguente codice:
Come possiamo subito notare la definizione della classe è simile a quella della SOE solo che implementerà anche le interfacce IWebRequestHandler, IRequestHandler2, IRequestHandler ed è decorata con l'attributo ServerObjectInterceptorAttribute. Init e Shutdown sono sempre presenti visto il SOI 'vive' con il servizio GIS nel quale è abilitato.
Nella region 'Utility code' sono già disponibili una funzione FindRequestHandlerDelegate ed una proprietà ServerEnvironment . FindRequestHandlerDelegate determina il corretto delegato per reindirizzare la richiesta in arrivo mentre la proprietà ServerEnvironment è utilizzata dalla funzione stessa per verificare se la richiesta è indirizzata al SO o ad una specifica SOE e quindi di conseguenza determinare il delegato corrispondente.
Vediamo ora un esempio che modifica la richiesta dopo averla fatta elaborare all'oggetto sottostante al servizio. Watermark sulla mappa generata su una richiesta di Export
Nel codice possiamo notare come, una volta disponibile la risposta dal SO, aggiungiamo il watermark all'immagine prima di inviarla al client.
Una volta compilato lo installiamo in ArcGIS Server Manager come avviene per le SOE.
e successivamente abilitiamo il SOI nel servizio GIS.
Possiamo effettuare il test direttamente dall'export del Rest Services Directory
Link live demo
Mentre in questo esempio vediamo una PoC di come gestire un controllo a livello di layer basato su permessi impostati su file esterno.
Se si implementa il controllo a livello di layer attraverso un SOI, occorre anche disabilitare il caching di tutti i layer delle risorse inclusi nel servizio. Questo consente di intercettare le operazione e filtrare i layer che non sono consentiti. Nell'ArcGIS Server Administrator Directory occorre impostare la proprietà disableCaching a true del servizio .
I passi da seguire nel dettaglio sono:
- Aprire l'ArcGIS Server Administrator Directory (l'url è http://myserver:6080/arcgis/admin);
- cliccare su services e cliccare il nome del servizio interessato;
- cliccare edit;
- nella sezione properties del json del servizio, aggiungere la proprietà disableCaching ed impostare il valore a true
"properties": {
...
"disableCaching": "true",
...
},
- cliccare save Edits.
Ed ora passiamo alla pratica vedendo un po' di esempi.
Come abbiamo accennato precedentemente lo sviluppo di un SOI è analogo a quello delle SOE.
Una volta installato l'SDK ArcObjects è possibile utilizzare il template disponibile nel proprio ambiente di sviluppo.
Il template genererà il seguente codice:
namespace MyFirstSOI { [ComVisible(true)] [Guid("29a910dc-1cb6-48f5-a8a5-7f723c63e553")] [ClassInterface(ClassInterfaceType.None)] [ServerObjectInterceptor("",//use "MapServer" if SOI extends a Map service and "ImageServer" if it extends an Image service. Description = "", DisplayName = "SOI1", Properties = "")] public class MyFirstSOI : IServerObjectExtension, IRESTRequestHandler, IWebRequestHandler, IRequestHandler2, IRequestHandler { private string _soiName; private IServerObjectHelper _soHelper; private ServerLogger _serverLog; private Dictionary<String, IServerObjectExtension> _extensionCache = new Dictionary<String, IServerObjectExtension>(); IServerEnvironment2 _serverEnvironment; public MyFirstSOI() { _soiName = this.GetType().Name; } public void Init(IServerObjectHelper pSOH) { _soHelper = pSOH; _serverLog = new ServerLogger(); _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".init()", 200, "Initialized " + _soiName + " SOI."); } public void Shutdown() { _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".init()", 200, "Shutting down " + _soiName + " SOI."); } #region REST interceptors public string GetSchema() { IRESTRequestHandler restRequestHandler = FindRequestHandlerDelegate<IRESTRequestHandler>(); if (restRequestHandler == null) return null; return restRequestHandler.GetSchema(); } public byte[] HandleRESTRequest(string Capabilities, string resourceName, string operationName, string operationInput, string outputFormat, string requestProperties, out string responseProperties) { responseProperties = null; _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleRESTRequest()", 200, "Request received in Sample Object Interceptor for handleRESTRequest"); /* * Add code to manipulate REST requests here */ // Find the correct delegate to forward the request too IRESTRequestHandler restRequestHandler = FindRequestHandlerDelegate<IRESTRequestHandler>(); if (restRequestHandler == null) return null; return restRequestHandler.HandleRESTRequest( Capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, out responseProperties); } #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 Sample Object Interceptor for HandleStringWebRequest"); /* * Add code to manipulate requests here */ IWebRequestHandler webRequestHandler = FindRequestHandlerDelegate<IWebRequestHandler>(); if (webRequestHandler != null) { return webRequestHandler.HandleStringWebRequest( httpMethod, requestURL, queryString, Capabilities, requestData, out responseContentType, out respDataType); } 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 Sample Object Interceptor for HandleBinaryRequest"); /* * Add code to manipulate requests here */ IRequestHandler requestHandler = FindRequestHandlerDelegate<IRequestHandler>(); if (requestHandler != null) { return requestHandler.HandleBinaryRequest(request); } //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 Sample Object Interceptor for HandleBinaryRequest2"); /* * Add code to manipulate requests here */ IRequestHandler2 requestHandler = FindRequestHandlerDelegate<IRequestHandler2>(); if (requestHandler != null) { return requestHandler.HandleBinaryRequest2(Capabilities, request); } //Insert error response here. return null; } public string HandleStringRequest(string Capabilities, string request) { _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleStringRequest()", 200, "Request received in Sample Object Interceptor for HandleStringRequest"); /* * Add code to manipulate requests here */ IRequestHandler requestHandler = FindRequestHandlerDelegate<IRequestHandler>(); if (requestHandler != null) { return requestHandler.HandleStringRequest(Capabilities, request); } //Insert error response here. return null; } #endregion #region Utility code private IServerEnvironment2 ServerEnvironment { get { if (_serverEnvironment == null) { UID uid = new UIDClass(); uid.Value = "{32D4C328-E473-4615-922C-63C108F55E60}"; // CoCreate an EnvironmentManager and retrieve the IServerEnvironment IEnvironmentManager environmentManager = new EnvironmentManager() as IEnvironmentManager; _serverEnvironment = environmentManager.GetEnvironment(uid) as IServerEnvironment2; } return _serverEnvironment; } } private THandlerInterface FindRequestHandlerDelegate<THandlerInterface>() where THandlerInterface : class { try { IPropertySet props = ServerEnvironment.Properties; String extensionName; try { extensionName = (String)props.GetProperty("ExtensionName"); } catch (Exception /*e*/) { extensionName = null; } if (String.IsNullOrEmpty(extensionName)) { return (_soHelper.ServerObject as THandlerInterface); } // Get the extension reference from cache if available if (_extensionCache.ContainsKey(extensionName)) { return (_extensionCache[extensionName] as THandlerInterface); } // This request is to be made on a specific extension // so we find the extension from the extension manager IServerObjectExtensionManager extnMgr = _soHelper.ServerObject as IServerObjectExtensionManager; IServerObjectExtension soe = extnMgr.FindExtensionByTypeName(extensionName); return (soe as THandlerInterface); } catch (Exception e) { _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".FindRequestHandlerDelegate()", 500, e.ToString()); throw; } } #endregion } }
Come possiamo subito notare la definizione della classe è simile a quella della SOE solo che implementerà anche le interfacce IWebRequestHandler, IRequestHandler2, IRequestHandler ed è decorata con l'attributo ServerObjectInterceptorAttribute. Init e Shutdown sono sempre presenti visto il SOI 'vive' con il servizio GIS nel quale è abilitato.
Nella region 'Utility code' sono già disponibili una funzione FindRequestHandlerDelegate ed una proprietà ServerEnvironment . FindRequestHandlerDelegate determina il corretto delegato per reindirizzare la richiesta in arrivo mentre la proprietà ServerEnvironment è utilizzata dalla funzione stessa per verificare se la richiesta è indirizzata al SO o ad una specifica SOE e quindi di conseguenza determinare il delegato corrispondente.
Vediamo ora un esempio che modifica la richiesta dopo averla fatta elaborare all'oggetto sottostante al servizio. Watermark sulla mappa generata su una richiesta di Export
namespace Studioat.ArcGis.Soi.Rest { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Runtime.InteropServices; using ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS.Server; using ESRI.ArcGIS.SOESupport; [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed.")] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:FieldNamesMustBeginWithLowerCaseLetter", Justification = "Reviewed.")] [ComVisible(true)] [Guid("b7058a41-e897-4151-bced-82ab9a03f2f9")] [ClassInterface(ClassInterfaceType.None)] [ServerObjectInterceptor("MapServer", Description = "Watermark SOI", DisplayName = "Watermark SOI", Properties = "")] public class Watermark : IServerObjectExtension, IRESTRequestHandler, IWebRequestHandler, IRequestHandler2, IRequestHandler { /// <summary> /// server object interceptor name /// </summary> private string soiName; /// <summary> /// server object Helper /// </summary> private IServerObjectHelper soHelper; /// <summary> /// server log /// </summary> private ServerLogger serverLog; /// <summary> /// extension cache of service /// </summary> private Dictionary<string, IServerObjectExtension> extensionCache = new Dictionary<string, IServerObjectExtension>(); /// <summary> /// server Environment /// </summary> private IServerEnvironment2 serverEnvironment; /// <summary> /// the first output directory ArcGIS server /// </summary> private string outputDirectory = string.Empty; /// <summary> /// Initializes a new instance of the <see cref="Watermark"/> class /// </summary> public Watermark() { this.soiName = this.GetType().Name; } /// <summary> /// Gets Server Environment /// </summary> private IServerEnvironment2 ServerEnvironment { get { if (this.serverEnvironment == null) { UID uid = new UIDClass(); uid.Value = "{32D4C328-E473-4615-922C-63C108F55E60}"; // CoCreate an EnvironmentManager and retrieve the IServerEnvironment IEnvironmentManager environmentManager = new EnvironmentManager() as IEnvironmentManager; this.serverEnvironment = environmentManager.GetEnvironment(uid) as IServerEnvironment2; } return this.serverEnvironment; } } /// <summary> /// initialize of server object interceptor /// </summary> /// <param name="pSOH">Server Object Helper</param> public void Init(IServerObjectHelper pSOH) { try { this.soHelper = pSOH; this.serverLog = new ServerLogger(); try { var se4 = this.ServerEnvironment as IServerEnvironmentEx; var dirInfos = se4.GetServerDirectoryInfos(); dirInfos.Reset(); object dirInfo = dirInfos.Next(); while (dirInfo != null) { var dinfo2 = dirInfo as IServerDirectoryInfo2; if (null != dinfo2 && dinfo2.Type == esriServerDirectoryType.esriSDTypeOutput) { this.outputDirectory = dinfo2.Path; break; } dirInfo = dirInfos.Next(); } } catch { this.outputDirectory = string.Empty; } this.outputDirectory = this.outputDirectory.Trim(); if (string.IsNullOrEmpty(this.outputDirectory)) { this.serverLog.LogMessage(ServerLogger.msgType.error, this.soiName + ".init()", 500, "OutputDirectory is empty or missing. Reset to default."); this.outputDirectory = "C:\\arcgisserver\\directories\\arcgisoutput"; } this.serverLog.LogMessage(ServerLogger.msgType.infoDetailed, this.soiName + ".init()", 500, "OutputDirectory is " + this.outputDirectory); this.serverLog.LogMessage(ServerLogger.msgType.infoStandard, this.soiName + ".init()", 200, "Initialized " + this.soiName + " SOI."); } catch (Exception e) { this.serverLog.LogMessage(ServerLogger.msgType.error, this.soiName + ".HandleRESTRequest()", 500, "Exception " + e.GetType().Name + " " + e.Message + " " + e.StackTrace); throw; } } /// <summary> /// shutdown server object interceptor /// </summary> public void Shutdown() { this.serverLog.LogMessage(ServerLogger.msgType.infoStandard, this.soiName + ".init()", 200, "Shutting down " + this.soiName + " SOI."); } #region REST interceptors /// <summary> /// get schema /// </summary> /// <returns>return schema</returns> public string GetSchema() { IRESTRequestHandler restRequestHandler = FindRequestHandlerDelegate<IRESTRequestHandler>(); if (restRequestHandler == null) { return null; } return restRequestHandler.GetSchema(); } /// <summary> /// handling rest request /// </summary> /// <param name="Capabilities">list of capabilities</param> /// <param name="resourceName">name of resource</param> /// <param name="operationName">name of operation</param> /// <param name="operationInput">input operation</param> /// <param name="outputFormat">output format</param> /// <param name="requestProperties">request properties</param> /// <param name="responseProperties">response properties</param> /// <returns>response</returns> public byte[] HandleRESTRequest(string Capabilities, string resourceName, string operationName, string operationInput, string outputFormat, string requestProperties, out string responseProperties) { try { responseProperties = null; this.serverLog.LogMessage(ServerLogger.msgType.infoStandard, this.soiName + ".HandleRESTRequest()", 200, "Request received in Sample Object Interceptor for handleRESTRequest"); // Add code to manipulate REST requests here // Find the correct delegate to forward the request too IRESTRequestHandler restRequestHandler = FindRequestHandlerDelegate<IRESTRequestHandler>(); if (restRequestHandler == null) { return null; } var response = restRequestHandler.HandleRESTRequest(Capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, out responseProperties); // Manipulate the response. // Add watermark if (operationName.Equals("export", StringComparison.CurrentCultureIgnoreCase)) { Image sourceImage = null; if (outputFormat.Equals("image", StringComparison.CurrentCultureIgnoreCase)) { sourceImage = Image.FromStream(new System.IO.MemoryStream(response)); var watermarker = new ApplyWatermark(); var watermarkedImage = watermarker.Mark(sourceImage, "(c) ESRI Inc."); var newResponse = new System.IO.MemoryStream(); watermarkedImage.Save(newResponse, sourceImage.RawFormat); return newResponse.GetBuffer(); } else if (outputFormat.Equals("json", StringComparison.CurrentCultureIgnoreCase)) { var responseString = System.Text.Encoding.UTF8.GetString(response); var jo = new JsonObject(responseString); string hrefString = null; if (!jo.TryGetString("href", out hrefString)) { throw new RESTErrorException("Export operation returned invalid response"); } if (string.IsNullOrEmpty(hrefString)) { throw new RESTErrorException("Export operation returned invalid response"); } // Generate output file location var outputImageFileLocation = this.GetOutputImageFileLocation(hrefString); var watermarker = new ApplyWatermark(); Image watermarkedImage; System.Drawing.Imaging.ImageFormat sourceImageFormat; using (sourceImage = Image.FromFile(outputImageFileLocation)) { sourceImageFormat = sourceImage.RawFormat; watermarkedImage = watermarker.Mark(sourceImage, "(c) ESRI Inc."); } // make sure we dispose sourceImage handles before saving to it watermarkedImage.Save(outputImageFileLocation, sourceImageFormat); watermarkedImage.Dispose(); // return response as is because we have modified the file its pointing to return response; } else if (outputFormat.Equals("kmz", StringComparison.CurrentCultureIgnoreCase)) { // Note: Watermark can be added for the kmz format too. In this example we didn't // implement it. throw new RESTErrorException("Kmz format is not supported"); } else { throw new RESTErrorException("Invalid operation parameters"); } } return response; } catch (RESTErrorException restException) { responseProperties = "{\"Content-Type\":\"text/plain;charset=utf-8\"}"; return System.Text.Encoding.UTF8.GetBytes(restException.Message); } catch (Exception e) { this.serverLog.LogMessage(ServerLogger.msgType.error, this.soiName + ".HandleRESTRequest()", 500, "Exception " + e.GetType().Name + " " + e.Message + " " + e.StackTrace); throw; } } #endregion #region SOAP interceptors /// <summary> /// handling OGC request /// </summary> /// <param name="httpMethod">http Method</param> /// <param name="requestURL">request URL</param> /// <param name="queryString">query string</param> /// <param name="Capabilities">list of capabilities</param> /// <param name="requestData">request data</param> /// <param name="responseContentType">response content type</param> /// <param name="respDataType">response data type</param> /// <returns>response</returns> public byte[] HandleStringWebRequest(esriHttpMethod httpMethod, string requestURL, string queryString, string Capabilities, string requestData, out string responseContentType, out esriWebResponseDataType respDataType) { this.serverLog.LogMessage(ServerLogger.msgType.infoStandard, this.soiName + ".HandleStringWebRequest()", 200, "Request received in Sample Object Interceptor for HandleStringWebRequest"); /* * Add code to manipulate requests here */ IWebRequestHandler webRequestHandler = FindRequestHandlerDelegate<IWebRequestHandler>(); if (webRequestHandler != null) { return webRequestHandler.HandleStringWebRequest(httpMethod, requestURL, queryString, Capabilities, requestData, out responseContentType, out respDataType); } responseContentType = null; respDataType = esriWebResponseDataType.esriWRDTPayload; // Insert error response here. return null; } /// <summary> /// handling soap binary /// </summary> /// <param name="request">request</param> /// <returns>response</returns> public byte[] HandleBinaryRequest(ref byte[] request) { this.serverLog.LogMessage(ServerLogger.msgType.infoStandard, this.soiName + ".HandleBinaryRequest()", 200, "Request received in Sample Object Interceptor for HandleBinaryRequest"); /* * Add code to manipulate requests here */ IRequestHandler requestHandler = FindRequestHandlerDelegate<IRequestHandler>(); if (requestHandler != null) { return requestHandler.HandleBinaryRequest(request); } // Insert error response here. return null; } /// <summary> /// handling soap binary /// </summary> /// <param name="Capabilities">list of capabilities</param> /// <param name="request">request</param> /// <returns>response</returns> public byte[] HandleBinaryRequest2(string Capabilities, ref byte[] request) { this.serverLog.LogMessage(ServerLogger.msgType.infoStandard, this.soiName + ".HandleBinaryRequest2()", 200, "Request received in Sample Object Interceptor for HandleBinaryRequest2"); /* * Add code to manipulate requests here */ IRequestHandler2 requestHandler = FindRequestHandlerDelegate<IRequestHandler2>(); if (requestHandler != null) { return requestHandler.HandleBinaryRequest2(Capabilities, request); } // Insert error response here. return null; } /// <summary> /// handler soap xml /// </summary> /// <param name="Capabilities">list of capabilities</param> /// <param name="request">request</param> /// <returns>response</returns> public string HandleStringRequest(string Capabilities, string request) { this.serverLog.LogMessage(ServerLogger.msgType.infoStandard, this.soiName + ".HandleStringRequest()", 200, "Request received in Sample Object Interceptor for HandleStringRequest"); /* * Add code to manipulate requests here */ IRequestHandler requestHandler = FindRequestHandlerDelegate<IRequestHandler>(); if (requestHandler != null) { return requestHandler.HandleStringRequest(Capabilities, request); } // Insert error response here. return null; } #endregion #region Utility code /// <summary> /// find handler rest for server object or server object extension /// </summary> /// <typeparam name="THandlerInterface">interface that implements handler rest</typeparam> /// <returns>object that implements handler rest</returns> private THandlerInterface FindRequestHandlerDelegate<THandlerInterface>() where THandlerInterface : class { try { IPropertySet props = this.ServerEnvironment.Properties; string extensionName; try { extensionName = (string)props.GetProperty("ExtensionName"); } catch (Exception /*e*/) { extensionName = null; } if (string.IsNullOrEmpty(extensionName)) { return this.soHelper.ServerObject as THandlerInterface; } // Get the extension reference from cache if available if (this.extensionCache.ContainsKey(extensionName)) { return this.extensionCache[extensionName] as THandlerInterface; } // This request is to be made on a specific extension // so we find the extension from the extension manager IServerObjectExtensionManager extnMgr = this.soHelper.ServerObject as IServerObjectExtensionManager; IServerObjectExtension soe = extnMgr.FindExtensionByTypeName(extensionName); return soe as THandlerInterface; } catch (Exception e) { this.serverLog.LogMessage(ServerLogger.msgType.error, this.soiName + ".FindRequestHandlerDelegate()", 500, e.ToString()); throw; } } /// <summary> /// Generate physical file path from virtual path /// </summary> /// <param name="virtualPath">virtualPath Path returned by the MapServer SO</param> /// <returns>physical file path</returns> private string GetOutputImageFileLocation(string virtualPath) { /* * Sample output returned by MapServer SO * example : /rest/directories/arcgisoutput/SampleWorldCities_MapServer/ * _ags_map26c62f8c2c0c4965b53e87e300e1912f.png */ var virtualPathParts = virtualPath.Split('/'); string imageFileLocation = this.outputDirectory; // build the physical path to the image file bool buildPath = false; foreach (string virtualPathPart in virtualPathParts) { if (buildPath) { imageFileLocation += "\\" + virtualPathPart; } if (virtualPathPart.Equals("arcgisoutput", StringComparison.CurrentCultureIgnoreCase)) { buildPath = true; } } return imageFileLocation; } #endregion } }
Nel codice possiamo notare come, una volta disponibile la risposta dal SO, aggiungiamo il watermark all'immagine prima di inviarla al client.
Una volta compilato lo installiamo in ArcGIS Server Manager come avviene per le SOE.
e successivamente abilitiamo il SOI nel servizio GIS.
Possiamo effettuare il test direttamente dall'export del Rest Services Directory
Link live demo
Mentre in questo esempio vediamo una PoC di come gestire un controllo a livello di layer basato su permessi impostati su file esterno.
Se si implementa il controllo a livello di layer attraverso un SOI, occorre anche disabilitare il caching di tutti i layer delle risorse inclusi nel servizio. Questo consente di intercettare le operazione e filtrare i layer che non sono consentiti. Nell'ArcGIS Server Administrator Directory occorre impostare la proprietà disableCaching a true del servizio .
I passi da seguire nel dettaglio sono:
- Aprire l'ArcGIS Server Administrator Directory (l'url è http://myserver:6080/arcgis/admin);
- cliccare su services e cliccare il nome del servizio interessato;
- cliccare edit;
- nella sezione properties del json del servizio, aggiungere la proprietà disableCaching ed impostare il valore a true
"properties": {
...
"disableCaching": "true",
...
},
- cliccare save Edits.
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 }
3 commenti:
Salve,
Sto avendo problemi con ApplyWatermark () e RESTErrorException. Avete questi definiti come classi aggiuntive?
Grazie
Damien
Se hai installato sdk arcobjects 10.3.1 il progetto ApplyWatermarkSOI lo trovi in questa cartella C:\Program Files (x86)\ArcGIS\DeveloperKit10.3\Samples\ArcObjectsNet\ServerApplyWatermarkSOI\CSharp
Molte grazie!
Posta un commento