From b9a98a9dff40c9fc7b53aa159c37fe71b752292e Mon Sep 17 00:00:00 2001 From: Tim McGee Date: Sat, 18 Jun 2016 18:20:39 -0700 Subject: [PATCH] Remove outdated resource proxy files. Add link to Esri resource proxy in README. Add note about proxy not available in GitHub demo. --- README.md | 5 +- resource-proxy.zip | Bin 54459 -> 0 bytes viewer/proxy/PROXY_README.md | 96 ---- viewer/proxy/Web.config | 18 - viewer/proxy/proxy.ashx | 834 ----------------------------------- viewer/proxy/proxy.config | 9 - viewer/proxy/proxy.xsd | 31 -- viewer/proxy/readme.md | 7 - 8 files changed, 4 insertions(+), 996 deletions(-) delete mode 100644 resource-proxy.zip delete mode 100755 viewer/proxy/PROXY_README.md delete mode 100755 viewer/proxy/Web.config delete mode 100755 viewer/proxy/proxy.ashx delete mode 100755 viewer/proxy/proxy.config delete mode 100755 viewer/proxy/proxy.xsd delete mode 100644 viewer/proxy/readme.md diff --git a/README.md b/README.md index efb59a017..3244f9377 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CMV - The Configurable Map Viewer -[![Read The Docs](https://img.shields.io/badge/docs-1.3.4-brightgreen.svg?style=flat)](http://docs.cmv.io/) [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/cmv/cmv-app?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](http://travis-ci.org/cmv/cmv-app.svg?branch=master)](http://travis-ci.org/cmv/cmv-app) +[![Read The Docs](https://img.shields.io/badge/docs-1.3.4-brightgreen.svg?style=flat)](http://docs.cmv.io/) [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/cmv/cmv-app?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](http://travis-ci.org/cmv/cmv-app.svg?branch=master)](http://travis-ci.org/cmv/cmv-app) ## Introduction [CMV](http://cmv.io/) is a community-supported open source mapping framework. CMV works with the [Esri JavaScript API](http://docs.cmv.io/en/latest/developers.arcgis.com/javascript/jsapi/), [ArcGIS Server](http://www.esri.com/software/arcgis/arcgisserver), [ArcGIS Online](https://arcgis.com/) and more. @@ -39,6 +39,9 @@ This JavaScript web app can be easily configured or used as a boilerplate/starti Read more about the [core widgets](http://docs.cmv.io/en/latest/widgets/). In addition, there is a growing number of [widgets contributed by the CMV developer community](https://github.com/cmv/cmv-contrib-widgets). +## Resource Proxy: +A [resource proxy](https://github.com/Esri/resource-proxy) may be required to access some MapServices and other content that reside on a different domain. A proxy is not available for the Github demo. + ## Documentation: Use the [documentation](http://docs.cmv.io/) for getting started and guidance on configuring your application. The initial documentation is sparse. Please help make it better by contributing over at the [cmv documentation repo](https://github.com/cmv/cmv-docs). diff --git a/resource-proxy.zip b/resource-proxy.zip deleted file mode 100644 index 4e127987674fecd73011da88c6b01a23f421843b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54459 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 entry for each service that will use the proxy. The proxy.config allows you to use the serverUrl tag to specify one or more ArcGIS Server services that the proxy will forward requests to. The serverUrl tag has the following attributes: - * **url**: Location of the ArcGIS Server service (or other URL) to proxy. Specify either the specific URL or the root (in which case you should set matchAll="false"). - * **matchAll="true"**: When true all requests that begin with the specified URL are forwarded. Otherwise, the URL requested must match exactly. - * **username**: Username to use when requesting a token - if needed for ArcGIS Server token based authentication. - * **password**: Password to use when requesting a token - if needed for ArcGIS Server token based authentication. - * **clientId**. Used with clientSecret for OAuth authentication to obtain a token - if needed for OAuth 2.0 authentication. **NOTE**: If used to access hosted services, the service(s) must be owned by the user accessing it, (with the exception of credit-based esri services, e.g. routing, geoenrichment, etc.) - * **clientSecret**: Used with clientId for OAuth authentication to obtain a token - if needed for OAuth 2.0 authentication. - * **oauth2Endpoint**: When using OAuth 2.0 authentication specify the portal specific OAuth 2.0 authentication endpoint. The default value is https://www.arcgis.com/sharing/oauth2/. - * **accessToken**: OAuth2 access token to use instead of on-demand access-token generation using clientId & clientSecret. - * **rateLimit**: The maximum number of requests with a particular referer over the specified **rateLimitPeriod**. - * **rateLimitPeriod**: The time period (in minutes) within which the specified number of requests (rate_limit) sent with a particular referer will be tracked. The default value is 60 (one hour). - -Note: Refresh the proxy application after updates to the proxy.config have been made. - -Example of proxy using application credentials and limiting requests to 10/minute -``` - - -``` -Example of a tag for a resource which does not require authentication -``` - - -``` -Note: You may have to refresh the proxy application after updates to the proxy.config have been made. - -##Folders and Files - -The proxy consists of the following files: -* proxy.config: This file contains the configuration settings for the proxy. This is where you will define all the resources that will use the proxy. After updating this file you might need to refresh the proxy application using IIS tools in order for the changes to take effect. -* **Important note:** In order to keep your credentials safe, ensure that your web server will not display the text inside your proxy.config in the browser (ie: http://[yourmachine]/proxy/proxy.config). -* proxy.ashx: The actual proxy application. In most cases you will not need to modify this file. -* web.config: An XML file that stores ASP.NET configuration data. Use this file to configure logging for the proxy. By default the proxy will write log messages to a file named auth_proxy.log located in 'C:\Temp\Shared\proxy_logs'. Note that the folder location needs to exist in order for the log file to be successfully created. -##Requirements - -* ASP.NET 4.0 or greater (4.5 is required on Windows 8/Server 2012, see [this article] (http://www.iis.net/learn/get-started/whats-new-in-iis-8/iis-80-using-aspnet-35-and-aspnet-45) for more information) - -##Issues - -Found a bug or want to request a new feature? Let us know by submitting an issue. - -##Contributing - -All contributions are welcome. - -##Licensing - -Copyright 2014 Esri - -Licensed under the Apache License, Version 2.0 (the "License"); -You may not use this file except in compliance with the License. -You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for specific language governing permissions and limitations under the license. diff --git a/viewer/proxy/Web.config b/viewer/proxy/Web.config deleted file mode 100755 index 8d1a40128..000000000 --- a/viewer/proxy/Web.config +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/viewer/proxy/proxy.ashx b/viewer/proxy/proxy.ashx deleted file mode 100755 index 6446e630d..000000000 --- a/viewer/proxy/proxy.ashx +++ /dev/null @@ -1,834 +0,0 @@ -<%@ WebHandler Language="C#" Class="proxy" %> - -/* - * DotNet proxy client. - * - * Version 1.1 beta - * See https://github.com/Esri/resource-proxy for more information. - * - */ - -#define TRACE -using System; -using System.IO; -using System.Web; -using System.Xml.Serialization; -using System.Web.Caching; -using System.Collections.Concurrent; -using System.Diagnostics; - -public class proxy : IHttpHandler { - - class RateMeter { - double _rate; //internal rate is stored in requests per second - int _countCap; - double _count = 0; - DateTime _lastUpdate = DateTime.Now; - - public RateMeter(int rate_limit, int rate_limit_period) { - _rate = (double) rate_limit / rate_limit_period / 60; - _countCap = rate_limit; - } - - //called when rate-limited endpoint is invoked - public bool click() { - TimeSpan ts = DateTime.Now - _lastUpdate; - _lastUpdate = DateTime.Now; - //assuming uniform distribution of requests over time, - //reducing the counter according to # of seconds passed - //since last invocation - _count = Math.Max(0, _count - ts.TotalSeconds * _rate); - if (_count <= _countCap) { - //good to proceed - _count++; - return true; - } - return false; - } - - public bool canBeCleaned() { - TimeSpan ts = DateTime.Now - _lastUpdate; - return _count - ts.TotalSeconds * _rate <= 0; - } - } - - private static string PROXY_REFERER = "http://localhost/proxy/proxy.ashx"; - private static string DEFAULT_OAUTH = "https://www.arcgis.com/sharing/oauth2/"; - private static int CLEAN_RATEMAP_AFTER = 10000; //clean the rateMap every xxxx requests - - private static Object _rateMapLock = new Object(); - - public void ProcessRequest(HttpContext context) { - HttpResponse response = context.Response; - if (context.Request.Url.Query.Length < 1) - { - string errorMsg = "No URL specified"; - log(TraceLevel.Error, errorMsg); - sendErrorResponse(context.Response, null, errorMsg, System.Net.HttpStatusCode.BadRequest); - return; - } - - string uri = context.Request.Url.Query.Substring(1); - - //if url is encoded, decode it. - if (uri.StartsWith("http%3a%2f%2f", StringComparison.InvariantCultureIgnoreCase) || uri.StartsWith("https%3a%2f%2f", StringComparison.InvariantCultureIgnoreCase)) - uri = HttpUtility.UrlDecode(uri); - - log(TraceLevel.Info, uri); - ServerUrl serverUrl; - bool passThrough = false; - try { - serverUrl = getConfig().GetConfigServerUrl(uri); - passThrough = serverUrl == null; - } - //if XML couldn't be parsed - catch (InvalidOperationException ex) { - - string errorMsg = ex.InnerException.Message + " " + uri; - log(TraceLevel.Error, errorMsg); - sendErrorResponse(context.Response, null, errorMsg, System.Net.HttpStatusCode.InternalServerError); - return; - } - //if mustMatch was set to true and URL wasn't in the list - catch (ArgumentException ex) { - string errorMsg = ex.Message + " " + uri; - log(TraceLevel.Error, errorMsg); - sendErrorResponse(context.Response, null, errorMsg, System.Net.HttpStatusCode.Forbidden); - return; - } - //use actual request header instead of a placeholder, if present - if (context.Request.Headers["referer"] != null) - PROXY_REFERER = context.Request.Headers["referer"]; - - //referer - //check against the list of referers if they have been specified in the proxy.config - String[] allowedReferersArray = ProxyConfig.GetAllowedReferersArray(); - if (allowedReferersArray != null && allowedReferersArray.Length > 0) - { - bool allowed = false; - string requestReferer = context.Request.Headers["referer"]; - - foreach (var referer in allowedReferersArray) - { - if ((allowedReferersArray.Length == 1) && referer == String.Empty) - break; - - if (requestReferer != null && requestReferer != String.Empty && (ProxyConfig.isUrlPrefixMatch(referer, requestReferer)) || referer == "*") - { - allowed = true; - break; - } - } - - if (!allowed) - { - string errorMsg = "Proxy is being used from an unsupported referer: " + context.Request.Headers["referer"]; - log(TraceLevel.Error, errorMsg); - sendErrorResponse(context.Response, null, errorMsg, System.Net.HttpStatusCode.Forbidden); - return; - } - } - - //Throttling: checking the rate limit coming from particular client IP - if (!passThrough && serverUrl.RateLimit > -1) { - lock (_rateMapLock) - { - ConcurrentDictionary ratemap = (ConcurrentDictionary)context.Application["rateMap"]; - if (ratemap == null) - { - ratemap = new ConcurrentDictionary(); - context.Application["rateMap"] = ratemap; - context.Application["rateMap_cleanup_counter"] = 0; - } - string key = "[" + serverUrl.Url + "]x[" + context.Request.UserHostAddress + "]"; - RateMeter rate; - if (!ratemap.TryGetValue(key, out rate)) - { - rate = new RateMeter(serverUrl.RateLimit, serverUrl.RateLimitPeriod); - ratemap.TryAdd(key, rate); - } - if (!rate.click()) - { - log(TraceLevel.Warning, " Pair " + key + " is throttled to " + serverUrl.RateLimit + " requests per " + serverUrl.RateLimitPeriod + " minute(s). Come back later."); - sendErrorResponse(context.Response, "This is a metered resource, number of requests have exceeded the rate limit interval.", "Unable to proxy request for requested resource", System.Net.HttpStatusCode.PaymentRequired); - return; - } - - //making sure the rateMap gets periodically cleaned up so it does not grow uncontrollably - int cnt = (int)context.Application["rateMap_cleanup_counter"]; - cnt++; - if (cnt >= CLEAN_RATEMAP_AFTER) - { - cnt = 0; - cleanUpRatemap(ratemap); - } - context.Application["rateMap_cleanup_counter"] = cnt; - } - } - - //readying body (if any) of POST request - byte[] postBody = readRequestPostBody(context); - string post = System.Text.Encoding.UTF8.GetString(postBody); - - System.Net.NetworkCredential credentials = null; - string requestUri = uri; - bool hasClientToken = false; - string token = string.Empty; - string tokenParamName = null; - - if (!passThrough && serverUrl.Domain != null) - { - credentials = new System.Net.NetworkCredential(serverUrl.Username, serverUrl.Password, serverUrl.Domain); - } - else - { - //if token comes with client request, it takes precedence over token or credentials stored in configuration - hasClientToken = uri.Contains("?token=") || uri.Contains("&token=") || post.Contains("?token=") || post.Contains("&token="); - - if (!passThrough && !hasClientToken) - { - // Get new token and append to the request. - // But first, look up in the application scope, maybe it's already there: - token = (String)context.Application["token_for_" + serverUrl.Url]; - bool tokenIsInApplicationScope = !String.IsNullOrEmpty(token); - - //if still no token, let's see if there is an access token or if are credentials stored in configuration which we can use to obtain new token - if (!tokenIsInApplicationScope) - { - token = serverUrl.AccessToken; - if (String.IsNullOrEmpty(token)) - token = getNewTokenIfCredentialsAreSpecified(serverUrl, uri); - } - - if (!String.IsNullOrEmpty(token) && !tokenIsInApplicationScope) - { - //storing the token in Application scope, to do not waste time on requesting new one untill it expires or the app is restarted. - context.Application.Lock(); - context.Application["token_for_" + serverUrl.Url] = token; - context.Application.UnLock(); - } - } - - //name by which token parameter is passed (if url actually came from the list) - tokenParamName = serverUrl != null ? serverUrl.TokenParamName : null; - - if (String.IsNullOrEmpty(tokenParamName)) - tokenParamName = "token"; - - requestUri = addTokenToUri(uri, token, tokenParamName); - } - - - - //forwarding original request - System.Net.WebResponse serverResponse = null; - try { - serverResponse = forwardToServer(context, requestUri, postBody, credentials); - } catch (System.Net.WebException webExc) { - - string errorMsg = webExc.Message + " " + uri; - log(TraceLevel.Error, errorMsg); - - if (webExc.Response != null) - { - copyHeaders(webExc.Response as System.Net.HttpWebResponse, context.Response); - - using (Stream responseStream = webExc.Response.GetResponseStream()) - { - byte[] bytes = new byte[32768]; - int bytesRead = 0; - - while ((bytesRead = responseStream.Read(bytes, 0, bytes.Length)) > 0) - { - responseStream.Write(bytes, 0, bytesRead); - } - - context.Response.StatusCode = (int)(webExc.Response as System.Net.HttpWebResponse).StatusCode; - context.Response.OutputStream.Write(bytes, 0, bytes.Length); - } - } - else - { - System.Net.HttpStatusCode statusCode = System.Net.HttpStatusCode.InternalServerError; - sendErrorResponse(context.Response, null, errorMsg, statusCode); - } - return; - } - - if (passThrough || string.IsNullOrEmpty(token) || hasClientToken) - //if token is not required or provided by the client, just fetch the response as is: - fetchAndPassBackToClient(serverResponse, response, true); - else { - //credentials for secured service have come from configuration file: - //it means that the proxy is responsible for making sure they were properly applied: - - //first attempt to send the request: - bool tokenRequired = fetchAndPassBackToClient(serverResponse, response, false); - - - //checking if previously used token has expired and needs to be renewed - if (tokenRequired) { - log(TraceLevel.Info, "Renewing token and trying again."); - //server returned error - potential cause: token has expired. - //we'll do second attempt to call the server with renewed token: - token = getNewTokenIfCredentialsAreSpecified(serverUrl, uri); - serverResponse = forwardToServer(context, addTokenToUri(uri, token, tokenParamName), postBody); - - //storing the token in Application scope, to do not waste time on requesting new one untill it expires or the app is restarted. - context.Application.Lock(); - context.Application["token_for_" + serverUrl.Url] = token; - context.Application.UnLock(); - - fetchAndPassBackToClient(serverResponse, response, true); - } - } - response.End(); - } - - public bool IsReusable { - get { return true; } - } - -/** -* Private -*/ - private byte[] readRequestPostBody(HttpContext context) { - if (context.Request.InputStream.Length > 0) { - byte[] bytes = new byte[context.Request.InputStream.Length]; - context.Request.InputStream.Read(bytes, 0, (int)context.Request.InputStream.Length); - return bytes; - } - return new byte[0]; - } - - private System.Net.WebResponse forwardToServer(HttpContext context, string uri, byte[] postBody, System.Net.NetworkCredential credentials = null) - { - return - postBody.Length > 0? - doHTTPRequest(uri, postBody, "POST", context.Request.Headers["referer"], context.Request.ContentType, credentials): - doHTTPRequest(uri, context.Request.HttpMethod, credentials); - } - - /// - /// Attempts to copy all headers from the fromResponse to the the toResponse. - /// - /// The response that we are copying the headers from - /// The response that we are copying the headers to - private void copyHeaders(System.Net.WebResponse fromResponse, HttpResponse toResponse) - { - foreach (var headerKey in fromResponse.Headers.AllKeys) - { - switch (headerKey.ToLower()) - { - case "content-type": - case "transfer-encoding": - continue; - default: - toResponse.AddHeader(headerKey, fromResponse.Headers[headerKey]); - break; - } - } - toResponse.ContentType = fromResponse.ContentType; - } - - private bool fetchAndPassBackToClient(System.Net.WebResponse serverResponse, HttpResponse clientResponse, bool ignoreAuthenticationErrors) { - if (serverResponse != null) { - copyHeaders(serverResponse, clientResponse); - using (Stream byteStream = serverResponse.GetResponseStream()) { - // Text response - if (serverResponse.ContentType.Contains("text") || - serverResponse.ContentType.Contains("json") || - serverResponse.ContentType.Contains("xml")) { - using (StreamReader sr = new StreamReader(byteStream)) { - string strResponse = sr.ReadToEnd(); - if ( - !ignoreAuthenticationErrors - && strResponse.IndexOf("{\"error\":{") > -1 - && (strResponse.IndexOf("\"code\":498") > -1 || strResponse.IndexOf("\"code\":499") > -1) - ) - return true; - clientResponse.Write(strResponse); - } - } else { - // Binary response (image, lyr file, other binary file) - - // Tell client not to cache the image since it's dynamic - clientResponse.CacheControl = "no-cache"; - byte[] buffer = new byte[32768]; - int read; - while ((read = byteStream.Read(buffer, 0, buffer.Length)) > 0) - { - clientResponse.OutputStream.Write(buffer, 0, read); - } - clientResponse.OutputStream.Close(); - } - serverResponse.Close(); - } - } - return false; - } - - private System.Net.WebResponse doHTTPRequest(string uri, string method, System.Net.NetworkCredential credentials = null) - { - byte[] bytes = null; - String contentType = null; - log(TraceLevel.Info, "Sending request!"); - - if (method.Equals("POST")) - { - String[] uriArray = uri.Split('?'); - - if (uriArray.Length > 1) - { - contentType = "application/x-www-form-urlencoded"; - String queryString = uriArray[1]; - - bytes = System.Text.Encoding.UTF8.GetBytes(queryString); - } - } - - return doHTTPRequest(uri, bytes, method, PROXY_REFERER, contentType, credentials); - } - - private System.Net.WebResponse doHTTPRequest(string uri, byte[] bytes, string method, string referer, string contentType, System.Net.NetworkCredential credentials = null) - { - System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(uri); - req.ServicePoint.Expect100Continue = false; - req.Referer = referer; - req.Method = method; - - if (credentials != null) - req.Credentials = credentials; - - if (bytes != null && bytes.Length > 0 || method == "POST") { - req.Method = "POST"; - req.ContentType = string.IsNullOrEmpty(contentType) ? "application/x-www-form-urlencoded" : contentType; - if (bytes != null && bytes.Length > 0) - req.ContentLength = bytes.Length; - using (Stream outputStream = req.GetRequestStream()) { - outputStream.Write(bytes, 0, bytes.Length); - } - } - return req.GetResponse(); - } - - private string webResponseToString(System.Net.WebResponse serverResponse) { - using (Stream byteStream = serverResponse.GetResponseStream()) { - using (StreamReader sr = new StreamReader(byteStream)) { - string strResponse = sr.ReadToEnd(); - return strResponse; - } - } - } - - private string getNewTokenIfCredentialsAreSpecified(ServerUrl su, string reqUrl) { - string token = ""; - string infoUrl = ""; - - bool isUserLogin = !String.IsNullOrEmpty(su.Username) && !String.IsNullOrEmpty(su.Password); - bool isAppLogin = !String.IsNullOrEmpty(su.ClientId) && !String.IsNullOrEmpty(su.ClientSecret); - if (isUserLogin || isAppLogin) { - log(TraceLevel.Info, "Matching credentials found in configuration file. OAuth 2.0 mode: " + isAppLogin); - if (isAppLogin) { - //OAuth 2.0 mode authentication - //"App Login" - authenticating using client_id and client_secret stored in config - su.OAuth2Endpoint = string.IsNullOrEmpty(su.OAuth2Endpoint) ? DEFAULT_OAUTH : su.OAuth2Endpoint; - if (su.OAuth2Endpoint[su.OAuth2Endpoint.Length - 1] != '/') - su.OAuth2Endpoint += "/"; - log(TraceLevel.Info, "Service is secured by " + su.OAuth2Endpoint + ": getting new token..."); - string uri = su.OAuth2Endpoint + "token?client_id=" + su.ClientId + "&client_secret=" + su.ClientSecret + "&grant_type=client_credentials&f=json"; - string tokenResponse = webResponseToString(doHTTPRequest(uri, "POST")); - token = extractToken(tokenResponse, "token"); - if (!string.IsNullOrEmpty(token)) - token = exchangePortalTokenForServerToken(token, su); - } else { - //standalone ArcGIS Server/ArcGIS Online token-based authentication - - //if a request is already being made to generate a token, just let it go - if (reqUrl.ToLower().Contains("/generatetoken")) { - string tokenResponse = webResponseToString(doHTTPRequest(reqUrl, "POST")); - token = extractToken(tokenResponse, "token"); - return token; - } - - //lets look for '/rest/' in the requested URL (could be 'rest/services', 'rest/community'...) - if (reqUrl.ToLower().Contains("/rest/")) - infoUrl = reqUrl.Substring(0, reqUrl.IndexOf("/rest/", StringComparison.OrdinalIgnoreCase)); - - //if we don't find 'rest', lets look for the portal specific 'sharing' instead - else if (reqUrl.ToLower().Contains("/sharing/")) { - infoUrl = reqUrl.Substring(0, reqUrl.IndexOf("/sharing/", StringComparison.OrdinalIgnoreCase)); - infoUrl = infoUrl + "/sharing"; - } - else - throw new ApplicationException("Unable to determine the correct URL to request a token to access private resources"); - - if (infoUrl != "") { - log(TraceLevel.Info," Querying security endpoint..."); - infoUrl += "/rest/info?f=json"; - //lets send a request to try and determine the URL of a token generator - string infoResponse = webResponseToString(doHTTPRequest(infoUrl, "GET")); - String tokenServiceUri = getJsonValue(infoResponse, "tokenServicesUrl"); - if (string.IsNullOrEmpty(tokenServiceUri)) - tokenServiceUri = getJsonValue(infoResponse, "tokenServiceUrl"); - if (tokenServiceUri != "") { - log(TraceLevel.Info," Service is secured by " + tokenServiceUri + ": getting new token..."); - string uri = tokenServiceUri + "?f=json&request=getToken&referer=" + PROXY_REFERER + "&expiration=60&username=" + su.Username + "&password=" + su.Password; - string tokenResponse = webResponseToString(doHTTPRequest(uri, "POST")); - token = extractToken(tokenResponse, "token"); - } - } - - - } - } - return token; - } - - private string exchangePortalTokenForServerToken(string portalToken, ServerUrl su) { - //ideally, we should POST the token request - log(TraceLevel.Info," Exchanging Portal token for Server-specific token for " + su.Url + "..."); - string uri = su.OAuth2Endpoint.Substring(0, su.OAuth2Endpoint.IndexOf("/oauth2/", StringComparison.OrdinalIgnoreCase)) + - "/generateToken?token=" + portalToken + "&serverURL=" + su.Url + "&f=json"; - string tokenResponse = webResponseToString(doHTTPRequest(uri, "GET")); - return extractToken(tokenResponse, "token"); - } - - private static void sendErrorResponse(HttpResponse response, String errorDetails, String errorMessage, System.Net.HttpStatusCode errorCode) - { - String message = string.Format("{{error: {{code: {0},message:\"{1}\"", (int)errorCode, errorMessage); - if (!string.IsNullOrEmpty(errorDetails)) - message += string.Format(",details:[message:\"{0}\"]", errorDetails); - message += "}}"; - response.StatusCode = (int)errorCode; - //this displays our customized error messages instead of IIS's custom errors - response.TrySkipIisCustomErrors = true; - response.Write(message); - response.Flush(); - } - - private static string getClientIp(HttpRequest request) - { - if (request == null) - return null; - string remoteAddr = request.ServerVariables["HTTP_X_FORWARDED_FOR"]; - if (string.IsNullOrWhiteSpace(remoteAddr)) - { - remoteAddr = request.ServerVariables["REMOTE_ADDR"]; - } - else - { - // the HTTP_X_FORWARDED_FOR may contain an array of IP, this can happen if you connect through a proxy. - string[] ipRange = remoteAddr.Split(','); - remoteAddr = ipRange[ipRange.Length - 1]; - } - return remoteAddr; - } - - private string addTokenToUri(string uri, string token, string tokenParamName) { - if (!String.IsNullOrEmpty(token)) - uri += uri.Contains("?")? "&" + tokenParamName + "=" + token : "?" + tokenParamName + "=" + token; - return uri; - } - - private string extractToken(string tokenResponse, string key) { - string token = getJsonValue(tokenResponse, key); - if (string.IsNullOrEmpty(token)) - log(TraceLevel.Error," Token cannot be obtained: " + tokenResponse); - else - log(TraceLevel.Info," Token obtained: " + token); - return token; - } - - private string getJsonValue(string text, string key) { - int i = text.IndexOf(key); - String value = ""; - if (i > -1) { - value = text.Substring(text.IndexOf(':', i) + 1).Trim(); - value = value.Length > 0 && value[0] == '"' ? - value.Substring(1, value.IndexOf('"', 1) - 1): - value = value.Substring(0, Math.Max(0, Math.Min(Math.Min(value.IndexOf(","), value.IndexOf("]")), value.IndexOf("}")))); - } - return value; - } - - private void cleanUpRatemap(ConcurrentDictionary ratemap) { - foreach (string key in ratemap.Keys){ - RateMeter rate = ratemap[key]; - if (rate.canBeCleaned()) - ratemap.TryRemove(key, out rate); - } - } - -/** -* Static -*/ - private static ProxyConfig getConfig() { - ProxyConfig config = ProxyConfig.GetCurrentConfig(); - if (config != null) - return config; - else - throw new ApplicationException("The proxy configuration file cannot be found, or is not readable."); - } - - //writing Log file - private static void log(TraceLevel logLevel, string msg) { - string logMessage = string.Format("{0} {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), msg); - if (TraceLevel.Error == logLevel) - { - Trace.TraceError(logMessage); - logMessageToFile(logMessage); - } - else if (TraceLevel.Warning == logLevel) - { - Trace.TraceWarning(logMessage); - logMessageToFile(logMessage); - } - else - { - Trace.TraceInformation(logMessage); - logMessageToFile(logMessage); - } - } - - private static object _lockobject = new object(); - - private static void logMessageToFile(String message) - { - //Only log messages to disk if logFile has value in configuration, otherwise log nothing. - ProxyConfig config = ProxyConfig.GetCurrentConfig(); - if (config.LogFile != null) - { - string log = config.LogFile; - if (!log.Contains("\\") || log.Contains(".\\")) - { - if (log.Contains(".\\")) //If this type of relative pathing .\log.txt - { - log = log.Replace(".\\", ""); - } - string configDirectory = HttpContext.Current.Server.MapPath("proxy.config"); //Cannot use System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath b/ config may be in a child directory - string path = configDirectory.Replace("proxy.config",""); - log = path + log; - } - - lock(_lockobject) { - using (StreamWriter sw = File.AppendText(log)) - { - sw.WriteLine(message); - } - } - } - } -} - - -[XmlRoot("ProxyConfig")] -public class ProxyConfig -{ - private static object _lockobject = new object(); - public static ProxyConfig LoadProxyConfig(string fileName) { - ProxyConfig config = null; - lock (_lockobject) { - if (System.IO.File.Exists(fileName)) { - XmlSerializer reader = new XmlSerializer(typeof(ProxyConfig)); - using (System.IO.StreamReader file = new System.IO.StreamReader(fileName)) { - try { - config = (ProxyConfig)reader.Deserialize(file); - } - catch (Exception ex) { - throw ex; - } - } - } - } - return config; - } - - public static ProxyConfig GetCurrentConfig() { - ProxyConfig config = HttpRuntime.Cache["proxyConfig"] as ProxyConfig; - if (config == null) { - string fileName = HttpContext.Current.Server.MapPath("proxy.config"); - config = LoadProxyConfig(fileName); - if (config != null) { - CacheDependency dep = new CacheDependency(fileName); - HttpRuntime.Cache.Insert("proxyConfig", config, dep); - } - } - return config; - } - - //referer - //create an array with valid referers using the allowedReferers String that is defined in the proxy.config - public static String[] GetAllowedReferersArray() - { - if (allowedReferers == null) - return null; - - return allowedReferers.Split(','); - } - - //referer - //check if URL starts with prefix... - public static bool isUrlPrefixMatch(String prefix, String uri) - { - - return uri.ToLower().StartsWith(prefix.ToLower()) || - uri.ToLower().Replace("https://", "http://").StartsWith(prefix.ToLower()) || - uri.ToLower().Substring(uri.IndexOf("//")).StartsWith(prefix.ToLower()); - } - - ServerUrl[] serverUrls; - public String logFile; - bool mustMatch; - //referer - static String allowedReferers; - - [XmlArray("serverUrls")] - [XmlArrayItem("serverUrl")] - public ServerUrl[] ServerUrls { - get { return this.serverUrls; } - set - { - this.serverUrls = value; - } - } - [XmlAttribute("mustMatch")] - public bool MustMatch { - get { return mustMatch; } - set - { mustMatch = value; } - } - - //logFile - [XmlAttribute("logFile")] - public String LogFile - { - get { return logFile; } - set - { logFile = value; } - } - - - //referer - [XmlAttribute("allowedReferers")] - public string AllowedReferers - { - get { return allowedReferers; } - set - { - allowedReferers = value; - } - } - - public ServerUrl GetConfigServerUrl(string uri) { - //split both request and proxy.config urls and compare them - string[] uriParts = uri.Split(new char[] {'/','?'}, StringSplitOptions.RemoveEmptyEntries); - string[] configUriParts = new string[] {}; - - foreach (ServerUrl su in serverUrls) { - //if a relative path is specified in the proxy.config, append what's in the request itself - if (!su.Url.StartsWith("http")) - su.Url = su.Url.Insert(0, uriParts[0]); - - configUriParts = su.Url.Split(new char[] { '/','?' }, StringSplitOptions.RemoveEmptyEntries); - - //if the request has less parts than the config, don't allow - if (configUriParts.Length > uriParts.Length) continue; - - int i = 0; - for (i = 0; i < configUriParts.Length; i++) { - - if (!configUriParts[i].ToLower().Equals(uriParts[i].ToLower())) break; - } - if (i == configUriParts.Length) { - //if the urls don't match exactly, and the individual matchAll tag is 'false', don't allow - if (configUriParts.Length == uriParts.Length || su.MatchAll) - return su; - } - } - - if (mustMatch) - throw new ArgumentException("Proxy is being used for an unsupported service:"); - - return null; - } - - -} - -public class ServerUrl { - string url; - bool matchAll; - string oauth2Endpoint; - string domain; - string username; - string password; - string clientId; - string clientSecret; - string accessToken; - string tokenParamName; - string rateLimit; - string rateLimitPeriod; - - [XmlAttribute("url")] - public string Url { - get { return url; } - set { url = value; } - } - [XmlAttribute("matchAll")] - public bool MatchAll { - get { return matchAll; } - set { matchAll = value; } - } - [XmlAttribute("oauth2Endpoint")] - public string OAuth2Endpoint { - get { return oauth2Endpoint; } - set { oauth2Endpoint = value; } - } - [XmlAttribute("domain")] - public string Domain - { - get { return domain; } - set { domain = value; } - } - [XmlAttribute("username")] - public string Username { - get { return username; } - set { username = value; } - } - [XmlAttribute("password")] - public string Password { - get { return password; } - set { password = value; } - } - [XmlAttribute("clientId")] - public string ClientId { - get { return clientId; } - set { clientId = value; } - } - [XmlAttribute("clientSecret")] - public string ClientSecret { - get { return clientSecret; } - set { clientSecret = value; } - } - [XmlAttribute("accessToken")] - public string AccessToken { - get { return accessToken; } - set { accessToken = value; } - } - [XmlAttribute("tokenParamName")] - public string TokenParamName { - get { return tokenParamName; } - set { tokenParamName = value; } - } - [XmlAttribute("rateLimit")] - public int RateLimit { - get { return string.IsNullOrEmpty(rateLimit)? -1 : int.Parse(rateLimit); } - set { rateLimit = value.ToString(); } - } - [XmlAttribute("rateLimitPeriod")] - public int RateLimitPeriod { - get { return string.IsNullOrEmpty(rateLimitPeriod)? 60 : int.Parse(rateLimitPeriod); } - set { rateLimitPeriod = value.ToString(); } - } -} diff --git a/viewer/proxy/proxy.config b/viewer/proxy/proxy.config deleted file mode 100755 index ef689f898..000000000 --- a/viewer/proxy/proxy.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/viewer/proxy/proxy.xsd b/viewer/proxy/proxy.xsd deleted file mode 100755 index d6c057fa8..000000000 --- a/viewer/proxy/proxy.xsd +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/viewer/proxy/readme.md b/viewer/proxy/readme.md deleted file mode 100644 index 630a8993d..000000000 --- a/viewer/proxy/readme.md +++ /dev/null @@ -1,7 +0,0 @@ -# AGS Proxy page help - -For full info please read here: -[https://developers.arcgis.com/javascript/jshelp/ags_proxy.html](https://developers.arcgis.com/javascript/jshelp/ags_proxy.html) - -You will more than likely need a proxy page for printing and other large cross domain requests. -There are diffrent flavors (.net, jsp, php) of the proxy page depending on your server side technology. See the above link for full details. \ No newline at end of file