Skip to content

Commit 359dfa6

Browse files
Merge pull request #1003 from TechnologyEnhancedLearning/Dev/Fixes/TD-3730-No-Rate-Limiting-Enabled
TD-3730: No Rate Limiting Enabled
2 parents ee5c721 + 3721852 commit 359dfa6

File tree

7 files changed

+138
-15
lines changed

7 files changed

+138
-15
lines changed

LearningHub.Nhs.WebUI/Controllers/HomeController.cs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ namespace LearningHub.Nhs.WebUI.Controllers
1515
using LearningHub.Nhs.WebUI.Helpers;
1616
using LearningHub.Nhs.WebUI.Interfaces;
1717
using LearningHub.Nhs.WebUI.Models;
18-
using Microsoft.ApplicationInsights.AspNetCore;
1918
using Microsoft.AspNetCore.Authentication;
2019
using Microsoft.AspNetCore.Authentication.Cookies;
2120
using Microsoft.AspNetCore.Authorization;
2221
using Microsoft.AspNetCore.Diagnostics;
2322
using Microsoft.AspNetCore.Hosting;
2423
using Microsoft.AspNetCore.Mvc;
24+
using Microsoft.Extensions.Configuration;
2525
using Microsoft.Extensions.Logging;
2626
using Microsoft.Extensions.Options;
2727
using Microsoft.FeatureManagement;
@@ -39,6 +39,7 @@ public class HomeController : BaseController
3939
private readonly IDashboardService dashboardService;
4040
private readonly IContentService contentService;
4141
private readonly IFeatureManager featureManager;
42+
private readonly Microsoft.Extensions.Configuration.IConfiguration configuration;
4243

4344
/// <summary>
4445
/// Initializes a new instance of the <see cref="HomeController"/> class.
@@ -53,6 +54,7 @@ public class HomeController : BaseController
5354
/// <param name="dashboardService">Dashboard service.</param>
5455
/// <param name="contentService">Content service.</param>
5556
/// <param name="featureManager"> featureManager.</param>
57+
/// <param name="configuration"> config.</param>
5658
public HomeController(
5759
IHttpClientFactory httpClientFactory,
5860
IWebHostEnvironment hostingEnvironment,
@@ -63,7 +65,8 @@ public HomeController(
6365
LearningHubAuthServiceConfig authConfig,
6466
IDashboardService dashboardService,
6567
IContentService contentService,
66-
IFeatureManager featureManager)
68+
IFeatureManager featureManager,
69+
Microsoft.Extensions.Configuration.IConfiguration configuration)
6770
: base(hostingEnvironment, httpClientFactory, logger, settings.Value)
6871
{
6972
this.authConfig = authConfig;
@@ -72,6 +75,7 @@ public HomeController(
7275
this.dashboardService = dashboardService;
7376
this.contentService = contentService;
7477
this.featureManager = featureManager;
78+
this.configuration = configuration;
7579
}
7680

7781
/// <summary>
@@ -133,11 +137,12 @@ public IActionResult CreateAccount()
133137
public IActionResult Error(int? httpStatusCode)
134138
{
135139
string originalPathUrlMessage = null;
136-
140+
string originalPath = null;
137141
if (httpStatusCode.HasValue && httpStatusCode.Value == 404)
138142
{
139143
var exceptionHandlerPathFeature = this.HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
140-
originalPathUrlMessage = $"Page Not Found url: {exceptionHandlerPathFeature?.OriginalPath}. ";
144+
originalPath = exceptionHandlerPathFeature?.OriginalPath;
145+
originalPathUrlMessage = $"Page Not Found url: {originalPath}. ";
141146
}
142147

143148
if (this.User.Identity.IsAuthenticated)
@@ -165,16 +170,26 @@ public IActionResult Error(int? httpStatusCode)
165170
}
166171
else
167172
{
168-
this.ViewBag.ErrorHeader = httpStatusCode.Value switch
173+
if (originalPath == "/TooManyRequests")
169174
{
170-
401 => "You do not have permission to access this page",
171-
404 => "We cannot find the page you are looking for",
172-
_ => "We cannot find the page you are looking for",
173-
};
175+
this.ViewBag.Period = this.configuration["IpRateLimiting:GeneralRules:0:Period"];
176+
this.ViewBag.Limit = this.configuration["IpRateLimiting:GeneralRules:0:Limit"];
174177

175-
this.ViewBag.HttpStatusCode = httpStatusCode.Value;
176-
this.ViewBag.HomePageUrl = "/home";
177-
return this.View("CustomError");
178+
return this.View("TooManyRequests");
179+
}
180+
else
181+
{
182+
this.ViewBag.ErrorHeader = httpStatusCode.Value switch
183+
{
184+
401 => "You do not have permission to access this page",
185+
404 => "We cannot find the page you are looking for",
186+
_ => "We cannot find the page you are looking for",
187+
};
188+
189+
this.ViewBag.HttpStatusCode = httpStatusCode.Value;
190+
this.ViewBag.HomePageUrl = "/home";
191+
return this.View("CustomError");
192+
}
178193
}
179194
}
180195

LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104

105105

