Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users trn verification level to elevated #744

Merged
merged 1 commit into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public enum UserUpdatedEventChanges
PreferredName = 1 << 8,
TrnVerificationLevel = 1 << 9,
NationalInsuranceNumber = 1 << 10,
TrnAssociationSource = 1 << 11,
}

public enum UserUpdatedEventSource
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@page "/admin/users/{userId}/elevate"
MrKevJoy marked this conversation as resolved.
Show resolved Hide resolved
@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>

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@
<govuk-summary-list-row>
<govuk-summary-list-row-key>TRN verification level</govuk-summary-list-row-key>
<govuk-summary-list-row-value><govuk-tag class="govuk-tag--yellow">Low</govuk-tag></govuk-summary-list-row-value>
<govuk-summary-list-row-actions>
<govuk-summary-list-row-action asp-page="ElevateUserTrnVerification" asp-route-userId="@Model.UserId" visually-hidden-text="TRN verification level">Elevate</govuk-summary-list-row-action>
</govuk-summary-list-row-actions>
</govuk-summary-list-row>
}
else if(Model.EffectiveVerificationLevel == TrnVerificationLevel.Medium)
Expand Down
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);
});
}
}
Loading