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()); } } }
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 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(); }
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); }
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); }
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); }
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); }
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); }