public RateLimitResult Process(ClientRequestIdentity clientRequestIdentity, RateLimitPolicy rateLimitPolicy, CancellationToken cancellationToken)
        {
            if (clientRequestIdentity == null)
            {
                throw new ArgumentNullException(nameof(clientRequestIdentity));
            }
            if (rateLimitPolicy == null)
            {
                return(RateLimitResult.NoLimit("no rate limit policy."));
            }
            if (rateLimitPolicy.ClientIsRequired && string.IsNullOrWhiteSpace(clientRequestIdentity.ClientId))
            {
                return(RateLimitResult.Limited("client id is required."));
            }
            if (rateLimitPolicy.IsWhitelisted(clientRequestIdentity, out RateLimitNameListPolicyMatchedResult result))
            {
                return(RateLimitResult.NoLimit(result.ToString()));
            }
            var matchedRules = rateLimitPolicy.GetMatchedRules(clientRequestIdentity);

            if (matchedRules != null && matchedRules.Any())
            {
                lock (o_lock)
                {
                    var rulesDict = new Dictionary <string, Tuple <RateLimitRule, RateLimitCounter> >();
                    foreach (var rule in matchedRules)
                    {
                        if (rule.Limit > 0)
                        {
                            var counterKey = ComputeCounterKey(rule);
                            var counter    = GetCounter(counterKey, rule);
                            // check if key expired
                            if (counter.Timestamp + rule.PeriodTimespan.Value < DateTime.UtcNow)
                            {
                                continue;
                            }
                            // check if limit is reached
                            if (counter.Count > rule.Limit)
                            {
                                return(RateLimitResult.Limited(rule.ToString()));
                            }
                            rulesDict.Add(counterKey, Tuple.Create(rule, counter));
                        }
                        else
                        {
                            return(RateLimitResult.Limited(rule.ToString()));
                        }
                    }
                    if (rulesDict.Any())
                    {
                        foreach (var item in rulesDict)
                        {
                            _rateLimitCounterHandler.Set(item.Key, item.Value.Item2, item.Value.Item1.PeriodTimespan.Value);
                        }
                    }
                }
            }

            return(RateLimitResult.Default);
        }
        public string ToJson(RateLimitResult result)
        {
            return($@"{{
    ""message"": ""API rate limit reached!"",
    ""period"": ""{result.RequestPeriod}"",
    ""limit"": ""{result.RequestLimit}"",
    ""retry-after"": ""{result.GetRateLimitResetAsString()}""
}}");
        }
        private static void SetLimitHeaders(IResponse response, RateLimitResult result)
        {
            var headerResults = RateLimitHeader.Create(result?.Results);

            using (var config = JsConfig.BeginScope())
            {
                config.ExcludeTypeInfo = true;
                foreach (var header in headerResults)
                {
                    response.AddHeader(header.HeaderName, header.Limits.ToJson());
                }
            }
        }
Example #4
0
        private static void SetLimitHeaders(IResponse response, RateLimitResult result)
        {
            var headerResults = RateLimitHeader.Create(result?.Results);

            using (var config = JsConfig.BeginScope())
            {
                config.ExcludeTypeInfo = true;
                foreach (var header in headerResults)
                {
                    response.AddHeader(header.HeaderName, header.Limits.ToJson());
                }
            }
        }
Example #5
0
        private void ProcessResult(IResponse response, RateLimitResult rateLimitResult)
        {
            SetLimitHeaders(response, rateLimitResult);

            // NOTE By default we return an empty RateLimitResult which will have an 'Access' value of null.. which we default to true. Is this correct?
            if (!rateLimitResult?.Access ?? true)
            {
                if (log.IsDebugEnabled)
                {
                    var request = response.Request;
                    log.Debug(
                        $"Rate limit exceeded for {request.AbsoluteUri}, user {KeyGenerator.GetConsumerId(request)}. Returning status code: {LimitStatusCode}");
                }
                response.StatusCode        = LimitStatusCode;
                response.StatusDescription = StatusDescription;
                response.Close();
            }
        }
