-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow users trn verification level to elevated
- Loading branch information
Showing
5 changed files
with
223 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
...t-authserver/src/TeacherIdentity.AuthServer/Pages/Admin/ElevateUserTrnVerification.cshtml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
@page "/admin/users/{userId}/elevate" | ||
@model TeacherIdentity.AuthServer.Pages.Admin.ElevateUserTrnVerificationModel | ||
@{ | ||
ViewBag.Title = "Confirm TRN verification level elevation"; | ||
} | ||
|
||
@section BeforeContent { | ||
<govuk-back-link asp-page="SelectUser" asp-route-userId="@Model.UserId" /> | ||
} | ||
|
||
<div class="govuk-grid-row"> | ||
<div class="govuk-grid-column-two-thirds-from-desktop"> | ||
<form asp-page="Confirm" method="post"> | ||
<h1 class="govuk-heading-l">@ViewBag.Title</h1> | ||
|
||
<govuk-summary-list> | ||
<govuk-summary-list-row> | ||
<govuk-summary-list-row-key>Email address</govuk-summary-list-row-key> | ||
<govuk-summary-list-row-value>@Model.User!.EmailAddress</govuk-summary-list-row-value> | ||
</govuk-summary-list-row> | ||
<govuk-summary-list-row> | ||
<govuk-summary-list-row-key>Name</govuk-summary-list-row-key> | ||
<govuk-summary-list-row-value>@Model.User!.FirstName @Model.User!.LastName</govuk-summary-list-row-value> | ||
</govuk-summary-list-row> | ||
<govuk-summary-list-row> | ||
<govuk-summary-list-row-key>TRN</govuk-summary-list-row-key> | ||
<govuk-summary-list-row-value>@(Model.User!.Trn ?? "None")</govuk-summary-list-row-value> | ||
</govuk-summary-list-row> | ||
</govuk-summary-list> | ||
|
||
<govuk-button type="submit">Continue</govuk-button> | ||
</form> | ||
</div> | ||
</div> | ||
|
80 changes: 80 additions & 0 deletions
80
...uthserver/src/TeacherIdentity.AuthServer/Pages/Admin/ElevateUserTrnVerification.cshtml.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.AspNetCore.Mvc.Filters; | ||
using Microsoft.AspNetCore.Mvc.RazorPages; | ||
using Microsoft.EntityFrameworkCore; | ||
using TeacherIdentity.AuthServer.Infrastructure.Security; | ||
using TeacherIdentity.AuthServer.Models; | ||
|
||
namespace TeacherIdentity.AuthServer.Pages.Admin; | ||
|
||
[Authorize(AuthorizationPolicies.GetAnIdentityAdmin)] | ||
public class ElevateUserTrnVerificationModel : PageModel | ||
{ | ||
private readonly TeacherIdentityServerDbContext _dbContext; | ||
private readonly IClock _clock; | ||
public new User? User { get; set; } | ||
[FromRoute] | ||
public Guid UserId { get; set; } | ||
|
||
public ElevateUserTrnVerificationModel(TeacherIdentityServerDbContext dbContext, IClock clock) | ||
{ | ||
_dbContext = dbContext; | ||
_clock = clock; | ||
} | ||
|
||
public void OnGet() | ||
{ | ||
} | ||
|
||
public async Task<IActionResult> OnPost() | ||
{ | ||
if (!ModelState.IsValid) | ||
{ | ||
return this.PageWithErrors(); | ||
} | ||
|
||
User!.TrnVerificationLevel = null; | ||
User!.TrnAssociationSource = TrnAssociationSource.SupportUi; | ||
_dbContext.AddEvent(new Events.UserUpdatedEvent | ||
{ | ||
Source = Events.UserUpdatedEventSource.SupportUi, | ||
CreatedUtc = _clock.UtcNow, | ||
Changes = Events.UserUpdatedEventChanges.TrnVerificationLevel | Events.UserUpdatedEventChanges.TrnAssociationSource, | ||
User = User, | ||
UpdatedByUserId = HttpContext.User.GetUserId(), | ||
UpdatedByClientId = null | ||
}); | ||
await _dbContext.SaveChangesAsync(); | ||
|
||
TempData.SetFlashSuccess("TRN verification level elevated"); | ||
|
||
return RedirectToPage("/Admin/User", new { UserId }); | ||
} | ||
|
||
public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) | ||
{ | ||
User = await GetUser(UserId); | ||
|
||
if (User == null) | ||
{ | ||
context.Result = NotFound(); | ||
return; | ||
} | ||
|
||
if (User.EffectiveVerificationLevel != TrnVerificationLevel.Low) | ||
{ | ||
context.Result = BadRequest(); | ||
return; | ||
} | ||
|
||
await next(); | ||
} | ||
|
||
private async Task<User?> GetUser(Guid userId) | ||
{ | ||
var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UserId == userId); | ||
|
||
return (user is null || user.UserType != UserType.Default) ? null : user; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
...s/TeacherIdentity.AuthServer.Tests/EndpointTests/Admin/ElevateUserTrnVerificationTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
using Microsoft.EntityFrameworkCore; | ||
using TeacherIdentity.AuthServer.Events; | ||
using TeacherIdentity.AuthServer.Models; | ||
|
||
namespace TeacherIdentity.AuthServer.Tests.EndpointTests.Admin; | ||
|
||
public class ElevateUserTrnVerificationTests : TestBase | ||
{ | ||
public ElevateUserTrnVerificationTests(HostFixture hostFixture) | ||
: base(hostFixture) | ||
{ | ||
} | ||
|
||
[Fact] | ||
public async Task Get_UnauthenticatedUser_RedirectsToSignIn() | ||
{ | ||
var user = await TestData.CreateUser(userType: UserType.Default, hasTrn: false); | ||
|
||
await UnauthenticatedUser_RedirectsToSignIn(HttpMethod.Get, $"/admin/users/{user.UserId}/elevate"); | ||
} | ||
|
||
[Fact] | ||
public async Task Get_AuthenticatedUserDoesNotHavePermission_ReturnsForbidden() | ||
{ | ||
var user = await TestData.CreateUser(userType: UserType.Default, hasTrn: false); | ||
|
||
await AuthenticatedUserDoesNotHavePermission_ReturnsForbidden(HttpMethod.Get, $"/admin/users/{user.UserId}/elevate"); | ||
} | ||
|
||
[Fact] | ||
public async Task Get_UserDoesNotExist_ReturnsNotFound() | ||
{ | ||
// Arrange | ||
var userId = Guid.NewGuid(); | ||
var _ = await TestData.CreateUser(userType: UserType.Default, hasTrn: true); | ||
var request = new HttpRequestMessage(HttpMethod.Get, $"/admin/users/{userId}/elevate"); | ||
|
||
// Act | ||
var response = await HttpClient.SendAsync(request); | ||
|
||
// Assert | ||
Assert.Equal(StatusCodes.Status404NotFound, (int)response.StatusCode); | ||
} | ||
|
||
[Fact] | ||
public async Task Post_UserDoesNotExist_ReturnsNotFound() | ||
{ | ||
// Arrange | ||
var userId = Guid.NewGuid(); | ||
var request = new HttpRequestMessage(HttpMethod.Post, $"/admin/users/{userId}/elevate"); | ||
|
||
// Act | ||
var response = await HttpClient.SendAsync(request); | ||
|
||
// Assert | ||
Assert.Equal(StatusCodes.Status404NotFound, (int)response.StatusCode); | ||
} | ||
|
||
[Fact] | ||
public async Task Post_UserTrnVerificationLevelNotLow_ReturnsBadRequest() | ||
{ | ||
// Arrange | ||
var user = await TestData.CreateUser(userType: UserType.Default, hasTrn: true, trnVerificationLevel: TrnVerificationLevel.Medium); | ||
var request = new HttpRequestMessage(HttpMethod.Post, $"/admin/users/{user.UserId}/elevate"); | ||
|
||
// Act | ||
var response = await HttpClient.SendAsync(request); | ||
|
||
// Assert | ||
Assert.Equal(StatusCodes.Status400BadRequest, (int)response.StatusCode); | ||
} | ||
|
||
[Fact] | ||
public async Task Post_UserWithLowTrnVerificationLevel_UpdatesVerificationLevelRedirectsToUsers() | ||
{ | ||
// Arrange | ||
var user = await TestData.CreateUser(userType: UserType.Default, hasTrn: true, trnVerificationLevel: TrnVerificationLevel.Low); | ||
var request = new HttpRequestMessage(HttpMethod.Post, $"/admin/users/{user.UserId}/elevate"); | ||
|
||
// Act | ||
var response = await HttpClient.SendAsync(request); | ||
|
||
// Assert | ||
Assert.Equal(StatusCodes.Status302Found, (int)response.StatusCode); | ||
Assert.StartsWith($"/admin/users", response.Headers.Location?.OriginalString); | ||
await TestData.WithDbContext(async dbContext => | ||
{ | ||
var fetchedUser = await dbContext.Users.IgnoreQueryFilters().Where(u => u.UserId == user.UserId).SingleOrDefaultAsync(); | ||
Assert.NotNull(fetchedUser); | ||
Assert.Null(fetchedUser.TrnVerificationLevel); | ||
Assert.Equal(TrnVerificationLevel.Medium, fetchedUser.EffectiveVerificationLevel); | ||
Assert.Equal(TrnAssociationSource.SupportUi, fetchedUser.TrnAssociationSource); | ||
}); | ||
|
||
EventObserver.AssertEventsSaved( | ||
e => | ||
{ | ||
var userChangedEvent = Assert.IsType<UserUpdatedEvent>(e); | ||
Assert.Equal(Events.UserUpdatedEventChanges.TrnVerificationLevel | UserUpdatedEventChanges.TrnAssociationSource, userChangedEvent.Changes); | ||
Assert.Equal(user.UserId, userChangedEvent.User.UserId); | ||
Assert.Equal(Clock.UtcNow, userChangedEvent.CreatedUtc); | ||
}); | ||
} | ||
} |