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

Use access token auth for name/DOB change requests & return reference #1281

Merged
merged 1 commit into from
Apr 15, 2024
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# API Changelog

## 20240412

### Change of name & Change of date of birth requests

These endpoints have been amended to use the ID/Teacher auth access token authorization mechanism.
The `POST /v3/teachers/<trn>/name-changes` and `POST /v3/teachers/<trn>/date-of-birth-changes` have been removed and
`POST /v3/teacher/name-changes` and `POST /v3/teacher/date-of-birth-changes` take their place.


## 20240307

### TRN Requests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class CreateDateOfBirthChangeRequestHandler(ICrmQueryDispatcher crmQueryD
{
private readonly HttpClient _downloadEvidenceFileHttpClient = httpClientFactory.CreateClient("EvidenceFiles");

public async Task Handle(CreateDateOfBirthChangeRequestCommand command)
public async Task<string> Handle(CreateDateOfBirthChangeRequestCommand command)
{
var contact = await crmQueryDispatcher.ExecuteQuery(
new GetActiveContactByTrnQuery(command.Trn, new Microsoft.Xrm.Sdk.Query.ColumnSet()));
Expand All @@ -43,7 +43,7 @@ public async Task Handle(CreateDateOfBirthChangeRequestCommand command)
evidenceFileMimeType = "application/octet-stream";
}

await crmQueryDispatcher.ExecuteQuery(new CreateDateOfBirthChangeIncidentQuery()
var (_, ticketNumber) = await crmQueryDispatcher.ExecuteQuery(new CreateDateOfBirthChangeIncidentQuery()
{
ContactId = contact.Id,
DateOfBirth = command.DateOfBirth,
Expand All @@ -52,5 +52,7 @@ await crmQueryDispatcher.ExecuteQuery(new CreateDateOfBirthChangeIncidentQuery()
EvidenceFileMimeType = evidenceFileMimeType,
FromIdentity = true
});

return ticketNumber;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class CreateNameChangeRequestHandler(ICrmQueryDispatcher crmQueryDispatch
{
private readonly HttpClient _downloadEvidenceFileHttpClient = httpClientFactory.CreateClient("EvidenceFiles");

public async Task Handle(CreateNameChangeRequestCommand command)
public async Task<string> Handle(CreateNameChangeRequestCommand command)
{
var contact = await crmQueryDispatcher.ExecuteQuery(
new GetActiveContactByTrnQuery(command.Trn, new Microsoft.Xrm.Sdk.Query.ColumnSet()));
Expand Down Expand Up @@ -50,7 +50,7 @@ public async Task Handle(CreateNameChangeRequestCommand command)
var firstName = firstAndMiddleNames[0];
var middleName = string.Join(" ", firstAndMiddleNames.Skip(1));

await crmQueryDispatcher.ExecuteQuery(new CreateNameChangeIncidentQuery()
var (_, ticketNumber) = await crmQueryDispatcher.ExecuteQuery(new CreateNameChangeIncidentQuery()
{
ContactId = contact.Id,
FirstName = firstName,
Expand All @@ -64,5 +64,7 @@ await crmQueryDispatcher.ExecuteQuery(new CreateNameChangeIncidentQuery()
EvidenceFileMimeType = evidenceFileMimeType,
FromIdentity = true
});

return ticketNumber;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Security.Claims;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using TeachingRecordSystem.Api.Infrastructure.Security;
using TeachingRecordSystem.Api.V3.Core.Operations;
using TeachingRecordSystem.Api.V3.V20240412.Requests;
using TeachingRecordSystem.Api.V3.V20240412.Responses;

namespace TeachingRecordSystem.Api.V3.V20240412.Controllers;

[Route("teacher")]
public class TeacherController : ControllerBase
{
[HttpPost("name-changes")]
[SwaggerOperation(
OperationId = "CreateNameChange",
Summary = "Create name change request",
Description = "Creates a name change request for the authenticated teacher.")]
[ProducesResponseType(typeof(CreateNameChangeResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[Authorize(AuthorizationPolicies.IdentityUserWithTrn)]
public async Task<IActionResult> CreateNameChange(
[FromBody] CreateNameChangeRequestRequest request,
[FromServices] CreateNameChangeRequestHandler handler)
{
var command = request.Adapt<CreateNameChangeRequestCommand>() with { Trn = User.FindFirstValue("trn")! };
var caseNumber = await handler.Handle(command);
var response = new CreateNameChangeResponse() { CaseNumber = caseNumber };
return Ok(response);
}

[HttpPost("date-of-birth-changes")]
[SwaggerOperation(
OperationId = "CreateDobChange",
Summary = "Create DOB change request",
Description = "Creates a date of birth change request for the authenticated teacher.")]
[ProducesResponseType(typeof(CreateDateOfBirthChangeResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[Authorize(AuthorizationPolicies.IdentityUserWithTrn)]
public async Task<IActionResult> CreateDateOfBirthChange(
[FromBody] CreateDateOfBirthChangeRequestRequest request,
[FromServices] CreateDateOfBirthChangeRequestHandler handler)
{
var command = request.Adapt<CreateDateOfBirthChangeRequestCommand>() with { Trn = User.FindFirstValue("trn")! };
var caseNumber = await handler.Handle(command);
var response = new CreateNameChangeResponse() { CaseNumber = caseNumber };
return Ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;

namespace TeachingRecordSystem.Api.V3.V20240412.Controllers;

[Route("teachers")]
public class TeachersController : ControllerBase
{
[HttpPost("name-changes")]
[RemovesFromApi]
public IActionResult CreateNameChange() => throw null!;

[HttpPost("date-of-birth-changes")]
[RemovesFromApi]
public IActionResult CreateDateOfBirthChange() => throw null!;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace TeachingRecordSystem.Api.V3.V20240412.Requests;

public record CreateDateOfBirthChangeRequestRequest
{
public string? Email { get; init; }
public required DateOnly DateOfBirth { get; init; }
public required string EvidenceFileName { get; init; }
public required string EvidenceFileUrl { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace TeachingRecordSystem.Api.V3.V20240412.Requests;

public record CreateNameChangeRequestRequest
{
public string? Email { get; init; }
public required string FirstName { get; init; }
public required string MiddleName { get; init; }
public required string LastName { get; init; }
public required string EvidenceFileName { get; init; }
public required string EvidenceFileUrl { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace TeachingRecordSystem.Api.V3.V20240412.Responses;

public record CreateDateOfBirthChangeResponse
{
public required string CaseNumber { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace TeachingRecordSystem.Api.V3.V20240412.Responses;

public record CreateNameChangeResponse
{
public required string CaseNumber { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using FluentValidation;
using TeachingRecordSystem.Api.V3.V20240412.Requests;

namespace TeachingRecordSystem.Api.V3.V20240412.Validators;

public class CreateDateOfBirthChangeRequestValidator : AbstractValidator<CreateDateOfBirthChangeRequestRequest>
{
public CreateDateOfBirthChangeRequestValidator()
{
RuleFor(r => r.DateOfBirth)
.NotNull();

RuleFor(r => r.EvidenceFileName)
.NotEmpty();

RuleFor(r => r.EvidenceFileUrl)
.NotEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using FluentValidation;
using TeachingRecordSystem.Api.V3.V20240412.Requests;

namespace TeachingRecordSystem.Api.V3.V20240412.Validators;

public class CreateNameChangeRequestValidator : AbstractValidator<CreateNameChangeRequestRequest>
{
public CreateNameChangeRequestValidator()
{
RuleFor(r => r.FirstName)
.NotEmpty();

RuleFor(r => r.LastName)
.NotEmpty();

RuleFor(r => r.EvidenceFileName)
.NotEmpty();

RuleFor(r => r.EvidenceFileUrl)
.NotEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static class VersionRegistry
[
V3MinorVersions.V20240101,
V3MinorVersions.V20240307,
V3MinorVersions.V20240412,
V3MinorVersions.VNext,
];

Expand Down Expand Up @@ -48,6 +49,7 @@ public static class V3MinorVersions
{
public const string V20240101 = "20240101";
public const string V20240307 = "20240307";
public const string V20240412 = "20240412";
public const string VNext = VNextVersion;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace TeachingRecordSystem.Core.Dqt.Queries;

public record CreateDateOfBirthChangeIncidentQuery : ICrmQuery<Guid>
public record CreateDateOfBirthChangeIncidentQuery : ICrmQuery<(Guid IncidentId, string TicketNumber)>
{
public required Guid ContactId { get; init; }
public required DateOnly DateOfBirth { get; init; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace TeachingRecordSystem.Core.Dqt.Queries;

public record CreateNameChangeIncidentQuery : ICrmQuery<Guid>
public record CreateNameChangeIncidentQuery : ICrmQuery<(Guid IncidentId, string TicketNumber)>
{
public required Guid ContactId { get; init; }
public required string FirstName { get; init; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace TeachingRecordSystem.Core.Dqt.QueryHandlers;

public class CreateDateOfBirthChangeIncidentHandler : ICrmQueryHandler<CreateDateOfBirthChangeIncidentQuery, Guid>
public class CreateDateOfBirthChangeIncidentHandler : ICrmQueryHandler<CreateDateOfBirthChangeIncidentQuery, (Guid IncidentId, string TicketNumber)>
{
private readonly ReferenceDataCache _referenceDataCache;

Expand All @@ -13,7 +13,7 @@ public CreateDateOfBirthChangeIncidentHandler(ReferenceDataCache referenceDataCa
_referenceDataCache = referenceDataCache;
}

public async Task<Guid> Execute(CreateDateOfBirthChangeIncidentQuery query, IOrganizationServiceAsync organizationService)
public async Task<(Guid IncidentId, string TicketNumber)> Execute(CreateDateOfBirthChangeIncidentQuery query, IOrganizationServiceAsync organizationService)
{
var subject = await _referenceDataCache.GetSubjectByTitle("Change of Date of Birth");

Expand Down Expand Up @@ -51,11 +51,18 @@ public async Task<Guid> Execute(CreateDateOfBirthChangeIncidentQuery query, IOrg
};

var requestBuilder = RequestBuilder.CreateTransaction(organizationService);
var createIncidentResponse = requestBuilder.AddRequest<CreateResponse>(new CreateRequest() { Target = incident });
requestBuilder.AddRequest<CreateResponse>(new CreateRequest() { Target = incident });
requestBuilder.AddRequest(new CreateRequest() { Target = document });
requestBuilder.AddRequest(new CreateRequest() { Target = annotation });
var getIncidentResponse = requestBuilder.AddRequest<RetrieveResponse>(
new RetrieveRequest()
{
Target = incident.Id.ToEntityReference(Incident.EntityLogicalName),
ColumnSet = new(Incident.Fields.TicketNumber)
});
await requestBuilder.Execute();

return createIncidentResponse.GetResponse().id;
var ticketNumber = getIncidentResponse.GetResponse().Entity.ToEntity<Incident>().TicketNumber;
return (incident.Id, ticketNumber);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace TeachingRecordSystem.Core.Dqt.QueryHandlers;

public class CreateNameChangeIncidentHandler : ICrmQueryHandler<CreateNameChangeIncidentQuery, Guid>
public class CreateNameChangeIncidentHandler : ICrmQueryHandler<CreateNameChangeIncidentQuery, (Guid IncidentId, string TicketNumber)>
{
private readonly ReferenceDataCache _referenceDataCache;

Expand All @@ -13,7 +13,7 @@ public CreateNameChangeIncidentHandler(ReferenceDataCache referenceDataCache)
_referenceDataCache = referenceDataCache;
}

public async Task<Guid> Execute(CreateNameChangeIncidentQuery query, IOrganizationServiceAsync organizationService)
public async Task<(Guid IncidentId, string TicketNumber)> Execute(CreateNameChangeIncidentQuery query, IOrganizationServiceAsync organizationService)
{
var subject = await _referenceDataCache.GetSubjectByTitle("Change of Name");

Expand Down Expand Up @@ -56,11 +56,18 @@ public async Task<Guid> Execute(CreateNameChangeIncidentQuery query, IOrganizati
};

var requestBuilder = RequestBuilder.CreateTransaction(organizationService);
var createIncidentResponse = requestBuilder.AddRequest<CreateResponse>(new CreateRequest() { Target = incident });
requestBuilder.AddRequest<CreateResponse>(new CreateRequest() { Target = incident });
requestBuilder.AddRequest(new CreateRequest() { Target = document });
requestBuilder.AddRequest(new CreateRequest() { Target = annotation });
var getIncidentResponse = requestBuilder.AddRequest<RetrieveResponse>(
new RetrieveRequest()
{
Target = incident.Id.ToEntityReference(Incident.EntityLogicalName),
ColumnSet = new(Incident.Fields.TicketNumber)
});
await requestBuilder.Execute();

return createIncidentResponse.GetResponse().id;
var ticketNumber = getIncidentResponse.GetResponse().Entity.ToEntity<Incident>().TicketNumber;
return (incident.Id, ticketNumber);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public virtual HttpClient GetHttpClientWithApiKey(string? version = null)
return client;
}

public HttpClient GetHttpClientWithIdentityAccessToken(string trn, string scope = "dqt:read")
public HttpClient GetHttpClientWithIdentityAccessToken(string trn, string scope = "dqt:read", string? version = null)
{
// The actual access tokens contain many more claims than this but these are the two we care about
var subject = new ClaimsIdentity(new[]
Expand All @@ -81,6 +81,11 @@ public HttpClient GetHttpClientWithIdentityAccessToken(string trn, string scope
var httpClient = HostFixture.CreateClient();
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");

if (version is not null)
{
httpClient.DefaultRequestHeaders.Add(VersionRegistry.MinorVersionHeaderName, version);
}

return httpClient;
}

Expand Down
Loading
Loading