Example #6
0
        public void ProcessRequest_ExecutesLuaScript(string sha1, RateLimitResult rateLimitResult)
        {
            var client = A.Fake <IRedisClient>();

            A.CallTo(() => redisManager.GetClient()).Returns(client);
            A.CallTo(() => limitProvider.GetRateLimitScriptId()).Returns(sha1);

            A.CallTo(() => client.ExecLuaSha(A <string> .Ignored, A <string[]> .Ignored, A <string[]> .Ignored))
            .Returns(new RedisText {
                Text = rateLimitResult.ToJson()
            });

            var feature          = GetSut();
            var mockHttpResponse = new MockHttpResponse();

            feature.ProcessRequest(new MockHttpRequest(), mockHttpResponse, null);

            mockHttpResponse.Headers[Redis.Headers.HttpHeaders.RateLimitUser].Should().NotBeNullOrWhiteSpace();
            mockHttpResponse.Headers[Redis.Headers.HttpHeaders.RateLimitRequest].Should().NotBeNullOrWhiteSpace();
        }
Example #7
0
        protected virtual void SetRateLimitResponse(ActionExecutingContext context, RateLimitResult rateLimitResult)
        {
            var headers = context.HttpContext.Response.Headers;

            if (rateLimitResult.MaxLimit >= 0)
            {
                headers.Add("X-RateLimit-Limit", rateLimitResult.MaxLimit.ToString());
            }
            if (rateLimitResult.Remaining >= 0)
            {
                headers.Add("X-RateLimit-Remaining", rateLimitResult.Remaining.ToString());
            }
            if (rateLimitResult.ResetAfter.Ticks >= 0)
            {
                headers.Add("X-RateLimit-Reset", rateLimitResult.ResetAfter.TotalSeconds.ToString());
            }
            if (rateLimitResult.RetryAfter.Ticks >= 0)
            {
                headers.Add("Retry-After", rateLimitResult.RetryAfter.TotalSeconds.ToString());
            }
            context.Result = new StatusCodeResult(StatusCode);
        }
        private void LogRateLimitEvent(AccessRequestModel model, IUser user, RateLimitResult rateLimitResult)
        {
            AuditableAction action = new AuditableAction
            {
                User                  = user,
                IsSuccess             = false,
                RequestedComputerName = model.ComputerName,
                RequestReason         = model.UserRequestReason,
            };

            if (rateLimitResult.IsUserRateLimit)
            {
                action.EventID = EventIDs.RateLimitExceededUser;
                action.Message = string.Format(LogMessages.RateLimitExceededUser, user.MsDsPrincipalName, rateLimitResult.IPAddress, rateLimitResult.Threshold, rateLimitResult.Duration);
            }
            else
            {
                action.EventID = EventIDs.RateLimitExceededIP;
                action.Message = string.Format(LogMessages.RateLimitExceededIP, user.MsDsPrincipalName, rateLimitResult.IPAddress, rateLimitResult.Threshold, rateLimitResult.Duration);
            }

            this.reporting.GenerateAuditEvent(action);
        }
