Skip to content

Commit 4486daa

Browse files
authored
Merge pull request #1038 from TechnologyEnhancedLearning/Dev/Feature/TD-3723-LH-Pen-Test-Fixes
Pen test Fixes to RC branch-EnglishIvy
2 parents 2fa10e4 + 95f435c commit 4486daa

File tree

15 files changed

+336
-131
lines changed

15 files changed

+336
-131
lines changed

LearningHub.Nhs.WebUI/Controllers/HomeController.cs

Lines changed: 52 additions & 18 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

@@ -320,14 +335,19 @@ public IActionResult NhsSites()
320335
}
321336

322337
/// <summary>
323-
/// The Logout.
324-
/// This is directly referenced in the LoginWizardFilter to allow
325-
/// logouts to bypass LoginWizard redirects.
326-
/// If the name is changed, the LoginWizardFilter MUST be updated.
338+
/// The ChangePasswordAcknowledgement.
327339
/// </summary>
328340
/// <returns>The <see cref="IActionResult"/>.</returns>
329-
[AllowAnonymous]
330-
public IActionResult Logout()
341+
public IActionResult ChangePasswordAcknowledgement()
342+
{
343+
return this.View();
344+
}
345+
346+
/// <summary>
347+
/// StatusUpdate.
348+
/// </summary>
349+
/// <returns>Actionresult.</returns>
350+
public IActionResult UserLogout()
331351
{
332352
if (!(this.User?.Identity.IsAuthenticated ?? false))
333353
{
@@ -337,6 +357,20 @@ public IActionResult Logout()
337357
return new SignOutResult(new[] { CookieAuthenticationDefaults.AuthenticationScheme, "oidc" });
338358
}
339359

360+
/// <summary>
361+
/// The Logout.
362+
/// This is directly referenced in the LoginWizardFilter to allow
363+
/// logouts to bypass LoginWizard redirects.
364+
/// If the name is changed, the LoginWizardFilter MUST be updated.
365+
/// </summary>
366+
/// <returns>The <see cref="IActionResult"/>.</returns>
367+
[AllowAnonymous]
368+
public IActionResult Logout()
369+
{
370+
var redirectUri = $"{this.configuration["LearningHubAuthServiceConfig:Authority"]}/Home/SetIsPasswordUpdate?isLogout=true";
371+
return this.Redirect(redirectUri);
372+
}
373+
340374
/// <summary>
341375
/// The SessionTimeout.
342376
/// </summary>

LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
using Microsoft.AspNetCore.Hosting;
2222
using Microsoft.AspNetCore.Mvc;
2323
using Microsoft.AspNetCore.Routing;
24+
using Microsoft.Extensions.Configuration;
2425
using Microsoft.Extensions.Logging;
2526
using Microsoft.Extensions.Options;
2627
using NHSUKViewComponents.Web.ViewModels;
2728
using ChangePasswordViewModel = LearningHub.Nhs.WebUI.Models.UserProfile.ChangePasswordViewModel;
29+
using IConfiguration = Microsoft.Extensions.Configuration.IConfiguration;
2830

2931
/// <summary>
3032
/// The UserController.
@@ -43,6 +45,7 @@ public partial class MyAccountController : BaseController
4345
private readonly ISpecialtyService specialtyService;
4446
private readonly ILocationService locationService;
4547
private readonly ICacheService cacheService;
48+
private readonly IConfiguration configuration;
4649

4750
/// <summary>
4851
/// Initializes a new instance of the <see cref="MyAccountController"/> class.
@@ -61,6 +64,7 @@ public partial class MyAccountController : BaseController
6164
/// <param name="locationService">The locationService.</param>
6265
/// <param name="multiPageFormService">The multiPageFormService<see cref="IMultiPageFormService"/>.</param>
6366
/// <param name="cacheService">The cacheService<see cref="ICacheService"/>.</param>
67+
/// <param name="configuration">The cacheService<see cref="IConfiguration"/>.</param>
6468
public MyAccountController(
6569
IWebHostEnvironment hostingEnvironment,
6670
ILogger<ResourceController> logger,
@@ -75,7 +79,8 @@ public MyAccountController(
7579
ISpecialtyService specialtyService,
7680
ILocationService locationService,
7781
IMultiPageFormService multiPageFormService,
78-
ICacheService cacheService)
82+
ICacheService cacheService,
83+
IConfiguration configuration)
7984
: base(hostingEnvironment, httpClientFactory, logger, settings.Value)
8085
{
8186
this.userService = userService;
@@ -88,6 +93,7 @@ public MyAccountController(
8893
this.locationService = locationService;
8994
this.multiPageFormService = multiPageFormService;
9095
this.cacheService = cacheService;
96+
this.configuration = configuration;
9197
}
9298

9399
private string LoginWizardCacheKey => $"{this.CurrentUserId}:LoginWizard";
@@ -452,9 +458,8 @@ public async Task<IActionResult> UpdatePassword(ChangePasswordViewModel model)
452458
if (this.ModelState.IsValid)
453459
{
454460
await this.userService.UpdatePassword(model.NewPassword);
455-
456-
this.ViewBag.SuccessMessage = CommonValidationErrorMessages.PasswordSuccessMessage;
457-
return this.View("SuccessMessage");
461+
var redirectUri = $"{this.configuration["LearningHubAuthServiceConfig:Authority"]}/Home/SetIsPasswordUpdate?isLogout=false";
462+
return this.Redirect(redirectUri);
458463
}
459464
else
460465
{
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
namespace LearningHub.Nhs.WebUI.Helpers
2+
{
3+
using System;
4+
using System.Collections.Concurrent;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Authentication;
8+
using Microsoft.AspNetCore.Authentication.Cookies;
9+
10+
/// <summary>
11+
/// Defines the <see cref="InMemoryTicketStore" />.
12+
/// </summary>
13+
public class InMemoryTicketStore : ITicketStore
14+
{
15+
private readonly ConcurrentDictionary<string, AuthenticationTicket> cache;
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="InMemoryTicketStore"/> class.
19+
/// The InMemoryTicketStore.
20+
/// </summary>
21+
/// <param name="cache">the cache.</param>
22+
public InMemoryTicketStore(ConcurrentDictionary<string, AuthenticationTicket> cache)
23+
{
24+
this.cache = cache;
25+
}
26+
27+
/// <summary>
28+
/// The StoreAsync.
29+
/// </summary>
30+
/// <param name="ticket">The ticket.</param>
31+
/// <returns>The key.</returns>
32+
public async Task<string> StoreAsync(AuthenticationTicket ticket)
33+
{
34+
var ticketUserId = ticket.Principal.Claims.Where(c => c.Type == "sub")
35+
.FirstOrDefault()
36+
.Value;
37+
var matchingAuthTicket = this.cache.Values.FirstOrDefault(
38+
t => t.Principal.Claims.FirstOrDefault(
39+
c => c.Type == "sub"
40+
&& c.Value == ticketUserId) != null);
41+
if (matchingAuthTicket != null)
42+
{
43+
var cacheKey = this.cache.Where(
44+
entry => entry.Value == matchingAuthTicket)
45+
.Select(entry => entry.Key)
46+
.FirstOrDefault();
47+
this.cache.TryRemove(
48+
cacheKey,
49+
out _);
50+
}
51+
52+
var key = Guid
53+
.NewGuid()
54+
.ToString();
55+
await this.RenewAsync(
56+
key,
57+
ticket);
58+
return key;
59+
}
60+
61+
/// <summary>
62+
/// The RenewAsync.
63+
/// </summary>
64+
/// <param name="key">The key.</param>
65+
/// <param name="ticket">The ticket.</param>
66+
/// <returns>The Task.</returns>
67+
public Task RenewAsync(
68+
string key,
69+
AuthenticationTicket ticket)
70+
{
71+
this.cache.AddOrUpdate(
72+
key,
73+
ticket,
74+
(_, _) => ticket);
75+
return Task.CompletedTask;
76+
}
77+
78+
/// <summary>
79+
/// The RetrieveAsync.
80+
/// </summary>
81+
/// <param name="key">The Key.</param>
82+
/// <returns>The Task.</returns>
83+
public Task<AuthenticationTicket> RetrieveAsync(string key)
84+
{
85+
this.cache.TryGetValue(
86+
key,
87+
out var ticket);
88+
return Task.FromResult(ticket);
89+
}
90+
91+
/// <summary>
92+
/// The RemoveAsync.
93+
/// </summary>
94+
/// <param name="key">The key.</param>
95+
/// <returns>The Task.</returns>
96+
public Task RemoveAsync(string key)
97+
{
98+
this.cache.TryRemove(
99+
key,
100+
out _);
101+
return Task.CompletedTask;
102+
}
103+
}
104+
}

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

0 commit comments

Comments
 (0)