106106
<ItemGroup>
107+
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
107108
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
108109
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
109110
<PackageReference Include="elfhHub.Nhs.Models" Version="3.0.9" />
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
namespace LearningHub.Nhs.WebUI.Middleware
2+
{
3+
using System.Threading.Tasks;
4+
using AspNetCoreRateLimit;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.Extensions.Logging;
7+
using Microsoft.Extensions.Options;
8+
9+
/// <summary>
10+
/// Defines the <see cref="LHIPRateLimitMiddleware" />.
11+
/// </summary>
12+
public class LHIPRateLimitMiddleware : IpRateLimitMiddleware
13+
{
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="LHIPRateLimitMiddleware"/> class.
16+
/// </summary>
17+
/// <param name="next">The next.</param>
18+
/// <param name="processingStrategy">The processingStrategy.</param>
19+
/// <param name="options">The options.</param>
20+
/// <param name="policyStore">The policyStore.</param>
21+
/// <param name="config">The config.</param>
22+
/// <param name="logger">The logger.</param>
23+
public LHIPRateLimitMiddleware(
24+
RequestDelegate next,
25+
IProcessingStrategy processingStrategy,
26+
IOptions<IpRateLimitOptions> options,
27+
IIpPolicyStore policyStore,
28+
IRateLimitConfiguration config,
29+
ILogger<IpRateLimitMiddleware> logger)
30+
: base(
31+
next,
32+
processingStrategy,
33+
options,
34+
policyStore,
35+
config,
36+
logger)
37+
{
38+
}
39+
40+
/// <summary>
41+
/// The ReturnQuotaExceededResponse method.
42+
/// </summary>
43+
/// <param name="httpContext">The httpContext.</param>
44+
/// <param name="rule">The rule.</param>
45+
/// <param name="retryAfter">The retryAfter.</param>
46+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
47+
public override Task ReturnQuotaExceededResponse(
48+
HttpContext httpContext,
49+
RateLimitRule rule,
50+
string retryAfter)
51+
{
52+
httpContext.Response.Headers["Location"] = "/TooManyRequests";
53+
httpContext.Response.StatusCode = 302;
54+
return httpContext.Response.WriteAsync(string.Empty);
55+
}
56+
}
57+
}

LearningHub.Nhs.WebUI/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
app.UseAuthorization();
8585

8686
app.UseMiddleware<NLogMiddleware>();
87-
87+
app.UseMiddleware<LHIPRateLimitMiddleware>();
8888
app.UseStaticFiles();
8989

9090
app.Map(TimezoneInfoMiddleware.TimezoneInfoUrl, b => b.UseMiddleware<TimezoneInfoMiddleware>());

LearningHub.Nhs.WebUI/ServiceCollectionExtension.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.IdentityModel.Tokens.Jwt;
55
using System.Net;
6+
using AspNetCoreRateLimit;
67
using LearningHub.Nhs.Caching;
78
using LearningHub.Nhs.Models.Binders;
89
using LearningHub.Nhs.Models.Enums;
@@ -110,6 +111,8 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur
110111
}
111112
});
112113

114+
ConfigureIpRateLimiting(services, configuration);
115+
113116
// this method setup so httpcontext is available from controllers
114117
services.AddHttpContextAccessor();
115118
services.AddSingleton(learningHubAuthSvcConf);
@@ -136,5 +139,17 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur
136139

137140
services.AddFeatureManagement();
138141
}
142+
143+
/// <summary>
144+
/// ConfigureIpRateLimiting.
145+
/// </summary>
146+
/// <param name="services">The services.</param>
147+
/// <param name="configuration">The configuration.</param>
148+
private static void ConfigureIpRateLimiting(IServiceCollection services, IConfiguration configuration)
149+
{
150+
services.Configure<IpRateLimitOptions>(configuration.GetSection("IpRateLimiting"));
151+
services.AddInMemoryRateLimiting();
152+
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
153+
}
139154
}
140155
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
@{
2+
ViewData["Title"] = "Reset limit reached";
3+
// Get the value from ViewBag
4+
var period = ViewBag.Period.ToString();
5+
6+
// Remove the last character (if the string is not empty)
7+
if (!string.IsNullOrEmpty(period) && period.Length > 0)
8+
{
9+
period = period.Substring(0, period.Length - 1);
10+
}
11+
}
12+
<div class="bg-white">
13+
<div class="nhsuk-width-container app-width-container">
14+
<div class="nhsuk-grid-row">
15+
<div class="nhsuk-grid-column-full nhsuk-u-padding-top-9 nhsuk-u-padding-bottom-7">
16+
<h1 class="nhsuk-heading-xl"> @ViewData["Title"]</h1>
17+
<p>You've requested the maximum number of password resets (@ViewBag.Limit). Please wait @period minutes before trying again or contact the <a href="@ViewBag.SupportFormUrl" target="_blank">support team</a>.</p>
18+
<p>@DateTimeOffset.Now.ToString("d MMMM yyyy HH:mm:ss")</p>
19+
</div>
20+
</div>
21+
</div>
22+
</div>

LearningHub.Nhs.WebUI/appsettings.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
},
114114
"EnableTempDebugging": "false",
115115
"LimitScormToAdmin": "false"
116-
116+
117117
},
118118
"LearningHubAuthServiceConfig": {
119119
"Authority": "",
@@ -158,5 +158,18 @@
158158
"FeatureManagement": {
159159
"ContributeAudioVideoResource": true,
160160
"DisplayAudioVideoResource": true
161-
}
161+
},
162+
"IpRateLimiting": {
163+
"EnableEndpointRateLimiting": true,
164+
"StackBlockedRequests": false,
165+
"RealIpHeader": "X-Real-IP",
166+
"HttpStatusCode": 429,
167+
"GeneralRules": [
168+
{
169+
"Endpoint": "post:/Account/ForgotPassword",
170+
"Period": "1m",
171+
"Limit": 5
172+
}
173+
]
174+
}
162175
}

0 commit comments

Comments
 (0)