Example #9
0
        public RateLimitResult Process(string key, string period, int maxCalls)
        {
            var result = new RateLimitResult();

            var value = _memoryCache.GetOrCreate(key, c => {
                var expiration       = DateTime.Now.Add(NotationToTimeSpan.Parse(period));
                var item             = new CacheEntry(expiration);
                c.AbsoluteExpiration = expiration;
                c.Value = item;
                return(item);
            });

            value.Count++;
            var remaining = maxCalls - value.Count;

            remaining = remaining > 0 ? remaining : 0;
            return(new RateLimitResult {
                IsSuccess = value.Count <= maxCalls,
                RateLimitReset = value.AbsoluteExpiration,
                RequestLimit = maxCalls,
                RequestPeriod = period,
                RequestsRemaining = remaining
            });
        }
        private static RateLimitResult RunTest(int?intervalLimit, RateLimitLoadTest test)
        {
            var parallelism = test.NumberPerBurst;

            if (parallelism > 10)
            {
                parallelism = 10;
            }

            var result = new RateLimitResult();

            var limiter = new RateLimiter(maxTracesPerInterval: intervalLimit);

            var traceContext = new TraceContext(Tracer.Instance);

            var barrier = new Barrier(parallelism + 1);

            var numberPerThread = test.NumberPerBurst / parallelism;

            var workers = new Task[parallelism];

            for (int i = 0; i < workers.Length; i++)
            {
                workers[i] = Task.Factory.StartNew(
                    () =>
                {
                    var stopwatch = new Stopwatch();

                    for (var i = 0; i < test.NumberOfBursts; i++)
                    {
                        // Wait for every worker to be ready for next burst
                        barrier.SignalAndWait();

                        stopwatch.Restart();

                        for (int j = 0; j < numberPerThread; j++)
                        {
                            var spanContext = new SpanContext(null, traceContext, "Weeeee");
                            var span        = new Span(spanContext, null);

                            if (limiter.Allowed(span))
                            {
                                result.Allowed.Add(span.SpanId);
                            }
                            else
                            {
                                result.Denied.Add(span.SpanId);
                            }
                        }

                        var remainingTime = (test.TimeBetweenBursts - stopwatch.Elapsed).TotalMilliseconds;

                        if (remainingTime > 0)
                        {
                            Thread.Sleep((int)remainingTime);
                        }
                    }
                },
                    TaskCreationOptions.LongRunning);
            }

            // Wait for all workers to be ready
            barrier.SignalAndWait();

            var sw = Stopwatch.StartNew();

            // We do not need to synchronize with workers anymore
            barrier.RemoveParticipant();

            // Wait for workers to finish
            Task.WaitAll(workers);

            result.TimeElapsed  = sw.Elapsed;
            result.RateLimiter  = limiter;
            result.ReportedRate = limiter.GetEffectiveRate();

            return(result);
        }
        private void ProcessResult(IResponse response, RateLimitResult rateLimitResult)
        {
            SetLimitHeaders(response, rateLimitResult);

            // NOTE By default we return an empty RateLimitResult which will have an 'Access' value of null.. which we default to true. Is this correct?
            if (!rateLimitResult?.Access ?? true)
            {
                if (log.IsDebugEnabled)
                {
                    var request = response.Request;
                    log.Debug(
                        $"Rate limit exceeded for {request.AbsoluteUri}, user {KeyGenerator.GetConsumerId(request)}. Returning status code: {LimitStatusCode}");
                }
                response.StatusCode = LimitStatusCode;
                response.StatusDescription = StatusDescription;
                response.Close();
            }
        }
        public void Results_DefaultedToEmptyArray()
        {
            var results = new RateLimitResult();

            results.Results.Should().BeEmpty();
        }
        public async Task Invoke(HttpContext httpContext)
        {
            // check if rate limiting is enabled
            if (_options == null)
            {
                await _next.Invoke(httpContext);

                return;
            }

            // get request details
            var clientRequest = httpContext.GetClientRequest(_options.ClientIdHeader);

            // check white list
            if (_processor.IsWhitelisted(clientRequest))
            {
                await _next.Invoke(httpContext);

                return;
            }

            var             rules  = _processor.GetMatchingRules(clientRequest);
            RateLimitResult result = null;

            foreach (var rule in rules)
            {
                // if limit is zero or less, block the request.
                if (rule.Limit <= 0)
                {
                    // log blocked request
                    LogBlockedRequest(httpContext, clientRequest, rule);

                    // return quote exceeded
                    await ReturnQuotaExceededResponse(httpContext, rule);

                    return;
                }

                // process request
                result = await _processor.ProcessRequestAsync(clientRequest, rule);

                // check if limit is exceeded
                if (!result.Success)
                {
                    //compute retry after value
                    var retryAfter = Convert.ToInt32((result.Expiry - DateTime.UtcNow).TotalSeconds).ToString(CultureInfo.InvariantCulture);

                    // log blocked request
                    LogBlockedRequest(httpContext, clientRequest, rule);

                    // return quote exceeded
                    await ReturnQuotaExceededResponse(httpContext, rule, retryAfter);

                    return;
                }
            }

            // set X-Rate-Limit headers
            if (result != null && !_options.DisableRateLimitHeaders)
            {
                var rule    = rules.Last();
                var headers = new RateLimitHeaders
                {
                    Reset     = result.Expiry.ToString("o", DateTimeFormatInfo.InvariantInfo),
                    Limit     = rule.Period,
                    Remaining = result.Remaining.ToString()
                };

                httpContext.Response.OnStarting(state => {
                    try
                    {
                        var context = (HttpContext)state;
                        context.Response.Headers["X-Rate-Limit-Limit"]     = headers.Limit;
                        context.Response.Headers["X-Rate-Limit-Remaining"] = headers.Remaining;
                        context.Response.Headers["X-Rate-Limit-Reset"]     = headers.Reset;
                    }
                    catch
                    {
                        // ignore exception adding headers
                    }
                    return(Task.FromResult(0));
                }, httpContext);
            }

            await _next.Invoke(httpContext);
        }
 public void Results_DefaultedToEmptyArray()
 {
     var results = new RateLimitResult();
     results.Results.Should().BeEmpty();
 }
        private static RateLimitResult RunTest(int?intervalLimit, RateLimitLoadTest test)
        {
            var parallelism = test.NumberPerBurst;

            if (parallelism > 10)
            {
                parallelism = 10;
            }

            var resetEvent = new ManualResetEventSlim(initialState: false); // Start blocked

            var workerReps = Enumerable.Range(1, parallelism).ToArray();

            var registry = new ConcurrentQueue <Thread>();

            var result = new RateLimitResult();

            var start   = DateTime.Now;
            var limiter = new RateLimiter(maxTracesPerInterval: intervalLimit);
            var end     = DateTime.Now;
            var endLock = new object();

            var traceContext = new TraceContext(Tracer.Instance);

            for (var i = 0; i < test.NumberOfBursts; i++)
            {
                var remaining = test.NumberPerBurst;

                var workers =
                    workerReps
                    .Select(t => new Thread(
                                thread =>
                {
                    resetEvent.Wait();
                    while (remaining > 0)
                    {
                        Interlocked.Decrement(ref remaining);

                        var spanContext = new SpanContext(null, traceContext, "Weeeee");
                        var span        = new Span(spanContext, null);

                        if (limiter.Allowed(span))
                        {
                            result.Allowed.Add(span.SpanId);
                        }
                        else
                        {
                            result.Denied.Add(span.SpanId);
                        }
                    }

                    lock (endLock)
                    {
                        end = DateTime.Now;
                    }
                }));

                foreach (var worker in workers)
                {
                    registry.Enqueue(worker);
                    worker.Start();
                }

                resetEvent.Set();

                Thread.Sleep(test.TimeBetweenBursts);

                resetEvent.Reset();
            }

            while (!registry.IsEmpty)
            {
                if (registry.TryDequeue(out var item))
                {
                    if (item.IsAlive)
                    {
                        registry.Enqueue(item);
                    }
                }
            }

            result.RateLimiter  = limiter;
            result.ReportedRate = limiter.GetEffectiveRate();
            result.TimeElapsed  = end.Subtract(start);

            return(result);
        }
        public void ProcessRequest_ExecutesLuaScript(string sha1, RateLimitResult rateLimitResult)
        {
            var client = A.Fake<IRedisClient>();
            A.CallTo(() => redisManager.GetClient()).Returns(client);
            A.CallTo(() => limitProvider.GetRateLimitScriptId()).Returns(sha1);

            A.CallTo(() => client.ExecLuaSha(A<string>.Ignored, A<string[]>.Ignored, A<string[]>.Ignored))
                .Returns(new RedisText { Text = rateLimitResult.ToJson() });

            var feature = GetSut();
            var mockHttpResponse = new MockHttpResponse();
            feature.ProcessRequest(new MockHttpRequest(), mockHttpResponse, null);

            mockHttpResponse.Headers[Redis.Headers.HttpHeaders.RateLimitUser].Should().NotBeNullOrWhiteSpace();
            mockHttpResponse.Headers[Redis.Headers.HttpHeaders.RateLimitRequest].Should().NotBeNullOrWhiteSpace();
        }
        private static RateLimitResult RunTest(int?intervalLimit, RateLimitLoadTest test)
        {
            var parallelism = test.NumberPerBurst;

            if (parallelism > 10)
            {
                parallelism = 10;
            }

            var limiter         = new RateLimiter(maxTracesPerInterval: intervalLimit);
            var barrier         = new Barrier(parallelism + 1);
            var numberPerThread = test.NumberPerBurst / parallelism;
            var workers         = new Task[parallelism];
            int totalAttempted  = 0;
            int totalAllowed    = 0;

            for (int i = 0; i < workers.Length; i++)
            {
                workers[i] = Task.Factory.StartNew(
                    () =>
                {
                    var stopwatch = new Stopwatch();

                    for (var i = 0; i < test.NumberOfBursts; i++)
                    {
                        // Wait for every worker to be ready for next burst
                        barrier.SignalAndWait();

                        stopwatch.Restart();

                        for (int j = 0; j < numberPerThread; j++)
                        {
                            // trace id and span id are not used in rate-limiting
                            var spanContext = new SpanContext(traceId: 1, spanId: 1, serviceName: "Weeeee");

                            // pass a specific start time since there is no TraceContext
                            var span = new Span(spanContext, DateTimeOffset.UtcNow);

                            Interlocked.Increment(ref totalAttempted);

                            if (limiter.Allowed(span))
                            {
                                Interlocked.Increment(ref totalAllowed);
                            }
                        }

                        var remainingTime = (test.TimeBetweenBursts - stopwatch.Elapsed).TotalMilliseconds;

                        if (remainingTime > 0)
                        {
                            Thread.Sleep((int)remainingTime);
                        }
                    }
                },
                    TaskCreationOptions.LongRunning);
            }

            // Wait for all workers to be ready
            barrier.SignalAndWait();

            var sw = Stopwatch.StartNew();

            // We do not need to synchronize with workers anymore
            barrier.RemoveParticipant();

            // Wait for workers to finish
            Task.WaitAll(workers);

            var result = new RateLimitResult
            {
                TimeElapsed    = sw.Elapsed,
                RateLimiter    = limiter,
                ReportedRate   = limiter.GetEffectiveRate(),
                TotalAttempted = totalAttempted,
                TotalAllowed   = totalAllowed
            };

            return(result);
        }
