Exemplo n.º 1
0
        /// <summary>
        /// Process and track REST request tracking.
        /// </summary>
        /// <example>
        /// One line of code will enforce rate limiting and blacklist a repeated bad request offender,
        /// returning a class object that contains the HttpStatusCode and messaging.
        /// <code>
        /// var jsonData = RestSecurity.RateLimit("169.128.25.23", "2018-12-01T23:59:00Z", "member/signin", restApiSettings);
        /// if (jsonData.StatusCode == HttpStatusCode.OK)
        ///	{
        ///	    // Do stuff and set the status code and messaging properties in jsonData.
        ///     // After all processing, execute ProcessRestSecurityTracking() so subsequent
        ///     // calls to RateLimit() are aware of the ultimate result of this request.
        ///	    RestSecurity.ProcessRestSecurityTracking(jsonData, "member/signin", restApiSettings);
        ///	}
        /// </code>
        /// </example>
        /// <param name="result">REST request result to process and track</param>
        /// <param name="restPath">REST path to use as an identifier so that each path has its own rate limiting, etc. (e.g. "/member/signin")</param>
        /// <param name="config">REST security configuration</param>
        public static void ProcessRestSecurityTracking(RestSecurityResult result, string restPath, RestSecurityConfig config)
        {
            // Failure, track bad requests for potential blacklisting
            if (result.StatusCode != HttpStatusCode.OK)
            {
                int failures = 0;
                var _x       = HttpContext.Current.Cache;

                if (CacheHelpers.CacheExists("FAILURES" + GetWanIpCacheName(result.WanIp, restPath)))
                {
                    failures = CacheHelpers.Cache <int>("FAILURES" + GetWanIpCacheName(result.WanIp, restPath));
                }

                failures++;

                if (failures > config.RestApiBlacklistMaxFailureCount)
                {
                    CacheHelpers.CacheDelete("FAILURES" + GetWanIpCacheName(result.WanIp, restPath));
                    CacheHelpers.CacheAdd("BLACKLISTED" + GetWanIpCacheName(result.WanIp, restPath), 0, expirationSeconds: config.RestApiBlacklistSeconds);
                }

                else
                {
                    CacheHelpers.CacheAdd("FAILURES" + GetWanIpCacheName(result.WanIp, restPath), failures, expirationSeconds: config.RestApiBlacklistMaxFailureSeconds);
                }
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// Simple way to rate limit and blacklist abusers for anonymous REST requests.
        /// Uses WAN IP address to identify requesters and track their bad requests in order to blacklist them.
        /// </summary>
        /// <example>
        /// One line of code will enforce rate limiting and blacklist a repeated bad request offender,
        /// returning a class object that contains the HttpStatusCode and messaging.
        /// <code>
        /// var jsonData = RestSecurity.RateLimit("169.128.25.23", "2018-12-01T23:59:00Z", "member/signin", restApiSettings);
        /// if (jsonData.StatusCode == HttpStatusCode.OK)
        ///	{
        ///	    // Do stuff and set the status code and messaging properties in jsonData.
        ///     // After all processing, execute ProcessRestSecurityTracking() so subsequent
        ///     // calls to RateLimit() are aware of the ultimate result of this request.
        ///	    RestSecurity.ProcessRestSecurityTracking(jsonData, "member/signin", restApiSettings);
        ///	}
        /// </code>
        /// </example>
        /// <param name="wanIpAddress">Optional WAN IP address of the caller (provided by the caller) to compare against the actual address used to make the request</param>
        /// <param name="timestamp">Optional UTC timestamp of the REST call (provided by the caller) in the format 2018-12-01T23:59:00Z</param>
        /// <param name="restPath">REST path to use as an identifier so that each path has its own rate limiting, etc. (e.g. "/member/signin")</param>
        /// <param name="config">RestSecurityConfig object</param>
        /// <param name="context">Optional HttpContext object (uses Current if not provided)</param>
        /// <returns>RestSecurityResult object with response information</returns>
        public static RestSecurityResult RateLimit(string wanIpAddress, string timestamp, string restPath, RestSecurityConfig config, HttpContext context = null)
        {
            var wanip  = ContextHelpers.EnsureAppContext(context).Request.UserHostAddress;
            var result = new RestSecurityResult {
                WanIp = wanip, StatusCode = HttpStatusCode.BadGateway, StatusText = "Bad Gateway", Message = "Invalid request"
            };

            try
            {
                if (CacheHelpers.CacheExists("BLACKLISTED" + GetWanIpCacheName(wanip, restPath)))
                {
                    result.StatusCode = HttpStatusCode.Forbidden;
                    result.StatusText = "Forbidden";
                    result.Message    = "You appear to be up to no good.";
                }

                else
                {
                    if (CacheHelpers.CacheExists(GetWanIpCacheName(wanip, restPath)) == false)
                    {
                        var newRateData = new RateLimitingCounter {
                            Counter = 0, Expiration = DateTime.Now.AddSeconds(config.RestApiRateLimitSeconds)
                        };
                        CacheHelpers.CacheAdd(GetWanIpCacheName(wanip, restPath), newRateData, expirationDateTime: newRateData.Expiration);
                    }

                    var rateData = CacheHelpers.Cache <RateLimitingCounter>(GetWanIpCacheName(wanip, restPath));
                    rateData.Counter += 1;

                    CacheHelpers.CacheDelete(GetWanIpCacheName(wanip, restPath));
                    CacheHelpers.CacheAdd(GetWanIpCacheName(wanip, restPath), rateData, expirationDateTime: rateData.Expiration);

                    if (rateData.Counter <= config.RestApiRateLimitCount)
                    {
                        // Validate WAN IP address matches actual
                        if (string.IsNullOrEmpty(wanIpAddress) || (string.IsNullOrEmpty(wanIpAddress) == false && wanip == wanIpAddress))
                        {
                            if (string.IsNullOrEmpty(timestamp))
                            {
                                // Timestamp not used, request is good...

                                result.StatusCode = HttpStatusCode.OK;
                                result.StatusText = "OK";
                                result.Message    = "";
                            }

                            else
                            {
                                // Enforce UTC timestamp format of 2018-12-01T23:59:00Z
                                if (timestamp.Length > 19 && timestamp.Contains("T") && timestamp.EndsWith("Z"))
                                {
                                    if (string.IsNullOrWhiteSpace(timestamp) == false && timestamp.IsDate() && DateTime.Parse(timestamp, null, System.Globalization.DateTimeStyles.AdjustToUniversal).DateDiff <int>(DateTime.Now.ToUniversalTime(), DateDiffComparisonType.Minutes) <= config.RestApiTimestampMinutes)
                                    {
                                        // Validate timestamp, request is good...

                                        result.StatusCode = HttpStatusCode.OK;
                                        result.StatusText = "OK";
                                        result.Message    = "";
                                    }
                                }
                            }
                        }

                        else
                        {
                            result.StatusCode = HttpStatusCode.BadRequest;
                            result.StatusText = "Bad Request";
                            result.Message    = "You are not who you claim to be.";
                        }
                    }

                    else
                    {
                        result.StatusCode = (HttpStatusCode)429;
                        result.StatusText = "Too Many Requests";
                        result.Message    = "Slow down there partner.";
                    }
                }
            }

            catch (Exception e)
            {
                result.StatusCode = HttpStatusCode.InternalServerError;
                result.StatusText = "Internal Server Error";
                result.Message    = e.Message;
            }

            ProcessRestSecurityTracking(result, restPath, config);

            return(result);
        }