Example #18
0
        private static RateLimitResult RunTest(int?intervalLimit, RateLimitLoadTest test)
        {
            var parallelism = test.NumberPerBurst;

            if (parallelism > Environment.ProcessorCount)
            {
                parallelism = Environment.ProcessorCount;
            }

            var clock = new SimpleClock();

            var limiter         = new RateLimiter(maxTracesPerInterval: intervalLimit);
            var barrier         = new Barrier(parallelism + 1, _ => clock.UtcNow += test.TimeBetweenBursts);
            var numberPerThread = test.NumberPerBurst / parallelism;
            var workers         = new Task[parallelism];
            int totalAttempted  = 0;
            int totalAllowed    = 0;

            for (int i = 0; i < workers.Length; i++)
            {
                workers[i] = Task.Factory.StartNew(
                    () =>
                {
                    using var lease = Clock.SetForCurrentThread(clock);

                    for (var i = 0; i < test.NumberOfBursts; i++)
                    {
                        // Wait for every worker to be ready for next burst
                        barrier.SignalAndWait();

                        for (int j = 0; j < numberPerThread; j++)
                        {
                            // trace id and span id are not used in rate-limiting
                            var spanContext = new SpanContext(TraceId.CreateFromInt(1), spanId: 1, serviceName: "Weeeee");

                            // pass a specific start time since there is no TraceContext
                            var span = new Span(spanContext, DateTimeOffset.UtcNow);

                            Interlocked.Increment(ref totalAttempted);

                            if (limiter.Allowed(span))
                            {
                                Interlocked.Increment(ref totalAllowed);
                            }
                        }
                    }
                },
                    TaskCreationOptions.LongRunning);
            }

            // Wait for all workers to be ready
            barrier.SignalAndWait();

            // We do not need to synchronize with workers anymore
            barrier.RemoveParticipant();

            // Wait for workers to finish
            Task.WaitAll(workers);

            var result = new RateLimitResult
            {
                RateLimiter    = limiter,
                ReportedRate   = limiter.GetEffectiveRate(),
                TotalAttempted = totalAttempted,
                TotalAllowed   = totalAllowed
            };

            return(result);
        }
        private AuthorizationResponse BuildAuthZResponseRateLimitExceeded(IUser user, IComputer computer, AccessMask requestedAccess, RateLimitResult result, IPAddress ip, SecurityDescriptorTarget matchedTarget)
        {
            this.logger.LogError(result.IsUserRateLimit ? EventIDs.RateLimitExceededUser : EventIDs.RateLimitExceededIP, $"User {user.MsDsPrincipalName} on IP {ip} is denied {requestedAccess} access for computer {computer.MsDsPrincipalName} because they have exceeded the {(result.IsUserRateLimit ? "user" : "IP")} rate limit of {result.Threshold}/{result.Duration.TotalSeconds} seconds");

            AuthorizationResponse response = AuthorizationResponse.CreateAuthorizationResponse(requestedAccess);

            response.Code = result.IsUserRateLimit ? AuthorizationResponseCode.UserRateLimitExceeded : AuthorizationResponseCode.IpRateLimitExceeded;
            response.NotificationChannels = this.GetNotificationRecipients(matchedTarget.Notifications, false);

            return(response);
        }